mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
Compare commits
386 Commits
v1.2
...
master-dep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef981c93e2 | ||
|
|
10c70209e7 | ||
|
|
8243be8d8e | ||
|
|
a4a3876cdc | ||
|
|
4c89c9061e | ||
|
|
7d8ffdd7da | ||
|
|
eb3b4ca5ec | ||
|
|
0753bfbcf6 | ||
|
|
8d7c95378c | ||
|
|
16eabb90b6 | ||
|
|
08f0ae711e | ||
|
|
569c9efca4 | ||
|
|
6d3b3e1fcb | ||
|
|
112acb2216 | ||
|
|
ceec228718 | ||
|
|
40b5c992e2 | ||
|
|
b35ab67360 | ||
|
|
a99fff46aa | ||
|
|
086760b9b1 | ||
|
|
bffdb9672e | ||
|
|
58412e0491 | ||
|
|
5de9759b90 | ||
|
|
967841fcb9 | ||
|
|
3f0d6c2de6 | ||
|
|
0928b1b430 | ||
|
|
0562989196 | ||
|
|
06bd7aa665 | ||
|
|
a7cf96307c | ||
|
|
8403f9ffd9 | ||
|
|
fbfb81cd25 | ||
|
|
7a803a8783 | ||
|
|
dfefa914af | ||
|
|
707dc32495 | ||
|
|
e7baa27e27 | ||
|
|
f826e4d68a | ||
|
|
e173de9a97 | ||
|
|
7c527e6144 | ||
|
|
74c0727bdb | ||
|
|
cd94a2fa4e | ||
|
|
91bb53aa1d | ||
|
|
069ca27b35 | ||
|
|
c3048fe1f8 | ||
|
|
ee02e70571 | ||
|
|
af69aa8e94 | ||
|
|
1de0535629 | ||
|
|
9b6ed672c4 | ||
|
|
9d704c5ff9 | ||
|
|
6475a9d181 | ||
|
|
a8e1e3cf19 | ||
|
|
44e0b6c62d | ||
|
|
b837e55edc | ||
|
|
d75aa56b38 | ||
|
|
9c73f3cf95 | ||
|
|
9a893e03d7 | ||
|
|
43970189a6 | ||
|
|
682b315e17 | ||
|
|
3078763ec3 | ||
|
|
44ff43c113 | ||
|
|
790264ba08 | ||
|
|
e29d1e69fe | ||
|
|
2689fba992 | ||
|
|
db1d3ccace | ||
|
|
3a6c86092b | ||
|
|
4ccf917707 | ||
|
|
b347794ce5 | ||
|
|
ed25f1f5f5 | ||
|
|
390801f53c | ||
|
|
73f4a69a41 | ||
|
|
197b097ee5 | ||
|
|
f2bbb87eef | ||
|
|
676c8617a5 | ||
|
|
6f8b72c980 | ||
|
|
b2d3bc1076 | ||
|
|
9345309c07 | ||
|
|
50a5ccbaf6 | ||
|
|
460f4857ad | ||
|
|
3b0d288418 | ||
|
|
c712d1234b | ||
|
|
31ad2e4944 | ||
|
|
6249f2957e | ||
|
|
efc8b51c8f | ||
|
|
44bd9f521b | ||
|
|
8b59551905 | ||
|
|
4fe930cdca | ||
|
|
337eddcc0b | ||
|
|
013f1845b5 | ||
|
|
4c6d93881c | ||
|
|
fa33467610 | ||
|
|
1f59e6fe2d | ||
|
|
a5bd6ddc8a | ||
|
|
6660125154 | ||
|
|
9abe9b0d56 | ||
|
|
83b99c47d2 | ||
|
|
cd4c023c61 | ||
|
|
870cbea2ca | ||
|
|
748a78c3c7 | ||
|
|
398b93e9a7 | ||
|
|
40c2c63d1b | ||
|
|
9da1e1575f | ||
|
|
88c066a8f5 | ||
|
|
88da3588e5 | ||
|
|
5309843d77 | ||
|
|
452e056499 | ||
|
|
d86936f6f5 | ||
|
|
c1eecb4c22 | ||
|
|
a62f24b97c | ||
|
|
e3fc4ebd62 | ||
|
|
79aa8fda7e | ||
|
|
fdf8d783b1 | ||
|
|
48ce7267e7 | ||
|
|
5270e70cd1 | ||
|
|
50f5af07f3 | ||
|
|
64fb86f3a9 | ||
|
|
778c21e817 | ||
|
|
af303f447a | ||
|
|
03cf3a5918 | ||
|
|
3aede18926 | ||
|
|
075e15ba0c | ||
|
|
7d2e306f0c | ||
|
|
b11c1125f6 | ||
|
|
57ad861da7 | ||
|
|
d2e109d818 | ||
|
|
dab110475c | ||
|
|
b330f88699 | ||
|
|
481a3dc63f | ||
|
|
1b2b773663 | ||
|
|
b5c2f7a296 | ||
|
|
07e001ee9c | ||
|
|
c30e68d86f | ||
|
|
7d545167df | ||
|
|
31928d56a7 | ||
|
|
99a95f3995 | ||
|
|
a9f1832b6d | ||
|
|
b0a51f4746 | ||
|
|
f55945780b | ||
|
|
81078bcf3d | ||
|
|
c14d53067b | ||
|
|
460615c112 | ||
|
|
98b1771850 | ||
|
|
37def70f6a | ||
|
|
f4364099c6 | ||
|
|
92a6e0122f | ||
|
|
828fa40d4b | ||
|
|
815f13e530 | ||
|
|
9435c0fc2e | ||
|
|
b8898797ed | ||
|
|
be7a84a651 | ||
|
|
0f6b8015a1 | ||
|
|
bd5200de85 | ||
|
|
f21e8045b2 | ||
|
|
8d885a5a67 | ||
|
|
a4bcb33ba4 | ||
|
|
8b46607c29 | ||
|
|
c3bae7fe5e | ||
|
|
2e9b503c59 | ||
|
|
02e33b4038 | ||
|
|
c3347b8e9c | ||
|
|
cd4e049680 | ||
|
|
2bd588045a | ||
|
|
05c036bd9f | ||
|
|
d93b5df5bc | ||
|
|
f12d16ea5b | ||
|
|
d2277d1034 | ||
|
|
4d306e64fb | ||
|
|
8016802ec6 | ||
|
|
2386e35207 | ||
|
|
5d4051928a | ||
|
|
33d2192431 | ||
|
|
ae54e5e213 | ||
|
|
cdf06deb90 | ||
|
|
69de457108 | ||
|
|
040cac41a1 | ||
|
|
abbedf9ae4 | ||
|
|
22b0f8c1c1 | ||
|
|
a4c734435e | ||
|
|
88c5127ab5 | ||
|
|
a08a713dcc | ||
|
|
79764ced60 | ||
|
|
e614f415a9 | ||
|
|
10de320616 | ||
|
|
9bb5afd9a7 | ||
|
|
615c203c7a | ||
|
|
931a90f5c8 | ||
|
|
bf184e7555 | ||
|
|
ab4cf67e80 | ||
|
|
7412a518a5 | ||
|
|
3d84870db8 | ||
|
|
b5b62a5a62 | ||
|
|
7e0be11323 | ||
|
|
e164023b8a | ||
|
|
12ddc6fe82 | ||
|
|
06ffba0239 | ||
|
|
d03448b053 | ||
|
|
0117916da9 | ||
|
|
c2bbfe17a6 | ||
|
|
7b924a197e | ||
|
|
8fe18ed41c | ||
|
|
d16b08bb29 | ||
|
|
cb3c7d4ef0 | ||
|
|
a4d05dc036 | ||
|
|
ce0e57e390 | ||
|
|
33ee358e0c | ||
|
|
82c04c9ed5 | ||
|
|
fc86825b15 | ||
|
|
e873d8cbd0 | ||
|
|
69fe0b5c7d | ||
|
|
e734b41632 | ||
|
|
5c1e9747c6 | ||
|
|
f861886fdf | ||
|
|
efab0bad32 | ||
|
|
bc7d561da2 | ||
|
|
84555afae3 | ||
|
|
f77d676a19 | ||
|
|
454e09cc78 | ||
|
|
2d5caba814 | ||
|
|
f422c4ecfb | ||
|
|
a87b869896 | ||
|
|
2c451b8deb | ||
|
|
f6565ea924 | ||
|
|
7d5930865c | ||
|
|
90b04865f5 | ||
|
|
a03d44ae67 | ||
|
|
a97cede1e0 | ||
|
|
b2690aea08 | ||
|
|
c87178c791 | ||
|
|
b5829cc954 | ||
|
|
9a1cb5f2f9 | ||
|
|
d7fa4a900e | ||
|
|
bf06c418d4 | ||
|
|
937bec3d84 | ||
|
|
99eca2661f | ||
|
|
6b25288e2b | ||
|
|
92c51237c6 | ||
|
|
5bb277bf2f | ||
|
|
5f4c62644d | ||
|
|
41c0af16b3 | ||
|
|
a4c15653d8 | ||
|
|
42d284dce0 | ||
|
|
d084e6072b | ||
|
|
67d4f12cfe | ||
|
|
97d59dbb98 | ||
|
|
f35adfeb3a | ||
|
|
e5e4446366 | ||
|
|
e01c3ac205 | ||
|
|
e09e3a7260 | ||
|
|
7b43739dbc | ||
|
|
8e5bdebea0 | ||
|
|
555a6211bd | ||
|
|
6249ac6b2a | ||
|
|
494328cf53 | ||
|
|
e2e0a0295b | ||
|
|
676a7ef24e | ||
|
|
c01c9ce901 | ||
|
|
d37eb9a59b | ||
|
|
d4a0abf704 | ||
|
|
ec1a2afc5f | ||
|
|
3e6e43fd68 | ||
|
|
c37d2fd594 | ||
|
|
25b5928a22 | ||
|
|
88b62730e1 | ||
|
|
f00ccedbd3 | ||
|
|
de2fa3770b | ||
|
|
be06b1e0cf | ||
|
|
29f80f9a76 | ||
|
|
e85a9e9d6a | ||
|
|
54b224d929 | ||
|
|
d0e5bec845 | ||
|
|
b125e55a4a | ||
|
|
611d2c40bd | ||
|
|
8b1fd3ac79 | ||
|
|
9831038729 | ||
|
|
8bbffae837 | ||
|
|
b2ca072120 | ||
|
|
9aeb2177cf | ||
|
|
3dfdc300c5 | ||
|
|
4e65f0a955 | ||
|
|
dd9eaf49ff | ||
|
|
6b0f617d1b | ||
|
|
ba53a5c760 | ||
|
|
6958efcd0f | ||
|
|
85e8175338 | ||
|
|
a14b5e2be1 | ||
|
|
d8a6f67688 | ||
|
|
979a21afae | ||
|
|
8269d4946f | ||
|
|
a39881e73f | ||
|
|
cfa5030f85 | ||
|
|
c84d1181bb | ||
|
|
d92b17a386 | ||
|
|
9e857035bd | ||
|
|
79383fc83a | ||
|
|
0e0116b274 | ||
|
|
deba5325a7 | ||
|
|
a73d71b966 | ||
|
|
06ecfd93d5 | ||
|
|
ac34b47c26 | ||
|
|
9b16f2d84a | ||
|
|
cc594e3415 | ||
|
|
4d6fb4975e | ||
|
|
0f186576d3 | ||
|
|
837fdf78a0 | ||
|
|
6382302a30 | ||
|
|
da8943095b | ||
|
|
26da03f029 | ||
|
|
73dd745fe8 | ||
|
|
eca3e09482 | ||
|
|
d8d2676596 | ||
|
|
a6f6e0b05d | ||
|
|
e9622e6991 | ||
|
|
fcf24b3a45 | ||
|
|
77f81c1bde | ||
|
|
fc7a526a67 | ||
|
|
e71da620c6 | ||
|
|
0d6a6e163c | ||
|
|
1c5e43d378 | ||
|
|
d16d685b70 | ||
|
|
856a98d875 | ||
|
|
bc3ba23162 | ||
|
|
0e9ca3ff90 | ||
|
|
24d683332d | ||
|
|
ebc20be508 | ||
|
|
8bf7b3ee06 | ||
|
|
78f7f6b9cf | ||
|
|
bfb7abb843 | ||
|
|
70a93d9905 | ||
|
|
153d489400 | ||
|
|
40014755e2 | ||
|
|
acb9c60c9a | ||
|
|
934066fed8 | ||
|
|
e916deaafc | ||
|
|
1130581152 | ||
|
|
5c227c01e4 | ||
|
|
a6897ad2f8 | ||
|
|
5be5c2b59e | ||
|
|
9c8656f4b5 | ||
|
|
14658a5016 | ||
|
|
5f708470fc | ||
|
|
f2a0e75bb1 | ||
|
|
4f13c492f3 | ||
|
|
6123b6bafc | ||
|
|
2c142c506c | ||
|
|
9f1cbc0233 | ||
|
|
b06d1a5c2c | ||
|
|
beb32da3b4 | ||
|
|
2e82bf84d2 | ||
|
|
25ddb0ca99 | ||
|
|
02239b4f74 | ||
|
|
e0a3c44776 | ||
|
|
e513e01b29 | ||
|
|
138dbbc8a5 | ||
|
|
6f87870e09 | ||
|
|
2210c0c4c5 | ||
|
|
8ab8eee1f4 | ||
|
|
d77e459da3 | ||
|
|
6ee0d63766 | ||
|
|
0c47f262ea | ||
|
|
ffa4cf5b29 | ||
|
|
ca26955ac2 | ||
|
|
f05fe60030 | ||
|
|
2b15c13e88 | ||
|
|
df1ba841c5 | ||
|
|
47da18b717 | ||
|
|
117ab617b6 | ||
|
|
7e5659f49f | ||
|
|
e493acca0d | ||
|
|
6de6853cfe | ||
|
|
8ec6cc548d | ||
|
|
c0e9159c1e | ||
|
|
1bd752a60f | ||
|
|
df3694b539 | ||
|
|
6594d6fa42 | ||
|
|
8425eeb685 | ||
|
|
2daa724720 | ||
|
|
6db77ed4f2 | ||
|
|
b08d30d043 | ||
|
|
b3e8f5e50f | ||
|
|
1f5e2645f0 | ||
|
|
04e55b5498 | ||
|
|
32b4c451e4 | ||
|
|
05f6d68070 | ||
|
|
e572967c08 | ||
|
|
1dc8d50289 | ||
|
|
7caabc0fa8 | ||
|
|
d819bf60f5 | ||
|
|
65bfd93761 | ||
|
|
3a98201837 |
10
.gitattributes
vendored
10
.gitattributes
vendored
@@ -1,4 +1,14 @@
|
||||
* text=input
|
||||
|
||||
*.php text eol=lf
|
||||
*.js text eol=lf
|
||||
*.css text eol=lf
|
||||
*.sql text eol=lf
|
||||
aowow text eol=lf
|
||||
prQueue text eol=lf
|
||||
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.ttf binary
|
||||
*.swf binary
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,8 +3,8 @@
|
||||
|
||||
# cache
|
||||
/cache/template/*
|
||||
/setup/generated/alphaMaps/*.png
|
||||
/cache/firstrun
|
||||
/cache/alphaMaps/*
|
||||
/cache/setup/*
|
||||
|
||||
# extract from MPQ
|
||||
/setup/mpqdata/*
|
||||
|
||||
32
README.md
32
README.md
@@ -13,20 +13,20 @@ While the first releases can be found as early as 2008, today it is impossible t
|
||||
This is a complete rewrite of the serverside php code and update to the clientside javascripts from 2008 to something 2013ish.
|
||||
|
||||
I myself take no credit for the clientside scripting, design and layout that these php-scripts cater to.
|
||||
Also, this project is not meant to be used for commercial puposes of any kind!
|
||||
Also, this project is not meant to be used for commercial purposes of any kind!
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
+ Webserver running PHP ≥ 7.4 — 8.0 including extensions:
|
||||
+ Webserver running PHP ≥ 8.2 including extensions:
|
||||
+ [SimpleXML](https://www.php.net/manual/en/book.simplexml.php)
|
||||
+ [GD](https://www.php.net/manual/en/book.image)
|
||||
+ [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
|
||||
+ [Multibyte String](https://www.php.net/manual/en/book.mbstring.php)
|
||||
+ [File Information](https://www.php.net/manual/en/book.fileinfo.php)
|
||||
+ [GNU Multiple Precision](https://www.php.net/manual/en/book.gmp.php) (When using TrinityCore as auth source)
|
||||
+ MySQL ≥ 5.5.30
|
||||
+ [TDB 335.21101](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.21101)
|
||||
+ MySQL ≥ 5.7.0 OR MariaDB ≥ 10.6.4 OR similar
|
||||
+ [TDB 335.21101](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.21101) (no other other providers are supported at this time)
|
||||
+ WIN: php.exe needs to be added to the `PATH` system variable, if it isn't already.
|
||||
+ Tools require cmake: Please refer to the individual repositories for detailed information
|
||||
+ [MPQExtractor](https://github.com/Sarjuuk/MPQExtractor) / [FFmpeg](https://ffmpeg.org/download.html) / (optional: [BLPConverter](https://github.com/Sarjuuk/BLPConverter))
|
||||
@@ -39,7 +39,7 @@ audio processing may require [lame](https://sourceforge.net/projects/lame/files/
|
||||
#### Highly Recommended
|
||||
+ setting the following configuration values on your TrinityCore server will greatly increase the accuracy of spawn points
|
||||
> Calculate.Creature.Zone.Area.Data = 1
|
||||
> Calculate.Gameoject.Zone.Area.Data = 1
|
||||
> Calculate.Gameobject.Zone.Area.Data = 1
|
||||
|
||||
|
||||
## Install
|
||||
@@ -72,19 +72,19 @@ Extract the following directories from the client archives into `setup/mpqdata/`
|
||||
|
||||
.. once is enough (still apply the localeCode though):
|
||||
> \<localeCode>/Interface/TalentFrame/
|
||||
> \<localeCode>/Interface/Glues/Credits/
|
||||
> \<localeCode>/Interface/Icons/
|
||||
> \<localeCode>/Interface/Spellbook/
|
||||
> \<localeCode>/Interface/PaperDoll/
|
||||
> \<localeCode>/Interface/GLUES/CHARACTERCREATE/
|
||||
> \<localeCode>/Interface/Glues/CharacterCreate/
|
||||
> \<localeCode>/Interface/Pictures
|
||||
> \<localeCode>/Interface/PvPRankBadges
|
||||
> \<localeCode>/Interface/FlavorImages
|
||||
> \<localeCode>/Interface/Calendar/Holidays/
|
||||
> \<localeCode>/Sound/
|
||||
|
||||
.. optionaly (not used in AoWoW):
|
||||
> \<localeCode>/Interface/GLUES/LOADINGSCREENS/
|
||||
.. optionally (not used in AoWoW):
|
||||
> \<localeCode>/Interface/Glues/Loadingscreens/
|
||||
> \<localeCode>/Interface/Glues/Credits/
|
||||
|
||||
#### 5. Reencode the audio files
|
||||
WAV-files need to be reencoded as `ogg/vorbis` and some MP3s may identify themselves as `application/octet-stream` instead of `audio/mpeg`.
|
||||
@@ -100,24 +100,24 @@ When you've created your admin account you are done.
|
||||
## Troubleshooting
|
||||
|
||||
Q: The Page appears white, without any styles.
|
||||
A: The static content is not being displayed. You are either using SSL and AoWoW is unable to detect it or STATIC_HOST is not defined poperly. Either way this can be fixed via config `php aowow --siteconfig`
|
||||
A: The static content is not being displayed. You are either using SSL and AoWoW is unable to detect it or STATIC_HOST is not defined properly. Either way this can be fixed via config `php aowow --siteconfig`
|
||||
|
||||
Q: Fatal error: Can't inherit abstract function \<functionName> (previously declared abstract in \<className>) in \<path>
|
||||
A: You are using cache optimization modules for php, that are in confict with each other. (Zend OPcache, XCache, ..) Disable all but one.
|
||||
A: You are using cache optimization modules for php, that are in conflict with each other. (Zend OPcache, XCache, ..) Disable all but one.
|
||||
|
||||
Q: Some generated images appear distorted or have alpha-channel issues.
|
||||
A: Image compression is beyond my understanding, so i am unable to fix these issues within the blpReader.
|
||||
BUT you can convert the affected blp file into a png file in the same directory, using the provided BLPConverter.
|
||||
AoWoW will priorize png files over blp files.
|
||||
AoWoW will prioritize png files over blp files.
|
||||
|
||||
Q: How can i get the modelviewer to work?
|
||||
A: You can't anymore. Wowhead switched from Flash to WebGL (as they should) and moved or deleted the old files in the process.
|
||||
|
||||
Q: I'm getting random javascript errors!
|
||||
A: Some server configurations or external services (like Cloudflare) come with modules, that automaticly minify js and css files. Sometimes they break in the process. Disable the module in this case.
|
||||
A: Some server configurations or external services (like Cloudflare) come with modules, that automatically minify js and css files. Sometimes they break in the process. Disable the module in this case.
|
||||
|
||||
Q: Some search results within the profiler act rather strange. How does it work?
|
||||
A: Whenever you try to view a new character, AoWoW needs to fetch it first. Since the data is structured for the needs of TrinityCore and not for easy viewing, AoWoW needs to save and restructure it locally. To this end, every char request is placed in a queue. While the queue is not empty, a single instance of `prQueue` is run in the background as not to overwhelm the characters database with requests. This also means, some more exotic search queries can't be run agains the characters database and have to use the incomplete/outdated cached profiles of AoWoW.
|
||||
A: Whenever you try to view a new character, AoWoW needs to fetch it first. Since the data is structured for the needs of TrinityCore and not for easy viewing, AoWoW needs to save and restructure it locally. To this end, every char request is placed in a queue. While the queue is not empty, a single instance of `prQueue` is run in the background as not to overwhelm the characters database with requests. This also means, some more exotic search queries can't be run against the characters database and have to use the incomplete/outdated cached profiles of AoWoW.
|
||||
|
||||
Q: Screenshot upload fails, because the file size is too large and/or the subdirectories are visible from the web!
|
||||
A: That's a web server configuration issue. If you are using Apache you may need to [enable the use of .htaccess](http://httpd.apache.org/docs/2.4/de/mod/core.html#allowoverride). Other servers require individual configuration.
|
||||
@@ -134,7 +134,7 @@ A: A search is only conducted against the currently used locale. You may have on
|
||||
|
||||
|
||||
## Special Thanks
|
||||
Said website with the red smiling rocket, for providing this beautifull website!
|
||||
Please do not reagard this project as blatant rip-off, rather as "We do really liked your presentation, but since time and content progresses, you are sadly no longer supplying the data we need".
|
||||
Said website with the red smiling rocket, for providing this beautiful website!
|
||||
Please do not regard this project as blatant rip-off, rather as "We do really liked your presentation, but since time and content progresses, you are sadly no longer supplying the data we need".
|
||||
|
||||

|
||||
|
||||
17
aowow
17
aowow
@@ -1,12 +1,15 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require 'includes/shared.php';
|
||||
|
||||
if (!CLI)
|
||||
if (PHP_SAPI !== 'cli')
|
||||
die("this script must be run from CLI\n");
|
||||
if (CLI && getcwd().DIRECTORY_SEPARATOR.'aowow' != __FILE__)
|
||||
die("this script must be run from root directory\n");
|
||||
else
|
||||
require 'setup/setup.php';
|
||||
if (PHP_SAPI === 'cli' && getcwd().DIRECTORY_SEPARATOR.'aowow' != __FILE__)
|
||||
die("this script must be run from the aowow root directory\n");
|
||||
|
||||
require_once 'includes/kernel.php';
|
||||
require_once 'includes/setup/cli.class.php';
|
||||
require_once 'includes/setup/timer.class.php';
|
||||
|
||||
require_once 'setup/setup.php';
|
||||
|
||||
?>
|
||||
|
||||
@@ -4,13 +4,16 @@ if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
function extAuth($user, $pass, &$userId = 0, &$userGroup = -1)
|
||||
function extAuth(string &$usernameOrEmail, string $password, int &$userId = 0, int &$userGroup = -1) : int
|
||||
{
|
||||
/*
|
||||
insert some auth mechanism here
|
||||
|
||||
see defines for usable return values
|
||||
set userId for identification
|
||||
set usernameOrEmail to a valid username, do not pass back the email if used for login
|
||||
set userId to uid from external auth provider for identification
|
||||
(optional) set userGroup to a valid userGroup (see U_GROUP_* defines)
|
||||
|
||||
return an AUTH_* result (see defines)
|
||||
*/
|
||||
|
||||
return AUTH_INTERNAL_ERR;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
Mapper.multiLevelZones = {
|
||||
206: ['206-1', '206-2', '206-3'],
|
||||
209: ['209-1', '209-2', '209-3', '209-4', '209-5', '209-6', '209-7'],
|
||||
616: ['616-1', '616_1', '616_2'],
|
||||
719: ['719-1', '719-2', '719-3'],
|
||||
721: ['721-1', '721-2', '721-3', '721-4'],
|
||||
796: ['796-1', '796-2', '796-3', '796-4'],
|
||||
1196: ['1196-1', '1196-2'],
|
||||
1337: ['1337-1', '1337-2'],
|
||||
1581: ['1581-1', '1581-2'],
|
||||
1583: ['1583-1', '1583-2', '1583-3', '1583-4', '1583-5', '1583-6', '1583-7'],
|
||||
1584: ['1584-1', '1584-2'],
|
||||
2017: ['2017-1', '2017-2'],
|
||||
2057: ['2057-1', '2057-2', '2057-3', '2057-4'],
|
||||
2100: ['2100-1', '2100-2'],
|
||||
2557: ['2557-1', '2557-2', '2557-3', '2557-4', '2557-5', '2557-6'],
|
||||
2677: ['2677-1', '2677-2', '2677-3', '2677-4'],
|
||||
3959: ['3959', '3959-1', '3959-2', '3959-3', '3959-4', '3959-5', '3959-6', '3959-7'],
|
||||
3428: ['3428-1', '3428-2', '3428-3'],
|
||||
3456: ['3456-1', '3456-2', '3456-3', '3456-4', '3456-5', '3456-6'],
|
||||
3457: ['3457-1', '3457-2', '3457-3', '3457-4', '3457-5', '3457-6', '3457-7', '3457-8', '3457-9', '3457-10', '3457-11', '3457-12', '3457-13', '3457-14', '3457-15', '3457-16', '3457-17'],
|
||||
3477: ['3477-1', '3477-2', '3477-3'],
|
||||
3715: ['3715-1', '3715-2'],
|
||||
3790: ['3790-1', '3790-2'],
|
||||
3791: ['3791-1', '3791-2'],
|
||||
3848: ['3848-1', '3848-2', '3848-3'],
|
||||
3849: ['3849-1', '3849-2'],
|
||||
4075: ['4075', '4075-1'],
|
||||
4100: ['4100-1', '4100-2'],
|
||||
4131: ['4131-1', '4131-2'],
|
||||
4196: ['4196-1', '4196-2'],
|
||||
4228: ['4228-1', '4228-2', '4228-3', '4228-4'],
|
||||
4272: ['4272-1', '4272-2'],
|
||||
4273: ['4273-0', '4273-1', '4273-2', '4273-3', '4273-4', '4273-5'],
|
||||
4277: ['4277-1', '4277-2', '4277-3'],
|
||||
4395: ['4395-1', '4395-2'],
|
||||
4494: ['4494-1', '4494-2'],
|
||||
4714: ['4714-1', '4714_1', '4714_2', '4714_3'],
|
||||
4722: ['4722-1', '4722-2'],
|
||||
4812: ['4812-1', '4812-2', '4812-3', '4812-4', '4812-5', '4812-6', '4812-7', '4812-8'],
|
||||
};
|
||||
|
||||
/*
|
||||
var g_zone_areas = {};
|
||||
in locale files
|
||||
*/
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -10,9 +12,9 @@ class AjaxAccount extends AjaxHandler
|
||||
'groups' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'save' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'delete' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList'],
|
||||
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAccount::checkName' ],
|
||||
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAccount::checkScale' ],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdList'],
|
||||
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAccount::checkName' ],
|
||||
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAccount::checkScale' ],
|
||||
'reset' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'mode' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'type' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
@@ -20,18 +22,12 @@ class AjaxAccount extends AjaxHandler
|
||||
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
// 'sessionKey' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
|
||||
);
|
||||
protected $_get = array(
|
||||
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if (is_numeric($this->_get['locale']))
|
||||
User::useLocale($this->_get['locale']);
|
||||
|
||||
if (!$this->params || !User::$id)
|
||||
if (!$this->params || !User::isLoggedIn())
|
||||
return;
|
||||
|
||||
// select handler
|
||||
@@ -50,22 +46,21 @@ class AjaxAccount extends AjaxHandler
|
||||
$type = $this->_post['type'];
|
||||
$ids = $this->_post['id'];
|
||||
|
||||
if (!Type::exists($type) || empty($ids))
|
||||
if ($validIds = Type::validateIds($this->_post['type'], $this->_post['id']))
|
||||
{
|
||||
trigger_error('AjaxAccount::handleExclude - invalid type #'.$type.(empty($ids) ? ' or id-list empty' : ''), E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// ready for some bullshit? here it comes!
|
||||
// we don't get signaled whether an id should be added to or removed from either includes or excludes
|
||||
// so we throw everything into one table and toggle the mode if its already in here
|
||||
|
||||
$includes = DB::Aowow()->selectCol('SELECT typeId FROM ?_profiler_excludes WHERE type = ?d AND typeId IN (?a)', $type, $ids);
|
||||
$includes = DB::Aowow()->selectCol('SELECT `typeId` FROM ?_profiler_excludes WHERE `type` = ?d AND `typeId` IN (?a)', $this->_post['type'], $validIds);
|
||||
|
||||
foreach ($ids as $typeId)
|
||||
DB::Aowow()->query('INSERT INTO ?_account_excludes (`userId`, `type`, `typeId`, `mode`) VALUES (?a) ON DUPLICATE KEY UPDATE mode = (mode ^ 0x3)', array(
|
||||
User::$id, $type, $typeId, in_array($includes, $typeId) ? 2 : 1
|
||||
));
|
||||
foreach ($validIds as $typeId)
|
||||
DB::Aowow()->query('INSERT INTO ?_account_excludes (`userId`, `type`, `typeId`, `mode`) VALUES (?a) ON DUPLICATE KEY UPDATE `mode` = (`mode` ^ 0x3)',
|
||||
[User::$id, $this->_post['type'], $typeId, in_array($typeId, $includes) ? 2 : 1]
|
||||
);
|
||||
}
|
||||
else
|
||||
trigger_error('AjaxAccount::handleExclude - invalid type #'.$type.(empty($ids) ? ' or id-list empty' : ''), E_USER_ERROR);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -94,24 +89,24 @@ class AjaxAccount extends AjaxHandler
|
||||
|
||||
if ($this->_post['id'] && ($id = $this->_post['id'][0]))
|
||||
{
|
||||
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account_weightscales WHERE userId = ?d AND id = ?d', User::$id, $id))
|
||||
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account_weightscales WHERE `userId` = ?d AND `id` = ?d', User::$id, $id))
|
||||
{
|
||||
trigger_error('AjaxAccount::handleWeightscales - scale #'.$id.' not in db or owned by user #'.User::$id, E_USER_ERROR);
|
||||
return '0';
|
||||
}
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_account_weightscales SET `name` = ? WHERE id = ?d', $this->_post['name'], $id);
|
||||
DB::Aowow()->query('UPDATE ?_account_weightscales SET `name` = ? WHERE `id` = ?d', $this->_post['name'], $id);
|
||||
}
|
||||
else
|
||||
{
|
||||
$nScales = DB::Aowow()->selectCell('SELECT COUNT(id) FROM ?_account_weightscales WHERE userId = ?d', User::$id);
|
||||
$nScales = DB::Aowow()->selectCell('SELECT COUNT(`id`) FROM ?_account_weightscales WHERE `userId` = ?d', User::$id);
|
||||
if ($nScales >= 5) // more or less hard-defined in LANG.message_weightscalesaveerror
|
||||
return '0';
|
||||
|
||||
$id = DB::Aowow()->query('INSERT INTO ?_account_weightscales (`userId`, `name`) VALUES (?d, ?)', User::$id, $this->_post['name']);
|
||||
}
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_account_weightscale_data WHERE id = ?d', $id);
|
||||
DB::Aowow()->query('DELETE FROM ?_account_weightscale_data WHERE `id` = ?d', $id);
|
||||
|
||||
foreach (explode(',', $this->_post['scale']) as $s)
|
||||
{
|
||||
@@ -125,13 +120,14 @@ class AjaxAccount extends AjaxHandler
|
||||
return (string)$id;
|
||||
}
|
||||
else if ($this->_post['delete'] && $this->_post['id'] && $this->_post['id'][0])
|
||||
DB::Aowow()->query('DELETE FROM ?_account_weightscales WHERE id = ?d AND userId = ?d', $this->_post['id'][0], User::$id);
|
||||
else
|
||||
{
|
||||
DB::Aowow()->query('DELETE FROM ?_account_weightscales WHERE `id` = ?d AND `userId` = ?d', $this->_post['id'][0], User::$id);
|
||||
return '';
|
||||
}
|
||||
|
||||
trigger_error('AjaxAccount::handleWeightscales - malformed request received', E_USER_ERROR);
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleFavorites() : void
|
||||
{
|
||||
@@ -146,8 +142,7 @@ class AjaxAccount extends AjaxHandler
|
||||
|
||||
if ($type = $this->_post['add'])
|
||||
{
|
||||
$tc = Type::newList($type, [['id', $typeId]]);
|
||||
if (!$tc || $tc->error)
|
||||
if (!Type::validateIds($this->_post['add'], $this->_post['id']))
|
||||
{
|
||||
trigger_error('AjaxAccount::handleFavorites - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR);
|
||||
return;
|
||||
@@ -171,7 +166,7 @@ class AjaxAccount extends AjaxHandler
|
||||
{
|
||||
$var = trim(urldecode($val));
|
||||
|
||||
return filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_AOWOW);
|
||||
return filter_var($var, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxAdmin extends AjaxHandler
|
||||
{
|
||||
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets', 'spawn-override', 'guide'];
|
||||
protected $validParams = ['screenshots', 'siteconfig', 'weight-presets', 'spawn-override', 'guide', 'comment'];
|
||||
protected $_get = array(
|
||||
'action' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned'],
|
||||
'key' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkKey' ],
|
||||
'all' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
|
||||
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
|
||||
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
|
||||
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkUser' ],
|
||||
'val' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
|
||||
'guid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
|
||||
'area' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ],
|
||||
'floor' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt' ]
|
||||
'action' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine' ],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdListUnsigned'],
|
||||
'key' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAdmin::checkKey' ],
|
||||
'all' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkEmptySet' ],
|
||||
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAdmin::checkUser' ],
|
||||
'val' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'guid' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'area' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'floor' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ]
|
||||
);
|
||||
protected $_post = array(
|
||||
'alt' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
|
||||
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkScale'],
|
||||
'__icon' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxAdmin::checkKey' ],
|
||||
'status' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
|
||||
'msg' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW]
|
||||
'alt' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob'],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'scale' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAdmin::checkScale' ],
|
||||
'__icon' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxAdmin::checkKey' ],
|
||||
'status' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt' ],
|
||||
'msg' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob']
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -89,6 +91,13 @@ class AjaxAdmin extends AjaxHandler
|
||||
|
||||
$this->handler = 'guideManage';
|
||||
}
|
||||
else if ($this->params[0] == 'comment')
|
||||
{
|
||||
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD))
|
||||
return;
|
||||
|
||||
$this->handler = 'commentOutOfDate';
|
||||
}
|
||||
}
|
||||
|
||||
// get all => null (optional)
|
||||
@@ -114,7 +123,7 @@ class AjaxAdmin extends AjaxHandler
|
||||
if ($this->_get['type'] && $this->_get['type'] && $this->_get['typeid'] && $this->_get['typeid'])
|
||||
$res = CommunityContent::getScreenshotsForManager($this->_get['type'], $this->_get['typeid']);
|
||||
else if ($this->_get['user'])
|
||||
if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', $this->_get['user']))
|
||||
if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user']))
|
||||
$res = CommunityContent::getScreenshotsForManager(0, 0, $uId);
|
||||
|
||||
return 'ssm_screenshotData = '.Util::toJSON($res);
|
||||
@@ -317,20 +326,7 @@ class AjaxAdmin extends AjaxHandler
|
||||
$key = trim($this->_get['key']);
|
||||
$val = trim(urldecode($this->_get['val']));
|
||||
|
||||
if ($key === null)
|
||||
return 'empty option name given';
|
||||
|
||||
if (!strlen($key))
|
||||
return 'invalid chars in option name: [a-z 0-9 _ . -] are allowed';
|
||||
|
||||
if (ini_get($key) === false || ini_set($key, $val) === false)
|
||||
return 'this configuration option cannot be set';
|
||||
|
||||
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_config WHERE `flags` & ?d AND `key` = ?', CON_FLAG_PHP, $key))
|
||||
return 'this configuration option is already in use';
|
||||
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_config (`key`, `value`, `cat`, `flags`) VALUES (?, ?, 0, ?d)', $key, $val, CON_FLAG_TYPE_STRING | CON_FLAG_PHP);
|
||||
return '';
|
||||
return Cfg::add($key, $val);
|
||||
}
|
||||
|
||||
protected function confRemove() : string
|
||||
@@ -338,39 +334,15 @@ class AjaxAdmin extends AjaxHandler
|
||||
if (!$this->reqGET('key'))
|
||||
return 'invalid configuration option given';
|
||||
|
||||
if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->_get['key'], CON_FLAG_PERSISTENT))
|
||||
return '';
|
||||
else
|
||||
return 'option name is either protected or was not found';
|
||||
return Cfg::delete($this->_get['key']);
|
||||
}
|
||||
|
||||
protected function confUpdate() : string
|
||||
{
|
||||
$key = trim($this->_get['key']);
|
||||
$val = trim(urldecode($this->_get['val']));
|
||||
$msg = '';
|
||||
|
||||
if (!strlen($key))
|
||||
return 'empty option name given';
|
||||
|
||||
$cfg = DB::Aowow()->selectRow('SELECT `flags`, `value` FROM ?_config WHERE `key` = ?', $key);
|
||||
if (!$cfg)
|
||||
return 'configuration option not found';
|
||||
|
||||
if (!($cfg['flags'] & CON_FLAG_TYPE_STRING) && !strlen($val))
|
||||
return 'empty value given';
|
||||
else if ($cfg['flags'] & CON_FLAG_TYPE_INT && !preg_match('/^-?\d+$/i', $val))
|
||||
return "value must be integer";
|
||||
else if ($cfg['flags'] & CON_FLAG_TYPE_FLOAT && !preg_match('/^-?\d*(,|.)?\d+$/i', $val))
|
||||
return "value must be float";
|
||||
else if ($cfg['flags'] & CON_FLAG_TYPE_BOOL && $val != '1')
|
||||
$val = '0';
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $val, $key);
|
||||
if (!$this->confOnChange($key, $val, $msg))
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $cfg['value'], $key);
|
||||
|
||||
return $msg;
|
||||
return Cfg::set($key, $val);
|
||||
}
|
||||
|
||||
protected function wtSave() : string
|
||||
@@ -413,14 +385,14 @@ class AjaxAdmin extends AjaxHandler
|
||||
$area = $this->_get['area'];
|
||||
$floor = $this->_get['floor'];
|
||||
|
||||
if (!in_array($type, [Type::NPC, Type::OBJECT, Type::SOUND, Type::AREATRIGGER]))
|
||||
if (!in_array($type, [Type::NPC, Type::OBJECT, Type::SOUND, Type::AREATRIGGER, Type::ZONE]))
|
||||
return '-3';
|
||||
|
||||
DB::Aowow()->query('REPLACE INTO ?_spawns_override VALUES (?d, ?d, ?d, ?d, ?d)', $type, $guid, $area, $floor, AOWOW_REVISION);
|
||||
|
||||
if ($wPos = Game::getWorldPosForGUID($type, $guid))
|
||||
if ($wPos = WorldPosition::getForGUID($type, $guid))
|
||||
{
|
||||
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor))
|
||||
if ($point = WorldPosition::toZonePos($wPos[$guid]['mapId'], $wPos[$guid]['posX'], $wPos[$guid]['posY'], $area, $floor))
|
||||
{
|
||||
$updGUIDs = [$guid];
|
||||
$newPos = array(
|
||||
@@ -434,9 +406,9 @@ class AjaxAdmin extends AjaxHandler
|
||||
if ($type == Type::NPC)
|
||||
{
|
||||
$jobs = array(
|
||||
'SELECT -w.id AS `entry`, w.point AS `pointId`, w.position_y AS `posX`, w.position_x AS `posY` FROM creature_addon ca JOIN waypoint_data w ON w.id = ca.path_id WHERE ca.guid = ?d AND ca.path_id <> 0',
|
||||
'SELECT `entry`, `pointId`, `location_y` AS `posX`, `location_x` AS `posY` FROM `script_waypoint` WHERE `entry` = ?d',
|
||||
'SELECT `entry`, `pointId`, `position_y` AS `posX`, `position_x` AS `posY` FROM `waypoints` WHERE `entry` = ?d'
|
||||
'SELECT -w.id AS `entry`, w.point AS `pointId`, w.position_x AS `posX`, w.position_y AS `posY` FROM creature_addon ca JOIN waypoint_data w ON w.id = ca.path_id WHERE ca.guid = ?d AND ca.path_id <> 0',
|
||||
'SELECT `entry`, `pointId`, `location_x` AS `posX`, `location_y` AS `posY` FROM `script_waypoint` WHERE `entry` = ?d',
|
||||
'SELECT `entry`, `pointId`, `position_x` AS `posX`, `position_y` AS `posY` FROM `waypoints` WHERE `entry` = ?d'
|
||||
);
|
||||
|
||||
foreach ($jobs as $idx => $job)
|
||||
@@ -445,7 +417,7 @@ class AjaxAdmin extends AjaxHandler
|
||||
{
|
||||
foreach ($swp as $w)
|
||||
{
|
||||
if ($point = Game::worldPosToZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor))
|
||||
if ($point = WorldPosition::toZonePos($wPos[$guid]['mapId'], $w['posX'], $w['posY'], $area, $floor))
|
||||
{
|
||||
$p = array(
|
||||
'posX' => $point[0]['posX'],
|
||||
@@ -453,11 +425,12 @@ class AjaxAdmin extends AjaxHandler
|
||||
'areaId' => $point[0]['areaId'],
|
||||
'floor' => $point[0]['floor']
|
||||
);
|
||||
}
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_creature_waypoints SET ?a WHERE `creatureOrPath` = ?d AND `point` = ?d', $p, $w['entry'], $w['pointId']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also move linked vehicle accessories (on the very same position)
|
||||
$updGUIDs = array_merge($updGUIDs, DB::Aowow()->selectCol('SELECT s2.guid FROM ?_spawns s1 JOIN ?_spawns s2 ON s1.posX = s2.posX AND s1.posY = s2.posY AND
|
||||
@@ -484,11 +457,11 @@ class AjaxAdmin extends AjaxHandler
|
||||
|
||||
// set display rev to latest
|
||||
if ($status == GUIDE_STATUS_APPROVED)
|
||||
DB::Aowow()->query('UPDATE ?_guides SET `rev` = (SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1) WHERE `id` = ?d', Type::GUIDE, $id, $id);
|
||||
DB::Aowow()->query('UPDATE ?_guides SET `rev` = (SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1), `approveUserId` = ?d, `approveDate` = ?d WHERE `id` = ?d', Type::GUIDE, $id, User::$id, time(), $id);
|
||||
|
||||
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $id, time(), User::$id, $status);
|
||||
if ($msg)
|
||||
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)' , $id, time(), User::$id, $msg);
|
||||
DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)', $id, time(), User::$id, $msg);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -526,6 +499,28 @@ class AjaxAdmin extends AjaxHandler
|
||||
return '-1';
|
||||
}
|
||||
|
||||
protected function commentOutOfDate() : string
|
||||
{
|
||||
$ok = false;
|
||||
switch ($this->_post['status'])
|
||||
{
|
||||
case 0: // up to date
|
||||
if ($ok = DB::Aowow()->query('UPDATE ?_comments SET `flags` = `flags` & ~?d WHERE `id` = ?d', CC_FLAG_OUTDATED, $this->_post['id']))
|
||||
if ($rep = new Report(Report::MODE_COMMENT, Report::CO_OUT_OF_DATE, $this->_post['id']))
|
||||
$rep->close(Report::STATUS_CLOSED_WONTFIX);
|
||||
break;
|
||||
case 1: // outdated, mark as deleted and clear other flags (sticky + outdated)
|
||||
if ($ok = DB::Aowow()->query('UPDATE ?_comments SET `flags` = ?d, `deleteUserId` = ?d, `deleteDate` = ?d WHERE `id` = ?d', CC_FLAG_DELETED, User::$id, time(), $this->_post['id']))
|
||||
if ($rep = new Report(Report::MODE_COMMENT, Report::CO_OUT_OF_DATE, $this->_post['id']))
|
||||
$rep->close(Report::STATUS_CLOSED_SOLVED);
|
||||
break;
|
||||
default:
|
||||
trigger_error('AjaxHandler::comentOutOfDate - called with invalid status');
|
||||
}
|
||||
|
||||
return $ok ? '1' : '0';
|
||||
}
|
||||
|
||||
|
||||
/***************************/
|
||||
/* additional input filter */
|
||||
@@ -534,7 +529,7 @@ class AjaxAdmin extends AjaxHandler
|
||||
protected static function checkKey(string $val) : string
|
||||
{
|
||||
// expecting string
|
||||
if (preg_match('/[^a-z0-9_\.\-]/i', $val))
|
||||
if (preg_match(Cfg::PATTERN_INV_CONF_KEY, $val))
|
||||
return '';
|
||||
|
||||
return strtolower($val);
|
||||
@@ -557,73 +552,6 @@ class AjaxAdmin extends AjaxHandler
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
/**********/
|
||||
/* helper */
|
||||
/**********/
|
||||
|
||||
private static function confOnChange(string $key, string $val, string &$msg) : bool
|
||||
{
|
||||
$fn = $buildList = null;
|
||||
|
||||
switch ($key)
|
||||
{
|
||||
case 'battlegroup':
|
||||
$buildList = 'realms,realmMenu';
|
||||
break;
|
||||
case 'name_short':
|
||||
$buildList = 'searchboxBody,demo,searchplugin';
|
||||
break;
|
||||
case 'site_host':
|
||||
$buildList = 'searchplugin,demo,power,searchboxBody';
|
||||
break;
|
||||
case 'static_host':
|
||||
$buildList = 'searchplugin,power,searchboxBody,searchboxScript';
|
||||
break;
|
||||
case 'contact_email':
|
||||
$buildList = 'markup';
|
||||
break;
|
||||
case 'locales':
|
||||
$buildList = 'locales';
|
||||
$msg .= ' * remember to rebuild all static files for the language you just added.<br />';
|
||||
$msg .= ' * you can speed this up by supplying the regionCode to the setup: <pre class="q1">--locales=<regionCodes,> -f</pre>';
|
||||
break;
|
||||
case 'profiler_enable':
|
||||
$buildList = 'realms,realmMenu';
|
||||
$fn = function($x) use (&$msg) {
|
||||
if (!$x)
|
||||
return true;
|
||||
|
||||
return Profiler::queueStart($msg);
|
||||
};
|
||||
break;
|
||||
case 'acc_auth_mode':
|
||||
$fn = function($x) use (&$msg) {
|
||||
if ($x == 1 && !extension_loaded('gmp'))
|
||||
{
|
||||
$msg .= 'PHP extension GMP is required to use TrinityCore as auth source, but is not currently enabled.<br />';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
break;
|
||||
default: // nothing to do, everything is fine
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($buildList)
|
||||
{
|
||||
// we need to use exec as build() can only be run from CLI
|
||||
exec('php aowow --build='.$buildList, $out);
|
||||
foreach ($out as $o)
|
||||
if (strstr($o, 'ERR'))
|
||||
$msg .= explode('0m]', $o)[1]."<br />\n";
|
||||
}
|
||||
|
||||
return $fn ? $fn($val) : true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -37,10 +39,7 @@ class AjaxHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
$h = $this->handler;
|
||||
$out = $this->$h();
|
||||
if ($out === null)
|
||||
$out = '';
|
||||
$out = $this->{$this->handler}() ?? '';
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -68,4 +67,5 @@ class AjaxHandler
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,14 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('invalid access');
|
||||
die('illegal access');
|
||||
|
||||
class AjaxArenaTeam extends AjaxHandler
|
||||
{
|
||||
protected $validParams = ['resync', 'status'];
|
||||
protected $_get = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
|
||||
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdList' ],
|
||||
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkEmptySet'],
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -74,8 +76,7 @@ class AjaxArenaTeam extends AjaxHandler
|
||||
*/
|
||||
protected function handleStatus() : string
|
||||
{
|
||||
$response = Profiler::resyncStatus(Type::ARENA_TEAM, $this->_get['id']);
|
||||
return Util::toJSON($response);
|
||||
return Profiler::resyncStatus(Type::ARENA_TEAM, $this->_get['id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -11,23 +13,23 @@ class AjaxComment extends AjaxHandler
|
||||
const REPLY_LENGTH_MAX = 600;
|
||||
|
||||
protected $_post = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned'],
|
||||
'body' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
|
||||
'commentbody' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext' ],
|
||||
'response' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'reason' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'commentId' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'replyId' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'sticky' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
// 'username' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdListUnsigned'],
|
||||
'body' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'commentbody' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'response' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'reason' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'remove' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'commentId' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'replyId' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'sticky' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
// 'username' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine' ]
|
||||
);
|
||||
|
||||
protected $_get = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
|
||||
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
|
||||
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt'],
|
||||
'rating' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt'],
|
||||
'type' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt'],
|
||||
'typeid' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt'],
|
||||
'rating' => ['filter' => FILTER_SANITIZE_NUMBER_INT ]
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -170,14 +172,14 @@ class AjaxComment extends AjaxHandler
|
||||
|
||||
protected function handleCommentDelete() : void
|
||||
{
|
||||
if (!$this->_post['id'] || !User::$id)
|
||||
if (!$this->_post['id'] || !User::isLoggedIn())
|
||||
{
|
||||
trigger_error('AjaxComment::handleCommentDelete - commentId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// in theory, there is a username passed alongside... lets just use the current user (see user.js)
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d, deleteUserId = ?d, deleteDate = UNIX_TIMESTAMP() WHERE id IN (?a){ AND userId = ?d}',
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET `flags` = `flags` | ?d, `deleteUserId` = ?d, `deleteDate` = UNIX_TIMESTAMP() WHERE `id` IN (?a){ AND `userId` = ?d}',
|
||||
CC_FLAG_DELETED,
|
||||
User::$id,
|
||||
$this->_post['id'],
|
||||
@@ -187,13 +189,14 @@ class AjaxComment extends AjaxHandler
|
||||
// deflag hasComment
|
||||
if ($ok)
|
||||
{
|
||||
$coInfo = DB::Aowow()->selectRow('SELECT IF(BIT_OR(~b.flags) & ?d, 1, 0) as hasMore, b.type, b.typeId FROM ?_comments a JOIN ?_comments b ON a.type = b.type AND a.typeId = b.typeId WHERE a.id = ?d',
|
||||
$coInfo = DB::Aowow()->select('SELECT IF(BIT_OR(~b.`flags`) & ?d, 1, 0) AS hasMore, b.`type`, b.`typeId` FROM ?_comments a JOIN ?_comments b ON a.`type` = b.`type` AND a.`typeId` = b.`typeId` WHERE a.`id` IN (?a) GROUP BY b.`type`, b.`typeId`',
|
||||
CC_FLAG_DELETED,
|
||||
$this->_post['id']
|
||||
);
|
||||
|
||||
if (!$coInfo['hasMore'] && ($tbl = Type::getClassAttrib($coInfo['type'], 'dataTable')))
|
||||
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags & ~?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']);
|
||||
foreach ($coInfo as $co)
|
||||
if (!$co['hasMore'] && ($tbl = Type::getClassAttrib($co['type'], 'dataTable')))
|
||||
DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` & ~?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_COMMENT, $co['typeId']);
|
||||
}
|
||||
else
|
||||
trigger_error('AjaxComment::handleCommentDelete - user #'.User::$id.' could not flag comment #'.$this->_post['id'].' as deleted', E_USER_ERROR);
|
||||
@@ -201,14 +204,14 @@ class AjaxComment extends AjaxHandler
|
||||
|
||||
protected function handleCommentUndelete() : void
|
||||
{
|
||||
if (!$this->_post['id'] || !User::$id)
|
||||
if (!$this->_post['id'] || !User::isLoggedIn())
|
||||
{
|
||||
trigger_error('AjaxComment::handleCommentUndelete - commentId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// in theory, there is a username passed alongside... lets just use the current user (see user.js)
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id IN (?a){ AND userId = deleteUserId AND deleteUserId = ?d}',
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET `flags` = `flags` & ~?d WHERE `id` IN (?a){ AND `userId` = `deleteUserId` AND `deleteUserId` = ?d}',
|
||||
CC_FLAG_DELETED,
|
||||
$this->_post['id'],
|
||||
User::isInGroup(U_GROUP_MODERATOR) ? DBSIMPLE_SKIP : User::$id
|
||||
@@ -217,9 +220,10 @@ class AjaxComment extends AjaxHandler
|
||||
// reflag hasComment
|
||||
if ($ok)
|
||||
{
|
||||
$coInfo = DB::Aowow()->selectRow('SELECT type, typeId FROM ?_comments WHERE id = ?d', $this->_post['id']);
|
||||
if ($tbl = Type::getClassAttrib($coInfo['type'], 'dataTable'))
|
||||
DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_COMMENT, $coInfo['typeId']);
|
||||
$coInfo = DB::Aowow()->select('SELECT `type`, `typeId` FROM ?_comments WHERE `id` IN (?a) GROUP BY `type`, `typeId`', $this->_post['id']);
|
||||
foreach ($coInfo as $co)
|
||||
if ($tbl = Type::getClassAttrib($co['type'], 'dataTable'))
|
||||
DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_COMMENT, $co['typeId']);
|
||||
}
|
||||
else
|
||||
trigger_error('AjaxComment::handleCommentUndelete - user #'.User::$id.' could not unflag comment #'.$this->_post['id'].' as deleted', E_USER_ERROR);
|
||||
@@ -238,7 +242,7 @@ class AjaxComment extends AjaxHandler
|
||||
|
||||
protected function handleCommentVote() : string
|
||||
{
|
||||
if (!User::$id || !$this->_get['id'] || !$this->_get['rating'])
|
||||
if (!User::isLoggedIn() || !$this->_get['id'] || !$this->_get['rating'])
|
||||
return Util::toJSON(['error' => 1, 'message' => Lang::main('genericError')]);
|
||||
|
||||
$target = DB::Aowow()->selectRow('SELECT c.`userId` AS owner, ur.`value` FROM ?_comments c LEFT JOIN ?_user_ratings ur ON ur.`type` = ?d AND ur.`entry` = c.id AND ur.`userId` = ?d WHERE c.id = ?d', RATING_COMMENT, User::$id, $this->_get['id']);
|
||||
@@ -246,7 +250,7 @@ class AjaxComment extends AjaxHandler
|
||||
if ($this->_get['rating'] < 0)
|
||||
$val *= -1;
|
||||
|
||||
if (User::getCurDailyVotes() <= 0)
|
||||
if (User::getCurrentDailyVotes() <= 0)
|
||||
return Util::toJSON(['error' => 1, 'message' => Lang::main('tooManyVotes')]);
|
||||
else if (!$target || $val != $this->_get['rating'])
|
||||
return Util::toJSON(['error' => 1, 'message' => Lang::main('genericError')]);
|
||||
@@ -300,19 +304,24 @@ class AjaxComment extends AjaxHandler
|
||||
if (User::isInGroup(U_GROUP_MODERATOR)) // directly mark as outdated
|
||||
{
|
||||
if (!$this->_post['remove'])
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | 0x4 WHERE id = ?d', $this->_post['id'][0]);
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d WHERE id = ?d', CC_FLAG_OUTDATED, $this->_post['id'][0]);
|
||||
else
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~0x4 WHERE id = ?d', $this->_post['id'][0]);
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags & ~?d WHERE id = ?d', CC_FLAG_OUTDATED, $this->_post['id'][0]);
|
||||
}
|
||||
else if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND `userId` = ?d', 1, 17, $this->_post['id'][0], User::$id))
|
||||
return Lang::main('alreadyReport');
|
||||
else if (User::$id && !$this->_post['reason'] || mb_strlen($this->_post['reason']) < self::REPLY_LENGTH_MIN)
|
||||
return Lang::main('textTooShort');
|
||||
else if (User::$id) // only report as outdated
|
||||
$ok = Util::createReport(1, 17, $this->_post['id'][0], '[Outdated Comment] '.$this->_post['reason']);
|
||||
else // try to report as outdated
|
||||
{
|
||||
$report = new Report(Report::MODE_COMMENT, Report::CO_OUT_OF_DATE, $this->_post['id'][0]);
|
||||
if ($report->create($this->_post['reason']))
|
||||
$ok = true; // the script expects the actual characters 'ok' not some json string like "ok"
|
||||
else
|
||||
return Lang::main('intError');
|
||||
|
||||
if ($ok) // this one is very special; as in: completely retarded
|
||||
return 'ok'; // the script expects the actual characters 'ok' not some string like "ok"
|
||||
if (count($report->getSimilar()) >= 5) // 5 or more reports on the same comment: trigger flag
|
||||
$ok = DB::Aowow()->query('UPDATE ?_comments SET flags = flags | ?d WHERE id = ?d', CC_FLAG_OUTDATED, $this->_post['id'][0]);
|
||||
}
|
||||
|
||||
if ($ok)
|
||||
return 'ok';
|
||||
else
|
||||
trigger_error('AjaxComment::handleCommentOutOfDate - failed to update comment in db', E_USER_ERROR);
|
||||
|
||||
@@ -384,7 +393,7 @@ class AjaxComment extends AjaxHandler
|
||||
|
||||
protected function handleReplyDelete() : void
|
||||
{
|
||||
if (!User::$id || !$this->_post['id'])
|
||||
if (!User::isLoggedIn() || !$this->_post['id'])
|
||||
{
|
||||
trigger_error('AjaxComment::handleReplyDelete - commentId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
@@ -398,13 +407,14 @@ class AjaxComment extends AjaxHandler
|
||||
|
||||
protected function handleReplyFlag() : void
|
||||
{
|
||||
if (!User::$id || !$this->_post['id'])
|
||||
if (!User::isLoggedIn() || !$this->_post['id'])
|
||||
{
|
||||
trigger_error('AjaxComment::handleReplyFlag - commentId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
Util::createReport(1, 19, $this->_post['id'][0], '[General Reply Report]');
|
||||
$report = new Report(Report::MODE_COMMENT, Report::CO_INAPPROPRIATE, $this->_post['id'][0]);
|
||||
$report->create('Report Reply Button Click');
|
||||
}
|
||||
|
||||
protected function handleReplyUpvote() : void
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxContactus extends AjaxHandler
|
||||
{
|
||||
protected $_post = array(
|
||||
'mode' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'reason' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'ua' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'appname' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'page' => ['filter' => FILTER_SANITIZE_URL],
|
||||
'desc' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'id' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'relatedurl' => ['filter' => FILTER_SANITIZE_URL],
|
||||
'email' => ['filter' => FILTER_SANITIZE_EMAIL]
|
||||
'mode' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'reason' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'ua' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'appname' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'page' => ['filter' => FILTER_SANITIZE_URL ],
|
||||
'desc' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob'],
|
||||
'id' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'relatedurl' => ['filter' => FILTER_SANITIZE_URL ],
|
||||
'email' => ['filter' => FILTER_SANITIZE_EMAIL ]
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -35,57 +37,12 @@ class AjaxContactus extends AjaxHandler
|
||||
*/
|
||||
protected function handleContactUs() : string
|
||||
{
|
||||
$mode = $this->_post['mode'];
|
||||
$rsn = $this->_post['reason'];
|
||||
$ua = $this->_post['ua'];
|
||||
$app = $this->_post['appname'];
|
||||
$url = $this->_post['page'];
|
||||
$desc = $this->_post['desc'];
|
||||
$subj = $this->_post['id'];
|
||||
|
||||
$contexts = array(
|
||||
[1, 2, 3, 4, 5, 6, 7, 8],
|
||||
[15, 16, 17, 18, 19, 20],
|
||||
[30, 31, 32, 33, 34, 35, 36, 37],
|
||||
[45, 46, 47, 48],
|
||||
[60, 61],
|
||||
[45, 46, 47, 48],
|
||||
[45, 46, 48]
|
||||
);
|
||||
|
||||
if ($mode === null || $rsn === null || $ua === null || $app === null || $url === null)
|
||||
{
|
||||
trigger_error('AjaxContactus::handleContactUs - malformed contact request received', E_USER_ERROR);
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
if (!isset($contexts[$mode]) || !in_array($rsn, $contexts[$mode]))
|
||||
{
|
||||
trigger_error('AjaxContactus::handleContactUs - report has invalid context (mode:'.$mode.' / reason:'.$rsn.')', E_USER_ERROR);
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
if (!$desc)
|
||||
return 3;
|
||||
|
||||
if (mb_strlen($desc) > 500)
|
||||
return 2;
|
||||
|
||||
if (!User::$id && !User::$ip)
|
||||
{
|
||||
trigger_error('AjaxContactus::handleContactUs - could not determine IP for anonymous user', E_USER_ERROR);
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
// check already reported
|
||||
$field = User::$id ? 'userId' : 'ip';
|
||||
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $mode, $rsn, $subj, $field, User::$id ?: User::$ip))
|
||||
return 7;
|
||||
|
||||
if (Util::createReport($mode, $rsn, $subj, $desc, $ua, $app, $url, $this->_post['relatedurl'], $this->_post['email']))
|
||||
$report = new Report($this->_post['mode'], $this->_post['reason'], $this->_post['id']);
|
||||
if ($report->create($this->_post['desc'], $this->_post['ua'], $this->_post['appname'], $this->_post['page'], $this->_post['relatedurl'], $this->_post['email']))
|
||||
return 0;
|
||||
|
||||
trigger_error('AjaxContactus::handleContactUs - write to db failed', E_USER_ERROR);
|
||||
else if (($e = $report->getError()) > 0)
|
||||
return $e;
|
||||
else
|
||||
return Lang::main('intError');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -8,11 +10,11 @@ class AjaxCookie extends AjaxHandler
|
||||
public function __construct(array $params)
|
||||
{
|
||||
// note that parent::__construct has to come after this
|
||||
if (!$params || !User::$id)
|
||||
if (!$params || !User::isLoggedIn())
|
||||
return;
|
||||
|
||||
$this->_get = array(
|
||||
$params[0] => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
$params[0] => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
);
|
||||
|
||||
// NOW we know, what to expect and sanitize
|
||||
@@ -28,7 +30,7 @@ class AjaxCookie extends AjaxHandler
|
||||
*/
|
||||
protected function handleCookie() : string
|
||||
{
|
||||
if (User::$id && $this->params && $this->_get[$this->params[0]])
|
||||
if (User::isLoggedIn() && $this->params && $this->_get[$this->params[0]])
|
||||
{
|
||||
if (DB::Aowow()->query('REPLACE INTO ?_account_cookies VALUES (?d, ?, ?)', User::$id, $this->params[0], $this->_get[$this->params[0]]))
|
||||
return '0';
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxData extends AjaxHandler
|
||||
{
|
||||
protected $_get = array(
|
||||
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale'],
|
||||
't' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\Locale::tryFrom' ],
|
||||
't' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'catg' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'skill' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxData::checkSkill' ],
|
||||
'skill' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxData::checkSkill' ],
|
||||
'class' => ['filter' => FILTER_SANITIZE_NUMBER_INT ],
|
||||
'callback' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxData::checkCallback' ]
|
||||
'callback' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxData::checkCallback' ]
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
if (is_numeric($this->_get['locale']))
|
||||
User::useLocale($this->_get['locale']);
|
||||
if ($this->_get['locale']?->validate())
|
||||
Lang::load($this->_get['locale']);
|
||||
|
||||
// always this one
|
||||
$this->handler = 'handleData';
|
||||
@@ -53,20 +55,19 @@ class AjaxData extends AjaxHandler
|
||||
$result .= $this->loadProfilerData($set);
|
||||
break;
|
||||
case 'companions':
|
||||
$result .= $this->loadProfilerData($set, '778');
|
||||
$result .= $this->loadProfilerData($set, SKILL_COMPANIONS);
|
||||
break;
|
||||
case 'mounts':
|
||||
$result .= $this->loadProfilerData($set, '777');
|
||||
$result .= $this->loadProfilerData($set, SKILL_MOUNTS);
|
||||
break;
|
||||
case 'quests':
|
||||
// &partial: im not doing this right
|
||||
// it expects a full quest dump on first lookup but will query subCats again if clicked..?
|
||||
// for now omiting the detail clicks with empty results and just set catg update
|
||||
$catg = isset($this->_get['catg']) ? $this->_get['catg'] : 'null';
|
||||
if ($catg == 'null')
|
||||
$result .= $this->loadProfilerData($set);
|
||||
else if ($this->_get['callback'])
|
||||
$result .= "\n\$WowheadProfiler.loadOnDemand('quests', ".$catg.");\n";
|
||||
Util::loadStaticFile('p-'.$set, $result, false);
|
||||
else
|
||||
Util::loadStaticFile('p-'.$set.'-'.$catg, $result, true);
|
||||
|
||||
$result .= "\n\$WowheadProfiler.loadOnDemand('".$set."', ".$catg.");\n";
|
||||
|
||||
break;
|
||||
case 'recipes':
|
||||
@@ -80,14 +81,13 @@ class AjaxData extends AjaxHandler
|
||||
$result .= "\n\$WowheadProfiler.loadOnDemand('recipes', null);\n";
|
||||
|
||||
break;
|
||||
// locale independant
|
||||
// locale independent
|
||||
case 'quick-excludes':
|
||||
case 'zones':
|
||||
case 'weight-presets':
|
||||
case 'item-scaling':
|
||||
case 'realms':
|
||||
case 'statistics':
|
||||
if (!Util::loadStaticFile($set, $result) && CFG_DEBUG)
|
||||
if (!Util::loadStaticFile($set, $result) && Cfg::get('DEBUG'))
|
||||
$result .= "alert('could not fetch static data: ".$set."');";
|
||||
|
||||
$result .= "\n\n";
|
||||
@@ -103,8 +103,9 @@ class AjaxData extends AjaxHandler
|
||||
case 'enchants':
|
||||
case 'itemsets':
|
||||
case 'pets':
|
||||
if (!Util::loadStaticFile($set, $result, true) && CFG_DEBUG)
|
||||
$result .= "alert('could not fetch static data: ".$set." for locale: ".User::$localeString."');";
|
||||
case 'zones':
|
||||
if (!Util::loadStaticFile($set, $result, true) && Cfg::get('DEBUG'))
|
||||
$result .= "alert('could not fetch static data: ".$set." for locale: ".Lang::getLocale()->json()."');";
|
||||
|
||||
$result .= "\n\n";
|
||||
break;
|
||||
@@ -119,7 +120,7 @@ class AjaxData extends AjaxHandler
|
||||
|
||||
protected static function checkSkill(string $val) : array
|
||||
{
|
||||
return array_intersect([171, 164, 333, 202, 182, 773, 755, 165, 186, 393, 197, 185, 129, 356], explode(',', $val));
|
||||
return array_intersect(array_merge(SKILLS_TRADE_PRIMARY, [SKILL_FIRST_AID, SKILL_COOKING, SKILL_FISHING]), explode(',', $val));
|
||||
}
|
||||
|
||||
protected static function checkCallback(string $val) : bool
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxEdit extends AjaxHandler
|
||||
{
|
||||
protected $_get = array(
|
||||
'qqfile' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'guide' => ['filter' => FILTER_SANITIZE_NUMBER_INT]
|
||||
'qqfile' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'guide' => ['filter' => FILTER_SANITIZE_NUMBER_INT ]
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -32,21 +34,21 @@ class AjaxEdit extends AjaxHandler
|
||||
*/
|
||||
protected function handleUpload() : string
|
||||
{
|
||||
if (!User::$id || $this->_get['guide'] != 1)
|
||||
if (!User::canWriteGuide() || $this->_get['guide'] != 1)
|
||||
return Util::toJSON(['success' => false, 'error' => '']);
|
||||
|
||||
require_once('includes/libs/qqFileUploader.class.php');
|
||||
|
||||
$targetPath = 'static/uploads/guide/images/';
|
||||
$tmpPath = 'static/uploads/temp/';
|
||||
$tmpFile = User::$displayName.'-'.Type::GUIDE.'-0-'.Util::createHash(16);
|
||||
$tmpFile = User::$username.'-'.Type::GUIDE.'-0-'.Util::createHash(16);
|
||||
|
||||
$uploader = new qqFileUploader(['jpg', 'jpeg', 'png'], 10 * 1024 * 1024);
|
||||
$uploader = new \qqFileUploader(['jpg', 'jpeg', 'png'], 10 * 1024 * 1024);
|
||||
$result = $uploader->handleUpload($tmpPath, $tmpFile, true);
|
||||
|
||||
if (isset($result['success']))
|
||||
{
|
||||
$finfo = new finfo(FILEINFO_MIME);
|
||||
$finfo = new \finfo(FILEINFO_MIME);
|
||||
$mime = $finfo->file($tmpPath.$result['newFilename']);
|
||||
if (preg_match('/^image\/(png|jpe?g)/i', $mime, $m))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -17,6 +19,8 @@ class AjaxFilter extends AjaxHandler
|
||||
if (!$params)
|
||||
return;
|
||||
|
||||
parent::__construct($params);
|
||||
|
||||
$p = explode('=', $params[0]);
|
||||
|
||||
$this->page = $p[0];
|
||||
@@ -30,55 +34,15 @@ class AjaxFilter extends AjaxHandler
|
||||
|
||||
$opts = ['parentCats' => $this->cat];
|
||||
|
||||
switch ($p[0])
|
||||
// so usually the page call is just the DBTypes file string with a plural 's' .. but then there are currencies
|
||||
$fileStr = match ($this->page)
|
||||
{
|
||||
case 'achievements':
|
||||
$this->filter = (new AchievementListFilter(true, $opts));
|
||||
break;
|
||||
case 'areatriggers':
|
||||
$this->filter = (new AreaTriggerListFilter(true, $opts));
|
||||
break;
|
||||
case 'enchantments':
|
||||
$this->filter = (new EnchantmentListFilter(true, $opts));
|
||||
break;
|
||||
case 'icons':
|
||||
$this->filter = (new IconListFilter(true, $opts));
|
||||
break;
|
||||
case 'items':
|
||||
$this->filter = (new ItemListFilter(true, $opts));
|
||||
break;
|
||||
case 'itemsets':
|
||||
$this->filter = (new ItemsetListFilter(true, $opts));
|
||||
break;
|
||||
case 'npcs':
|
||||
$this->filter = (new CreatureListFilter(true, $opts));
|
||||
break;
|
||||
case 'objects':
|
||||
$this->filter = (new GameObjectListFilter(true, $opts));
|
||||
break;
|
||||
case 'quests':
|
||||
$this->filter = (new QuestListFilter(true, $opts));
|
||||
break;
|
||||
case 'sounds':
|
||||
$this->filter = (new SoundListFilter(true, $opts));
|
||||
break;
|
||||
case 'spells':
|
||||
$this->filter = (new SpellListFilter(true, $opts));
|
||||
break;
|
||||
case 'profiles':
|
||||
$this->filter = (new ProfileListFilter(true, $opts));
|
||||
break;
|
||||
case 'guilds':
|
||||
$this->filter = (new GuildListFilter(true, $opts));
|
||||
break;
|
||||
case 'arena-teams':
|
||||
$this->filter = (new ArenaTeamListFilter(true, $opts));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
'currencies' => 'currency',
|
||||
default => substr($this->page, 0, -1)
|
||||
};
|
||||
|
||||
parent::__construct($params);
|
||||
// yes, the whole _POST! .. should the input fields be exposed and static so they can be evaluated via BaseResponse::initRequestData() ?
|
||||
$this->filter = Type::newFilter($fileStr, $_POST, $opts);
|
||||
|
||||
// always this one
|
||||
$this->handler = 'handleFilter';
|
||||
@@ -88,20 +52,16 @@ class AjaxFilter extends AjaxHandler
|
||||
{
|
||||
$url = '?'.$this->page;
|
||||
|
||||
$this->filter->mergeCat($this->cat);
|
||||
$this->filter?->mergeCat($this->cat);
|
||||
|
||||
if ($this->cat)
|
||||
$url .= '='.implode('.', $this->cat);
|
||||
|
||||
$fi = [];
|
||||
if ($x = $this->filter->getFilterString())
|
||||
if ($x = $this->filter?->buildGETParam())
|
||||
$url .= '&filter='.$x;
|
||||
|
||||
if ($this->filter->error)
|
||||
$_SESSION['fiError'] = get_class($this->filter);
|
||||
|
||||
if ($fi)
|
||||
$url .= '&filter='.implode(';', $fi);
|
||||
if ($this->filter?->error)
|
||||
$_SESSION['error']['fi'] = get_class($this->filter);
|
||||
|
||||
// do get request
|
||||
return $url;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxGetdescription extends AjaxHandler
|
||||
{
|
||||
protected $_post = array(
|
||||
'description' => [FILTER_CALLBACK, ['options' => 'AjaxHandler::checkFulltext']]
|
||||
'description' => [FILTER_CALLBACK, ['options' => 'Aowow\AjaxHandler::checkTextBlob']]
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -23,10 +25,10 @@ class AjaxGetdescription extends AjaxHandler
|
||||
{
|
||||
$this->contentType = MIME_TYPE_TEXT;
|
||||
|
||||
if (!User::$id)
|
||||
if (!User::canWriteGuide())
|
||||
return '';
|
||||
|
||||
$desc = (new Markup($this->_post['description']))->stripTags();
|
||||
$desc = Markup::stripTags($this->_post['description']);
|
||||
|
||||
return Lang::trimTextClean($desc, 120);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxGotocomment extends AjaxHandler
|
||||
{
|
||||
protected $_get = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkInt']
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkInt']
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -26,10 +28,10 @@ class AjaxGotocomment extends AjaxHandler
|
||||
if (!$this->_get['id'])
|
||||
return '.'; // go home
|
||||
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT IFNULL(c2.id, c1.id) AS id, IFNULL(c2.type, c1.type) AS type, IFNULL(c2.typeId, c1.typeId) AS typeId FROM ?_comments c1 LEFT JOIN ?_comments c2 ON c1.replyTo = c2.id WHERE c1.id = ?d', $this->_get['id']))
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT IFNULL(c2.`id`, c1.`id`) AS "id", IFNULL(c2.`type`, c1.`type`) AS "type", IFNULL(c2.`typeId`, c1.`typeId`) AS "typeId" FROM ?_comments c1 LEFT JOIN ?_comments c2 ON c1.`replyTo` = c2.`id` WHERE c1.`id` = ?d', $this->_get['id']))
|
||||
return '?'.Type::getFileString(intVal($_['type'])).'='.$_['typeId'].'#comments:id='.$_['id'].($_['id'] != $this->_get['id'] ? ':reply='.$this->_get['id'] : null);
|
||||
else
|
||||
trigger_error('AjaxGotocomment::handleGoToComment - could not find comment #'.$this->get['id'], E_USER_ERROR);
|
||||
trigger_error('AjaxGotocomment::handleGoToComment - could not find comment #'.$this->_get['id'], E_USER_ERROR);
|
||||
|
||||
return '.';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('invalid access');
|
||||
die('illegal access');
|
||||
|
||||
class AjaxGuild extends AjaxHandler
|
||||
{
|
||||
protected $validParams = ['resync', 'status'];
|
||||
protected $_get = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
|
||||
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdList' ],
|
||||
'profile' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkEmptySet'],
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -74,8 +76,7 @@ class AjaxGuild extends AjaxHandler
|
||||
*/
|
||||
protected function handleStatus() : string
|
||||
{
|
||||
$response = Profiler::resyncStatus(Type::GUILD, $this->_get['id']);
|
||||
return Util::toJSON($response);
|
||||
return Profiler::resyncStatus(Type::GUILD, $this->_get['id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class AjaxLocale extends AjaxHandler
|
||||
{
|
||||
protected $_get = array(
|
||||
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkLocale']
|
||||
'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\Locale::tryFrom']
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -23,8 +25,11 @@ class AjaxLocale extends AjaxHandler
|
||||
*/
|
||||
protected function handleLocale() : string
|
||||
{
|
||||
User::setLocale($this->_get['locale']);
|
||||
User::save();
|
||||
if ($this->_get['locale']?->validate())
|
||||
{
|
||||
User::$preferedLoc = $this->_get['locale'];
|
||||
User::save(true);
|
||||
}
|
||||
|
||||
return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '.';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -9,16 +11,16 @@ class AjaxProfile extends AjaxHandler
|
||||
|
||||
protected $validParams = ['link', 'unlink', 'pin', 'unpin', 'public', 'private', 'avatar', 'resync', 'status', 'save', 'delete', 'purge', 'summary', 'load'];
|
||||
protected $_get = array(
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdList' ],
|
||||
'items' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxProfile::checkItemList'],
|
||||
'size' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'guild' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
|
||||
'arena-team' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkEmptySet'],
|
||||
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxProfile::checkUser' ]
|
||||
'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdList' ],
|
||||
'items' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkItemList'],
|
||||
'size' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'guild' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkEmptySet'],
|
||||
'arena-team' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkEmptySet'],
|
||||
'user' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkUser' ]
|
||||
);
|
||||
|
||||
protected $_post = array(
|
||||
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext'],
|
||||
'name' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'],
|
||||
'level' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'class' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'race' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
@@ -28,17 +30,17 @@ class AjaxProfile extends AjaxHandler
|
||||
'talenttree2' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'talenttree3' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'activespec' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'talentbuild1' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'glyphs1' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'talentbuild2' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'glyphs2' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'icon' => ['filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'description' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkFulltext'],
|
||||
'talentbuild1' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkTalentString'],
|
||||
'glyphs1' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkGlyphString' ],
|
||||
'talentbuild2' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkTalentString'],
|
||||
'glyphs2' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxProfile::checkGlyphString' ],
|
||||
'icon' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine' ],
|
||||
'description' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextBlob' ],
|
||||
'source' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'copy' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'public' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'gearscore' => ['filter' => FILTER_SANITIZE_NUMBER_INT],
|
||||
'inv' => ['filter' => FILTER_CALLBACK, 'options' => 'AjaxHandler::checkIdListUnsigned', 'flags' => FILTER_REQUIRE_ARRAY],
|
||||
'inv' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdListUnsigned', 'flags' => FILTER_REQUIRE_ARRAY],
|
||||
);
|
||||
|
||||
public function __construct(array $params)
|
||||
@@ -48,7 +50,7 @@ class AjaxProfile extends AjaxHandler
|
||||
if (!$this->params)
|
||||
return;
|
||||
|
||||
if (!CFG_PROFILER_ENABLE)
|
||||
if (!Cfg::get('PROFILER_ENABLE'))
|
||||
return;
|
||||
|
||||
switch ($this->params[0])
|
||||
@@ -101,7 +103,7 @@ class AjaxProfile extends AjaxHandler
|
||||
*/
|
||||
protected function handleLink() : void // links char with account
|
||||
{
|
||||
if (!User::$id || empty($this->_get['id']))
|
||||
if (!User::isLoggedIn() || empty($this->_get['id']))
|
||||
{
|
||||
trigger_error('AjaxProfile::handleLink - profileId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
@@ -110,7 +112,7 @@ class AjaxProfile extends AjaxHandler
|
||||
$uid = User::$id;
|
||||
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
|
||||
{
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])))
|
||||
{
|
||||
trigger_error('AjaxProfile::handleLink - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
|
||||
return;
|
||||
@@ -118,12 +120,12 @@ class AjaxProfile extends AjaxHandler
|
||||
}
|
||||
|
||||
if ($this->undo)
|
||||
DB::Aowow()->query('DELETE FROM ?_account_profiles WHERE accountId = ?d AND profileId IN (?a)', $uid, $this->_get['id']);
|
||||
DB::Aowow()->query('DELETE FROM ?_account_profiles WHERE `accountId` = ?d AND `profileId` IN (?a)', $uid, $this->_get['id']);
|
||||
else
|
||||
{
|
||||
foreach ($this->_get['id'] as $prId) // only link characters, not custom profiles
|
||||
{
|
||||
if ($prId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE id = ?d AND realm IS NOT NULL', $prId))
|
||||
if ($prId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `id` = ?d AND `realm` IS NOT NULL', $prId))
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_account_profiles VALUES (?d, ?d, 0)', $uid, $prId);
|
||||
else
|
||||
{
|
||||
@@ -141,7 +143,7 @@ class AjaxProfile extends AjaxHandler
|
||||
*/
|
||||
protected function handlePin() : void // (un)favorite
|
||||
{
|
||||
if (!User::$id || empty($this->_get['id'][0]))
|
||||
if (!User::isLoggedIn() || empty($this->_get['id'][0]))
|
||||
{
|
||||
trigger_error('AjaxProfile::handlePin - profileId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
@@ -150,7 +152,7 @@ class AjaxProfile extends AjaxHandler
|
||||
$uid = User::$id;
|
||||
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
|
||||
{
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])))
|
||||
{
|
||||
trigger_error('AjaxProfile::handlePin - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
|
||||
return;
|
||||
@@ -158,10 +160,10 @@ class AjaxProfile extends AjaxHandler
|
||||
}
|
||||
|
||||
// since only one character can be pinned at a time we can reset everything
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags & ?d WHERE accountId = ?d', ~PROFILER_CU_PINNED, $uid);
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET `extraFlags` = `extraFlags` & ?d WHERE `accountId` = ?d', ~PROFILER_CU_PINNED, $uid);
|
||||
// and set a single char if necessary
|
||||
if (!$this->undo)
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags | ?d WHERE profileId = ?d AND accountId = ?d', PROFILER_CU_PINNED, $this->_get['id'][0], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET `extraFlags` = `extraFlags` | ?d WHERE `profileId` = ?d AND `accountId` = ?d', PROFILER_CU_PINNED, $this->_get['id'][0], $uid);
|
||||
}
|
||||
|
||||
/* params
|
||||
@@ -171,7 +173,7 @@ class AjaxProfile extends AjaxHandler
|
||||
*/
|
||||
protected function handlePrivacy() : void // public visibility
|
||||
{
|
||||
if (!User::$id || empty($this->_get['id'][0]))
|
||||
if (!User::isLoggedIn() || empty($this->_get['id'][0]))
|
||||
{
|
||||
trigger_error('AjaxProfile::handlePrivacy - profileId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
@@ -180,7 +182,7 @@ class AjaxProfile extends AjaxHandler
|
||||
$uid = User::$id;
|
||||
if ($this->_get['user'] && User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
|
||||
{
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE user = ?', $this->_get['user'])))
|
||||
if (!($uid = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])))
|
||||
{
|
||||
trigger_error('AjaxProfile::handlePrivacy - user "'.$this->_get['user'].'" does not exist', E_USER_ERROR);
|
||||
return;
|
||||
@@ -189,13 +191,13 @@ class AjaxProfile extends AjaxHandler
|
||||
|
||||
if ($this->undo)
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags & ?d WHERE profileId IN (?a) AND accountId = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags & ?d WHERE id IN (?a) AND user = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET `extraFlags` = `extraFlags` & ?d WHERE `profileId` IN (?a) AND `accountId` = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` & ?d WHERE `id` IN (?a) AND `user` = ?d', ~PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET extraFlags = extraFlags | ?d WHERE profileId IN (?a) AND accountId = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags | ?d WHERE id IN (?a) AND user = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_account_profiles SET `extraFlags` = `extraFlags` | ?d WHERE `profileId` IN (?a) AND `accountId` = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` | ?d WHERE `id` IN (?a) AND `user` = ?d', PROFILER_CU_PUBLISHED, $this->_get['id'], $uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,8 +307,7 @@ class AjaxProfile extends AjaxHandler
|
||||
return Util::toJSON([1, [PR_QUEUE_STATUS_ERROR, 0, 0, PR_QUEUE_ERROR_CHAR]]);
|
||||
}
|
||||
|
||||
$response = Profiler::resyncStatus(Type::PROFILE, $ids);
|
||||
return Util::toJSON($response);
|
||||
return Profiler::resyncStatus(Type::PROFILE, $ids);
|
||||
}
|
||||
|
||||
/* params (get))
|
||||
@@ -322,7 +323,7 @@ class AjaxProfile extends AjaxHandler
|
||||
// todo (med): detail check this post-data
|
||||
$cuProfile = array(
|
||||
'user' => User::$id,
|
||||
// 'userName' => User::$displayName,
|
||||
// 'userName' => User::$username,
|
||||
'name' => $this->_post['name'],
|
||||
'level' => $this->_post['level'],
|
||||
'class' => $this->_post['class'],
|
||||
@@ -360,7 +361,7 @@ class AjaxProfile extends AjaxHandler
|
||||
$cuProfile['sourceId'] = $_;
|
||||
}
|
||||
|
||||
if ($cuProfile['sourceId'])
|
||||
if (!empty($cuProfile['sourceId']))
|
||||
$cuProfile['sourceName'] = DB::Aowow()->selectCell('SELECT name FROM ?_profiler_profiles WHERE id = ?d', $cuProfile['sourceId']);
|
||||
|
||||
$charId = -1;
|
||||
@@ -420,7 +421,7 @@ class AjaxProfile extends AjaxHandler
|
||||
$itemData[2] = 0;
|
||||
|
||||
// item sockets are fubar
|
||||
$nSockets = $items->json[$itemData[1]]['nsockets'];
|
||||
$nSockets = $items->json[$itemData[1]]['nsockets'] ?? 0;
|
||||
$nSockets += in_array($slot, [SLOT_WAIST, SLOT_WRISTS, SLOT_HANDS]) ? 1 : 0;
|
||||
for ($i = 5; $i < 9; $i++)
|
||||
if ($itemData[$i] > 0 && (!$items->getEntry($itemData[$i]) || $i >= (5 + $nSockets)))
|
||||
@@ -450,7 +451,7 @@ class AjaxProfile extends AjaxHandler
|
||||
*/
|
||||
protected function handleDelete() : void // kill a profile
|
||||
{
|
||||
if (!User::$id || !$this->_get['id'])
|
||||
if (!User::isLoggedIn() || !$this->_get['id'])
|
||||
{
|
||||
trigger_error('AjaxProfile::handleDelete - profileId empty or user not logged in', E_USER_ERROR);
|
||||
return;
|
||||
@@ -501,13 +502,16 @@ class AjaxProfile extends AjaxHandler
|
||||
if ($rId == $pBase['realm'])
|
||||
break;
|
||||
|
||||
if (!$rData) // realm doesn't exist or access is restricted
|
||||
return '';
|
||||
|
||||
$profile = array(
|
||||
'id' => $pBase['id'],
|
||||
'source' => $pBase['id'],
|
||||
'level' => $pBase['level'],
|
||||
'classs' => $pBase['class'],
|
||||
'race' => $pBase['race'],
|
||||
'faction' => Game::sideByRaceMask(1 << ($pBase['race'] - 1)) - 1,
|
||||
'faction' => ChrRace::tryFrom($pBase['race'])?->getTeam() ?? TEAM_NEUTRAL,
|
||||
'gender' => $pBase['gender'],
|
||||
'skincolor' => $pBase['skincolor'],
|
||||
'hairstyle' => $pBase['hairstyle'],
|
||||
@@ -553,14 +557,14 @@ class AjaxProfile extends AjaxHandler
|
||||
$profile['sourcename'] = $pBase['sourceName'];
|
||||
$profile['description'] = $pBase['description'];
|
||||
$profile['user'] = $pBase['user'];
|
||||
$profile['username'] = DB::Aowow()->selectCell('SELECT displayName FROM ?_account WHERE id = ?d', $pBase['user']);
|
||||
$profile['username'] = DB::Aowow()->selectCell('SELECT `username` FROM ?_account WHERE `id` = ?d', $pBase['user']);
|
||||
}
|
||||
|
||||
// custom profiles inherit this when copied from real char :(
|
||||
if ($pBase['realm'])
|
||||
{
|
||||
$profile['region'] = [$rData['region'], Lang::profiler('regions', $rData['region'])];
|
||||
$profile['battlegroup'] = [Profiler::urlize(CFG_BATTLEGROUP), CFG_BATTLEGROUP];
|
||||
$profile['battlegroup'] = [Profiler::urlize(Cfg::get('BATTLEGROUP')), Cfg::get('BATTLEGROUP')];
|
||||
$profile['realm'] = [Profiler::urlize($rData['name'], true), $rData['name']];
|
||||
}
|
||||
|
||||
@@ -568,12 +572,12 @@ class AjaxProfile extends AjaxHandler
|
||||
if ($_ = DB::Aowow()->selectCol('SELECT accountId FROM ?_account_profiles WHERE profileId = ?d', $pBase['id']))
|
||||
$profile['bookmarks'] = $_;
|
||||
|
||||
// arena teams - [size(2|3|5) => DisplayName]; DisplayName gets urlized to use as link
|
||||
// arena teams - [size(2|3|5) => name]; name gets urlized to use as link
|
||||
if ($at = DB::Aowow()->selectCol('SELECT type AS ARRAY_KEY, name FROM ?_profiler_arena_team at JOIN ?_profiler_arena_team_member atm ON atm.arenaTeamId = at.id WHERE atm.profileId = ?d', $pBase['id']))
|
||||
$profile['arenateams'] = $at;
|
||||
|
||||
// pets if hunter fields: [name:name, family:petFamily, npc:npcId, displayId:modelId, talents:talentString]
|
||||
if ($pets = DB::Aowow()->select('SELECT name, family, npc, displayId, talents FROM ?_profiler_pets WHERE owner = ?d', $pBase['id']))
|
||||
if ($pets = DB::Aowow()->select('SELECT `name`, `family`, `npc`, `displayId`, CONCAT("$\"", `talents`, "\"") AS "talents" FROM ?_profiler_pets WHERE `owner` = ?d', $pBase['id']))
|
||||
$profile['pets'] = $pets;
|
||||
|
||||
// source for custom profiles; profileId => [name, ownerId, iconString(optional)]
|
||||
@@ -598,60 +602,42 @@ class AjaxProfile extends AjaxHandler
|
||||
*/
|
||||
|
||||
|
||||
$completion = DB::Aowow()->select('SELECT type AS ARRAY_KEY, typeId AS ARRAY_KEY2, cur, max FROM ?_profiler_completion WHERE id = ?d', $pBase['id']);
|
||||
foreach ($completion as $type => $data)
|
||||
// questId => [cat1, cat2]
|
||||
$profile['quests'] = [];
|
||||
if ($quests = DB::Aowow()->selectCol('SELECT `questId` FROM ?_profiler_completion_quests WHERE `id` = ?d', $pBase['id']))
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case Type::FACTION: // factionId => amount
|
||||
$profile['reputation'] = array_combine(array_keys($data), array_column($data, 'cur'));
|
||||
break;
|
||||
case Type::TITLE:
|
||||
foreach ($data as &$d)
|
||||
$d = 1;
|
||||
|
||||
$profile['titles'] = $data;
|
||||
break;
|
||||
case Type::QUEST:
|
||||
foreach ($data as &$d)
|
||||
$d = 1;
|
||||
|
||||
$profile['quests'] = $data;
|
||||
break;
|
||||
case Type::SPELL:
|
||||
foreach ($data as &$d)
|
||||
$d = 1;
|
||||
|
||||
$profile['spells'] = $data;
|
||||
break;
|
||||
case Type::ACHIEVEMENT:
|
||||
$achievements = array_filter($data, function ($x) { return $x['max'] === null; });
|
||||
$statistics = array_filter($data, function ($x) { return $x['max'] !== null; });
|
||||
|
||||
// achievements
|
||||
$profile['achievements'] = array_combine(array_keys($achievements), array_column($achievements, 'cur'));
|
||||
$profile['achievementpoints'] = DB::Aowow()->selectCell('SELECT SUM(points) FROM ?_achievement WHERE id IN (?a)', array_keys($achievements));
|
||||
|
||||
// raid progression
|
||||
$activity = array_filter($statistics, function ($x) { return $x['cur'] > (time() - MONTH); });
|
||||
foreach ($activity as &$r)
|
||||
$r = 1;
|
||||
|
||||
// ony .. subtract 10-man from 25-man
|
||||
|
||||
$profile['statistics'] = array_combine(array_keys($statistics), array_column($statistics, 'max'));
|
||||
$profile['activity'] = $activity;
|
||||
break;
|
||||
case Type::SKILL:
|
||||
foreach ($data as &$d)
|
||||
$d = [$d['cur'], $d['max']];
|
||||
|
||||
$profile['skills'] = $data;
|
||||
break;
|
||||
}
|
||||
$qList = new QuestList(array(['id', $quests], Cfg::get('SQL_LIMIT_NONE')));
|
||||
if (!$qList->error)
|
||||
foreach ($qList->iterate() as $id => $__)
|
||||
$profile['quests'][$id] = [$qList->getField('cat1'), $qList->getField('cat2')];
|
||||
}
|
||||
|
||||
$buff = '';
|
||||
// skillId => [value, max]
|
||||
$profile['skills'] = DB::Aowow()->select('SELECT `skillId` AS ARRAY_KEY, `value` AS "0", `max` AS "1" FROM ?_profiler_completion_skills WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
// factionId => amount
|
||||
$profile['reputation'] = DB::Aowow()->selectCol('SELECT `factionId` AS ARRAY_KEY, `standing` FROM ?_profiler_completion_reputation WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
// titleId => 1
|
||||
$profile['titles'] = DB::Aowow()->selectCol('SELECT `titleId` AS ARRAY_KEY, 1 FROM ?_profiler_completion_titles WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
// achievementId => js date object
|
||||
$profile['achievements'] = DB::Aowow()->selectCol('SELECT `achievementId` AS ARRAY_KEY, CONCAT("$new Date(", `date` * 1000, ")") FROM ?_profiler_completion_achievements WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
// just points
|
||||
$profile['achievementpoints'] = $profile['achievements'] ? DB::Aowow()->selectCell('SELECT SUM(`points`) FROM ?_achievement WHERE `id` IN (?a)', array_keys($profile['achievements'])) : 0;
|
||||
|
||||
// achievementId => counter
|
||||
$profile['statistics'] = DB::Aowow()->selectCol('SELECT `achievementId` AS ARRAY_KEY, `counter` FROM ?_profiler_completion_statistics WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
// achievementId => 1
|
||||
$profile['activity'] = DB::Aowow()->selectCol('SELECT `achievementId` AS ARRAY_KEY, 1 FROM ?_profiler_completion_statistics WHERE `id` = ?d AND `date` > ?d', $pBase['id'], time() - MONTH);
|
||||
|
||||
// spellId => 1
|
||||
$profile['spells'] = DB::Aowow()->selectCol('SELECT `spellId` AS ARRAY_KEY, 1 FROM ?_profiler_completion_spells WHERE `id` = ?d', $pBase['id']);
|
||||
|
||||
|
||||
$gItems = [];
|
||||
|
||||
$usedSlots = [];
|
||||
if ($this->_get['items'])
|
||||
@@ -668,7 +654,12 @@ class AjaxProfile extends AjaxHandler
|
||||
if (in_array($sl, $invTypes) && !in_array($slot, $usedSlots))
|
||||
{
|
||||
// get and apply inventory
|
||||
$buff .= 'g_items.add('.$iId.', {name_'.User::$localeString.":'".Util::jsEscape($phItems->getField('name', true))."', quality:".$phItems->getField('quality').", icon:'".$phItems->getField('iconString')."', jsonequip:".Util::toJSON($data[$iId])."});\n";
|
||||
$gItems[$iId] = array(
|
||||
'name_'.Lang::getLocale()->json() => $phItems->getField('name', true),
|
||||
'quality' => $phItems->getField('quality'),
|
||||
'icon' => $phItems->getField('iconString'),
|
||||
'jsonequip' => $data[$iId]
|
||||
);
|
||||
$profile['inventory'][$slot] = [$iId, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
$usedSlots[] = $slot;
|
||||
@@ -681,7 +672,7 @@ class AjaxProfile extends AjaxHandler
|
||||
|
||||
if ($items = DB::Aowow()->select('SELECT * FROM ?_profiler_items WHERE id = ?d', $pBase['id']))
|
||||
{
|
||||
$itemz = new ItemList(array(['id', array_column($items, 'item')], CFG_SQL_LIMIT_NONE));
|
||||
$itemz = new ItemList(array(['id', array_column($items, 'item')], Cfg::get('SQL_LIMIT_NONE')));
|
||||
if (!$itemz->error)
|
||||
{
|
||||
$data = $itemz->getListviewData(ITEMINFO_JSON | ITEMINFO_SUBITEMS);
|
||||
@@ -691,20 +682,26 @@ class AjaxProfile extends AjaxHandler
|
||||
if ($itemz->getEntry($i['item']) && !in_array($i['slot'], $usedSlots))
|
||||
{
|
||||
// get and apply inventory
|
||||
$buff .= 'g_items.add('.$i['item'].', {name_'.User::$localeString.":'".Util::jsEscape($itemz->getField('name', true))."', quality:".$itemz->getField('quality').", icon:'".$itemz->getField('iconString')."', jsonequip:".Util::toJSON($data[$i['item']])."});\n";
|
||||
$gItems[$i['item']] = array(
|
||||
'name_'.Lang::getLocale()->json() => $itemz->getField('name', true),
|
||||
'quality' => $itemz->getField('quality'),
|
||||
'icon' => $itemz->getField('iconString'),
|
||||
'jsonequip' => $data[$i['item']]
|
||||
);
|
||||
$profile['inventory'][$i['slot']] = [$i['item'], $i['subItem'], $i['permEnchant'], $i['tempEnchant'], $i['gem1'], $i['gem2'], $i['gem3'], $i['gem4']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($buff)
|
||||
$buff .= "\n";
|
||||
$buff = '';
|
||||
foreach ($gItems as $id => $item)
|
||||
$buff .= 'g_items.add('.$id.', '.Util::toJSON($item, JSON_NUMERIC_CHECK | JSON_UNESCAPED_UNICODE).");\n";
|
||||
|
||||
|
||||
// if ($au = $char->getField('auras'))
|
||||
// {
|
||||
// $auraz = new SpellList(array(['id', $char->getField('auras')], CFG_SQL_LIMIT_NONE));
|
||||
// $auraz = new SpellList(array(['id', $char->getField('auras')], Cfg::get('SQL_LIMIT_NONE')));
|
||||
// $dataz = $auraz->getListviewData();
|
||||
// $modz = $auraz->getProfilerMods();
|
||||
|
||||
@@ -723,7 +720,7 @@ class AjaxProfile extends AjaxHandler
|
||||
// }
|
||||
// }
|
||||
|
||||
// $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', modifier:".Util::toJSON($mods)."});\n";
|
||||
// $buff .= 'g_spells.add('.$id.", {id:".$id.", name:'".Util::jsEscape(mb_substr($data['name'], 1))."', icon:'".$data['icon']."', callback:".Util::toJSON($mods)."});\n";
|
||||
// }
|
||||
// $buff .= "\n";
|
||||
// }
|
||||
@@ -762,6 +759,22 @@ class AjaxProfile extends AjaxHandler
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected static function checkTalentString(string $val) : string
|
||||
{
|
||||
if (preg_match('/^\d+$/', $val))
|
||||
return $val;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected static function checkGlyphString(string $val) : string
|
||||
{
|
||||
if (preg_match('/^\d+(:\d+)*$/', $val))
|
||||
return $val;
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
451
includes/cfg.class.php
Normal file
451
includes/cfg.class.php
Normal file
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class Cfg
|
||||
{
|
||||
public const PATTERN_CONF_KEY = '/[a-z0-9_\.\-]/i';
|
||||
public const PATTERN_INV_CONF_KEY = '/[^a-z0-9_\.\-]/i';
|
||||
public const PATTERN_INVALID_CHARS = '/\p{C}/ui';
|
||||
|
||||
// config flags
|
||||
public const FLAG_TYPE_INT = 0x001; // validate with intVal()
|
||||
public const FLAG_TYPE_FLOAT = 0x002; // validate with floatVal()
|
||||
public const FLAG_TYPE_BOOL = 0x004; // 0 || 1
|
||||
public const FLAG_TYPE_STRING = 0x008; //
|
||||
public const FLAG_OPT_LIST = 0x010; // single option
|
||||
public const FLAG_BITMASK = 0x020; // multiple options
|
||||
public const FLAG_PHP = 0x040; // applied with ini_set() [restrictions apply!]
|
||||
public const FLAG_PERSISTENT = 0x080; // can not be deleted
|
||||
public const FLAG_REQUIRED = 0x100; // required to have non-empty value
|
||||
public const FLAG_ON_LOAD_FN = 0x200; // run static function of the same name after load
|
||||
public const FLAG_ON_SET_FN = 0x400; // run static function of the same name as validator
|
||||
public const FLAG_INTERNAL = 0x800; // can not be configures, automaticly calculated, skip on lists
|
||||
|
||||
public const CAT_MISCELLANEOUS = 0;
|
||||
public const CAT_SITE = 1;
|
||||
public const CAT_CACHE = 2;
|
||||
public const CAT_ACCOUNT = 3;
|
||||
public const CAT_SESSION = 4;
|
||||
public const CAT_SITE_REPUTATION = 5;
|
||||
public const CAT_ANALYTICS = 6;
|
||||
public const CAT_PROFILER = 7;
|
||||
|
||||
public static $categories = array( // don't mind the ordering ... please?
|
||||
1 => 'Site', 'Caching', 'Account', 'Session', 'Site Reputation', 'Google Analytics', 'Profiler', 0 => 'Other'
|
||||
);
|
||||
|
||||
private const IDX_VALUE = 0;
|
||||
private const IDX_FLAGS = 1;
|
||||
private const IDX_CATEGORY = 2;
|
||||
private const IDX_DEFAULT = 3;
|
||||
private const IDX_COMMENT = 4;
|
||||
|
||||
private static $store = []; // name => [value, flags, cat, default, comment]
|
||||
private static $isLoaded = false;
|
||||
|
||||
private static $rebuildScripts = array(
|
||||
// 'rep_req_border_unco' => ['global'], // currently not a template or buildScript
|
||||
// 'rep_req_border_rare' => ['global'],
|
||||
// 'rep_req_border_epic' => ['global'],
|
||||
// 'rep_req_border_lege' => ['global'],
|
||||
'profiler_enable' => ['realms', 'realmMenu'],
|
||||
'battlegroup' => ['realms', 'realmMenu'],
|
||||
'name_short' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'demo'],
|
||||
'site_host' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'demo', 'power'],
|
||||
'static_host' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'power'],
|
||||
'contact_email' => ['markup'],
|
||||
'locales' => ['locales']
|
||||
);
|
||||
|
||||
public static function load() : void
|
||||
{
|
||||
if (!DB::isConnectable(DB_AOWOW))
|
||||
return;
|
||||
|
||||
$sets = DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value` AS "0", `flags` AS "1", `cat` AS "2", `default` AS "3", `comment` AS "4" FROM ?_config ORDER BY `key` ASC');
|
||||
foreach ($sets as $key => [$value, $flags, $catg, $default, $comment])
|
||||
{
|
||||
$php = $flags & self::FLAG_PHP;
|
||||
|
||||
if ($err = self::validate($value, $flags, $comment))
|
||||
{
|
||||
self::throwError('Aowow config '.strtoupper($key).' failed validation and was skipped: '.$err);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flags & self::FLAG_INTERNAL)
|
||||
{
|
||||
self::throwError('Aowow config '.strtoupper($key).' is flagged as internaly generated and should not have been set in DB.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flags & self::FLAG_ON_LOAD_FN)
|
||||
{
|
||||
if (!method_exists(__CLASS__, $key))
|
||||
self::throwError('Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set');
|
||||
else
|
||||
self::{$key}($value);
|
||||
}
|
||||
|
||||
if ($php)
|
||||
ini_set(strtolower($key), $value);
|
||||
|
||||
self::$store[strtolower($key)] = [$value, $flags, $catg, $default, $comment];
|
||||
}
|
||||
|
||||
if (CLI && !count(self::$store))
|
||||
{
|
||||
CLI::write('Cfg::load - aowow_config unexpectedly empty.', CLI::LOG_WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
self::$isLoaded = true;
|
||||
}
|
||||
|
||||
public static function add(string $key, /*int|string*/ $value) : string
|
||||
{
|
||||
if (!self::$isLoaded)
|
||||
return 'used add() on uninitialized config';
|
||||
|
||||
if (!$key)
|
||||
return 'empty option name given';
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if (preg_match(self::PATTERN_INV_CONF_KEY, $key))
|
||||
return 'invalid chars in option name: [a-z 0-9 _ . -] are allowed';
|
||||
|
||||
if (isset(self::$store[$key]))
|
||||
return 'this configuration option is already in use';
|
||||
|
||||
if ($errStr = self::validate($value))
|
||||
return $errStr;
|
||||
|
||||
if (ini_get($key) === false || ini_set($key, $value) === false)
|
||||
return 'this configuration option cannot be set';
|
||||
|
||||
$flags = self::FLAG_TYPE_STRING | self::FLAG_PHP;
|
||||
if (!DB::Aowow()->query('INSERT IGNORE INTO ?_config (`key`, `value`, `cat`, `flags`) VALUES (?, ?, ?d, ?d)', $key, $value, self::CAT_MISCELLANEOUS, $flags))
|
||||
return 'internal error';
|
||||
|
||||
self::$store[$key] = [$value, $flags, self::CAT_MISCELLANEOUS, null, null];
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function delete(string $key) : string
|
||||
{
|
||||
if (!self::$isLoaded)
|
||||
return 'used delete() on uninitialized config';
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if (!isset(self::$store[$key]))
|
||||
return 'configuration option not found';
|
||||
|
||||
if (self::$store[$key][self::IDX_FLAGS] & self::FLAG_PERSISTENT)
|
||||
return 'can\'t delete persistent option';
|
||||
|
||||
if (!(self::$store[$key][self::IDX_FLAGS] & self::FLAG_PHP))
|
||||
return 'can\'t delete non-php option';
|
||||
|
||||
if (self::$store[$key][self::IDX_FLAGS] & self::FLAG_INTERNAL)
|
||||
return 'can\'t delete internal option';
|
||||
|
||||
if (!DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0 AND (`flags` & ?d) > 0', $key, self::FLAG_PERSISTENT, self::FLAG_PHP))
|
||||
return 'internal error';
|
||||
|
||||
unset(self::$store[$key]);
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function get(string $key, bool $fromDB = false, bool $fullInfo = false) // : int|float|string
|
||||
{
|
||||
$key = strtolower($key);
|
||||
|
||||
if (!isset(self::$store[$key]))
|
||||
{
|
||||
if (self::$isLoaded)
|
||||
self::throwError('cfg not defined: '.strtoupper($key));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($fromDB && $fullInfo)
|
||||
return array_values(DB::Aowow()->selectRow('SELECT `value`, `flags`, `cat`, `default`, `comment` FROM ?_config WHERE `key` = ?', $key));
|
||||
if ($fromDB)
|
||||
return DB::Aowow()->selectCell('SELECT `value` FROM ?_config WHERE `key` = ?', $key);
|
||||
if ($fullInfo)
|
||||
return self::$store[$key];
|
||||
|
||||
return self::$store[$key][self::IDX_VALUE];
|
||||
}
|
||||
|
||||
public static function set(string $key, /*int|string*/ $value, ?array &$rebuildFiles = []) : string
|
||||
{
|
||||
if (!self::$isLoaded)
|
||||
return 'used set() on uninitialized config';
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if (!isset(self::$store[$key]))
|
||||
return 'configuration option not found';
|
||||
|
||||
[$oldValue, $flags, , , $comment] = self::$store[$key];
|
||||
|
||||
if ($flags & self::FLAG_INTERNAL)
|
||||
return 'can\'t set an internal option directly';
|
||||
|
||||
if ($err = self::validate($value, $flags, $comment))
|
||||
return $err;
|
||||
|
||||
if ($flags & self::FLAG_REQUIRED && !strlen($value))
|
||||
return 'empty value given for required config';
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $value, $key);
|
||||
|
||||
self::$store[$key][self::IDX_VALUE] = $value;
|
||||
|
||||
// validate change
|
||||
if ($flags & self::FLAG_ON_SET_FN)
|
||||
{
|
||||
$errMsg = '';
|
||||
if (!method_exists(__CLASS__, $key))
|
||||
$errMsg = 'Aowow config '.strtoupper($key).' flagged for onSetFN validation, but no handler was set';
|
||||
else
|
||||
self::{$key}($value, $errMsg);
|
||||
|
||||
if ($errMsg)
|
||||
{
|
||||
// rollback change
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $oldValue, $key);
|
||||
self::$store[$key][self::IDX_VALUE] = $oldValue;
|
||||
|
||||
return $errMsg;
|
||||
}
|
||||
}
|
||||
|
||||
if ($flags & self::FLAG_ON_LOAD_FN)
|
||||
{
|
||||
if (!method_exists(__CLASS__, $key))
|
||||
return 'Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set';
|
||||
else
|
||||
self::{$key}($value);
|
||||
}
|
||||
|
||||
// trigger setup build
|
||||
return self::handleFileBuild($key, $rebuildFiles);
|
||||
}
|
||||
|
||||
public static function reset(string $key, ?array &$rebuildFiles = []) : string
|
||||
{
|
||||
if (!self::$isLoaded)
|
||||
return 'used reset() on uninitialized config';
|
||||
|
||||
$key = strtolower($key);
|
||||
|
||||
if (!isset(self::$store[$key]))
|
||||
return 'configuration option not found';
|
||||
|
||||
[$oldValue, $flags, , $default, ] = self::$store[$key];
|
||||
if ($flags & self::FLAG_INTERNAL)
|
||||
return 'can\'t set an internal option directly';
|
||||
|
||||
if (!$default)
|
||||
return 'config option has no default value';
|
||||
|
||||
// @eval .. some dafault values are supplied as bitmask or the likes
|
||||
if (!($flags & Cfg::FLAG_TYPE_STRING))
|
||||
$default = @eval('return ('.$default.');');
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $default, $key);
|
||||
self::$store[$key][self::IDX_VALUE] = $default;
|
||||
|
||||
// validate change
|
||||
if ($flags & self::FLAG_ON_SET_FN)
|
||||
{
|
||||
$errMsg = '';
|
||||
if (!method_exists(__CLASS__, $key))
|
||||
$errMsg = 'required onSetFN validator not set';
|
||||
else
|
||||
self::{$key}($default, $errMsg);
|
||||
|
||||
if ($errMsg)
|
||||
{
|
||||
// rollback change
|
||||
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $oldValue, $key);
|
||||
self::$store[$key][self::IDX_VALUE] = $oldValue;
|
||||
|
||||
return $errMsg;
|
||||
}
|
||||
}
|
||||
|
||||
// trigger setup build
|
||||
return self::handleFileBuild($key, $rebuildFiles);
|
||||
}
|
||||
|
||||
public static function forCategory(int $category) : \Generator
|
||||
{
|
||||
foreach (self::$store as $k => [, $flags, $catg, , ])
|
||||
if ($catg == $category && !($flags & self::FLAG_INTERNAL))
|
||||
yield $k => self::$store[$k];
|
||||
}
|
||||
|
||||
public static function applyToString(string $string) : string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
['/CFG_([A-Z_]+)/', '/((HOST|STATIC)_URL)/'],
|
||||
function ($m) {
|
||||
if (!isset(self::$store[strtolower($m[1])]))
|
||||
return $m[1];
|
||||
|
||||
[$val, $flags, , , ] = self::$store[strtolower($m[1])];
|
||||
return $flags & (self::FLAG_TYPE_FLOAT | self::FLAG_TYPE_INT) ? Lang::nf($val) : $val;
|
||||
},
|
||||
$string
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/************/
|
||||
/* internal */
|
||||
/************/
|
||||
|
||||
private static function validate(&$value, int $flags = self::FLAG_TYPE_STRING | self::FLAG_PHP, string $comment = ' - ') : string
|
||||
{
|
||||
$value = preg_replace(self::PATTERN_INVALID_CHARS, '', $value);
|
||||
|
||||
if (!($flags & (self::FLAG_TYPE_BOOL | self::FLAG_TYPE_FLOAT | self::FLAG_TYPE_INT | self::FLAG_TYPE_STRING)))
|
||||
return 'no type set for value';
|
||||
|
||||
if ($flags & self::FLAG_TYPE_INT && !Util::checkNumeric($value, NUM_CAST_INT))
|
||||
return 'value must be integer';
|
||||
|
||||
if ($flags & self::FLAG_TYPE_FLOAT && !Util::checkNumeric($value, NUM_CAST_FLOAT))
|
||||
return 'value must be float';
|
||||
|
||||
if ($flags & self::FLAG_OPT_LIST)
|
||||
{
|
||||
$info = explode(' - ', $comment)[1];
|
||||
foreach (explode(', ', $info) as $option)
|
||||
if (explode(':', $option)[0] == $value)
|
||||
return '';
|
||||
|
||||
return 'value not in range';
|
||||
}
|
||||
|
||||
if ($flags & self::FLAG_BITMASK)
|
||||
{
|
||||
$mask = 0x0;
|
||||
$info = explode(' - ', $comment)[1];
|
||||
foreach (explode(', ', $info) as $option)
|
||||
$mask |= (1 << explode(':', $option)[0]);
|
||||
|
||||
if (!($value &= $mask) && ($flags & self::FLAG_REQUIRED))
|
||||
return 'value not in range';
|
||||
}
|
||||
|
||||
if ($flags & self::FLAG_TYPE_BOOL)
|
||||
$value = (bool)$value;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function handleFileBuild(string $key, array &$rebuildFiles) : string
|
||||
{
|
||||
if (!isset(self::$rebuildScripts[$key]))
|
||||
return '';
|
||||
|
||||
$msg = '';
|
||||
|
||||
if (CLI)
|
||||
{
|
||||
$rebuildFiles = array_merge($rebuildFiles, self::$rebuildScripts[$key]);
|
||||
return '';
|
||||
}
|
||||
|
||||
// not in CLI mode and build() can only be run from CLI. .. todo: other options..?
|
||||
exec('php aowow --build='.implode(',', self::$rebuildScripts[$key]), $out);
|
||||
foreach ($out as $o)
|
||||
if (strstr($o, 'ERR'))
|
||||
$msg .= explode('0m]', $o)[1]."<br />\n";
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
private static function throwError($msg) : void
|
||||
{
|
||||
if (CLI)
|
||||
CLI::write($msg, CLI::LOG_ERROR);
|
||||
else
|
||||
trigger_error($msg, E_USER_ERROR);
|
||||
}
|
||||
|
||||
private static function locales(/*int|string*/ $value, ?string &$msg = '') : bool
|
||||
{
|
||||
if (!CLI)
|
||||
return true;
|
||||
|
||||
// note: Change is written to db and storage at this point, but can be rolled back.
|
||||
if (CLISetup::setLocales())
|
||||
return true;
|
||||
|
||||
$msg .= 'no valid locales set';
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function acc_auth_mode(/*int|string*/ $value, ?string &$msg = '') : bool
|
||||
{
|
||||
if ($value == 1 && !extension_loaded('gmp'))
|
||||
{
|
||||
$msg .= 'PHP extension GMP is required to use TrinityCore as auth source, but is not currently enabled.';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function profiler_enable(/*int|string*/ $value, ?string &$msg = '') : bool
|
||||
{
|
||||
if ($value != 1)
|
||||
return true;
|
||||
|
||||
return Profiler::queueStart($msg);
|
||||
}
|
||||
|
||||
private static function static_host(/*int|string*/ $value, ?string &$msg = '') : bool
|
||||
{
|
||||
self::$store['static_url'] = array( // points js to images & scripts
|
||||
(self::useSSL() ? 'https://' : 'http://').$value,
|
||||
self::FLAG_PERSISTENT | self::FLAG_TYPE_STRING | self::FLAG_INTERNAL,
|
||||
self::CAT_SITE,
|
||||
null, // no default value
|
||||
null, // no comment/info
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function site_host(/*int|string*/ $value, ?string &$msg = '') : bool
|
||||
{
|
||||
self::$store['host_url'] = array( // points js to executable files
|
||||
(self::useSSL() ? 'https://' : 'http://').$value,
|
||||
self::FLAG_PERSISTENT | self::FLAG_TYPE_STRING | self::FLAG_INTERNAL,
|
||||
self::CAT_SITE,
|
||||
null, // no default value
|
||||
null, // no comment/info
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function useSSL() : bool
|
||||
{
|
||||
return (($_SERVER['HTTPS'] ?? 'off') != 'off') || (self::$store['force_ssl'][self::IDX_VALUE] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
746
includes/components/Conditions/Conditions.class.php
Normal file
746
includes/components/Conditions/Conditions.class.php
Normal file
@@ -0,0 +1,746 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
// TrinityCore - Condition System
|
||||
|
||||
class Conditions
|
||||
{
|
||||
// enum TypeID
|
||||
private const TYPEID_OBJECT = 0;
|
||||
private const TYPEID_ITEM = 1;
|
||||
private const TYPEID_CONTAINER = 2;
|
||||
private const TYPEID_UNIT = 3;
|
||||
private const TYPEID_PLAYER = 4;
|
||||
private const TYPEID_GAMEOBJECT = 5;
|
||||
private const TYPEID_DYNAMICOBJECT = 6;
|
||||
private const TYPEID_CORPSE = 7;
|
||||
|
||||
public const OP_E = 0; // ==
|
||||
public const OP_GT = 1; // >
|
||||
public const OP_LT = 2; // <
|
||||
public const OP_GT_E = 3; // >=
|
||||
public const OP_LT_E = 4; // <=
|
||||
// Group, Entry, Id
|
||||
public const SRC_NONE = 0; // null, null, null - use when adding external conditions
|
||||
public const SRC_CREATURE_LOOT_TEMPLATE = 1; // tplEntry, itemId, null
|
||||
public const SRC_DISENCHANT_LOOT_TEMPLATE = 2; // tplEntry, itemId, null
|
||||
public const SRC_FISHING_LOOT_TEMPLATE = 3; // tplEntry, itemId, null
|
||||
public const SRC_GAMEOBJECT_LOOT_TEMPLATE = 4; // tplEntry, itemId, null
|
||||
public const SRC_ITEM_LOOT_TEMPLATE = 5; // tplEntry, itemId, null
|
||||
public const SRC_MAIL_LOOT_TEMPLATE = 6; // tplEntry, itemId, null
|
||||
public const SRC_MILLING_LOOT_TEMPLATE = 7; // tplEntry, itemId, null
|
||||
public const SRC_PICKPOCKETING_LOOT_TEMPLATE = 8; // tplEntry, itemId, null
|
||||
public const SRC_PROSPECTING_LOOT_TEMPLATE = 9; // tplEntry, itemId, null
|
||||
public const SRC_REFERENCE_LOOT_TEMPLATE = 10; // tplEntry, itemId, null
|
||||
public const SRC_SKINNING_LOOT_TEMPLATE = 11; // tplEntry, itemId, null
|
||||
public const SRC_SPELL_LOOT_TEMPLATE = 12; // tplEntry, itemId, null
|
||||
public const SRC_SPELL_IMPLICIT_TARGET = 13; // effectMask, spellId, null
|
||||
public const SRC_GOSSIP_MENU = 14; // menuId, textId, null
|
||||
public const SRC_GOSSIP_MENU_OPTION = 15; // menuId, optionId, null
|
||||
public const SRC_CREATURE_TEMPLATE_VEHICLE = 16; // npcId, null, null
|
||||
public const SRC_SPELL = 17; // null, spellId, null
|
||||
public const SRC_SPELL_CLICK_EVENT = 18; // npcId, spellId, null
|
||||
public const SRC_QUEST_AVAILABLE = 19; // null, questId, null
|
||||
public const SRC_QUEST_SHOW_MARK = 20; // null, questId, null - ⚠️ unused as of 01.05.2024
|
||||
public const SRC_VEHICLE_SPELL = 21; // npcId, spellId, null
|
||||
public const SRC_SMART_EVENT = 22; // id, entryGuid, srcType
|
||||
public const SRC_NPC_VENDOR = 23; // npcId, itemId, null
|
||||
public const SRC_SPELL_PROC = 24; // null, spellId, null
|
||||
// public const SRC_SPELL_TERRAIN_SWAP = 25; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_PHASE = 26; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_GRAVEYARD = 27; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_AREATRIGGER = 28; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_CONVERSATION_LINE = 29; // - ❌ reserved for TC master
|
||||
public const SRC_AREATRIGGER_CLIENT = 30; // null, atId, null
|
||||
// public const SRC_SPELL_TRAINER_SPELL = 31; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_OBJECT_VISIBILITY = 32; // - ❌ reserved for TC master
|
||||
// public const SRC_SPELL_SPAWN_GROUP = 33; // - ❌ reserved for TC master
|
||||
|
||||
public const NONE = 0; // always true: NULL, NULL, NULL
|
||||
public const AURA = 1; // aura is applied: spellId, effIdx, NULL
|
||||
public const ITEM = 2; // owns item: itemId, count, includeBank?
|
||||
public const ITEM_EQUIPPED = 3; // has item equipped: itemId, NULL, NULL
|
||||
public const ZONEID = 4; // is in zone: areaId, NULL, NULL
|
||||
public const REPUTATION_RANK = 5; // reputation status: factionId, rankMask, NULL
|
||||
public const TEAM = 6; // is on team: teamId, NULL, NULL
|
||||
public const SKILL = 7; // has skill: skillId, value, NULL
|
||||
public const QUESTREWARDED = 8; // has finished quest: questId, NULL, NULL
|
||||
public const QUESTTAKEN = 9; // has accepted quest: questId, NULL, NULL
|
||||
public const DRUNKENSTATE = 10; // has drunken status: stateId, NULL, NULL
|
||||
public const WORLD_STATE = 11; // world var == value: worldStateId, value, NULL
|
||||
public const ACTIVE_EVENT = 12; // world event is active: eventId, NULL, NULL
|
||||
public const INSTANCE_INFO = 13; // instance var == data: entry data, type
|
||||
public const QUEST_NONE = 14; // never seen quest: questId, NULL, NULL
|
||||
public const CHR_CLASS = 15; // belongs to classes: classMask, NULL, NULL
|
||||
public const CHR_RACE = 16; // belongs to races: raceMask, NULL, NULL
|
||||
public const ACHIEVEMENT = 17; // obtained achievement: achievementId, NULL, NULL
|
||||
public const TITLE = 18; // obtained title: titleId, NULL, NULL
|
||||
public const SPAWNMASK = 19; // spawnMask, NULL, NULL
|
||||
public const GENDER = 20; // has gender: genderId, NULL, NULL
|
||||
public const UNIT_STATE = 21; // unit has state: unitState, NULL, NULL
|
||||
public const MAPID = 22; // is on map: mapId, NULL, NULL
|
||||
public const AREAID = 23; // is in area: areaId, NULL, NULL
|
||||
public const CREATURE_TYPE = 24; // creature is of type: creaturetypeId, NULL, NULL
|
||||
public const SPELL = 25; // knows spell: spellId, NULL, NULL
|
||||
public const PHASEMASK = 26; // is in phase: phaseMask, NULL, NULL
|
||||
public const LEVEL = 27; // player level is..: level, comparator, NULL
|
||||
public const QUEST_COMPLETE = 28; // has completed quest: questId, NULL, NULL
|
||||
public const NEAR_CREATURE = 29; // is near creature: creatureId, dist, includeCorpse?
|
||||
public const NEAR_GAMEOBJECT = 30; // is near gameObject: gameObjectId, dist, NULL
|
||||
public const OBJECT_ENTRY_GUID = 31; // target is ???: objectType, id, guid
|
||||
public const TYPE_MASK = 32; // target matches type: typeMask, NULL, NULL
|
||||
public const RELATION_TO = 33; // Cond.Target, relation, NULL
|
||||
public const REACTION_TO = 34; // Cond.Target, rankMask, NULL
|
||||
public const DISTANCE_TO = 35; // distance to target Cond.Target, dist, comparator
|
||||
public const ALIVE = 36; // target is alive: NULL, NULL, NULL
|
||||
public const HP_VAL = 37; // targets absolute health: amount, comparator, NULL
|
||||
public const HP_PCT = 38; // targets relative health: amount, comparator, NULL
|
||||
public const REALM_ACHIEVEMENT = 39; // realmfirst was achieved: achievementId, NULL, NULL
|
||||
public const IN_WATER = 40; // unit is swimming: NULL, NULL, NULL
|
||||
// public const TERRAIN_SWAP = 41; // ❌ reserved for TC master
|
||||
public const STAND_STATE = 42; // stateType, state, NULL
|
||||
public const DAILY_QUEST_DONE = 43; // repeatable quest done: questId, NULL, NULL
|
||||
public const CHARMED = 44; // unit is charmed: NULL, NULL, NULL
|
||||
public const PET_TYPE = 45; // player has pet of type: petType, NULL, NULL
|
||||
public const TAXI = 46; // player is on taxi: NULL, NULL, NULL
|
||||
public const QUESTSTATE = 47; // questId, stateMask, NULL
|
||||
public const QUEST_OBJECTIVE_PROGRESS = 48; // questId, objectiveIdx, count
|
||||
public const DIFFICULTY_ID = 49; // map has difficulty id: difficulty, NULL, NULL
|
||||
public const GAMEMASTER = 50; // player is GM: canBeGM?, NULL, NULL
|
||||
// public const OBJECT_ENTRY_GUID_MASTER = 51; // ❌ reserved for TC master
|
||||
// public const TYPE_MASK_MASTER = 52; // ❌ reserved for TC master
|
||||
// public const BATTLE_PET_COUNT = 53; // ❌ reserved for TC master
|
||||
// public const SCENARIO_STEP = 54; // ❌ reserved for TC master
|
||||
// public const SCENE_IN_PROGRESS = 55; // ❌ reserved for TC master
|
||||
// public const PLAYER_CONDITION = 56; // ❌ reserved for TC master
|
||||
|
||||
private const IDX_SRC_GROUP = 0;
|
||||
private const IDX_SRC_ENTRY = 1;
|
||||
private const IDX_SRC_ID = 2;
|
||||
private const IDX_SRC_FN = 3;
|
||||
|
||||
private static $source = array( // [Group, Entry, Id, typeResolverFN]
|
||||
self::SRC_NONE => [null, null, null, null],
|
||||
self::SRC_CREATURE_LOOT_TEMPLATE => [Type::NPC, Type::ITEM, null, 'lootIdToNpc'],
|
||||
self::SRC_DISENCHANT_LOOT_TEMPLATE => [Type::ITEM, Type::ITEM, null, 'disenchantIdToItem'],
|
||||
self::SRC_FISHING_LOOT_TEMPLATE => [Type::ZONE, Type::ITEM, null, null],
|
||||
self::SRC_GAMEOBJECT_LOOT_TEMPLATE => [Type::OBJECT, Type::ITEM, null, 'lootIdToGObject'],
|
||||
self::SRC_ITEM_LOOT_TEMPLATE => [Type::ITEM, Type::ITEM, null, null],
|
||||
self::SRC_MAIL_LOOT_TEMPLATE => [Type::QUEST, Type::ITEM, null, 'RewardTemplateToQuest'],
|
||||
self::SRC_MILLING_LOOT_TEMPLATE => [Type::ITEM, Type::ITEM, null, null],
|
||||
self::SRC_PICKPOCKETING_LOOT_TEMPLATE => [Type::NPC, Type::ITEM, null, 'PickpocketLootToNpc'],
|
||||
self::SRC_PROSPECTING_LOOT_TEMPLATE => [Type::ITEM, Type::ITEM, null, null],
|
||||
self::SRC_REFERENCE_LOOT_TEMPLATE => [null, Type::ITEM, null, null],
|
||||
self::SRC_SKINNING_LOOT_TEMPLATE => [Type::NPC, Type::ITEM, null, 'SkinLootToNpc'],
|
||||
self::SRC_SPELL_LOOT_TEMPLATE => [Type::SPELL, Type::ITEM, null, null],
|
||||
self::SRC_SPELL_IMPLICIT_TARGET => [true, Type::SPELL, null, null],
|
||||
self::SRC_GOSSIP_MENU => [true, true, null, null],
|
||||
self::SRC_GOSSIP_MENU_OPTION => [true, true, null, null],
|
||||
self::SRC_CREATURE_TEMPLATE_VEHICLE => [null, Type::NPC, null, null],
|
||||
self::SRC_SPELL => [null, Type::SPELL, null, null],
|
||||
self::SRC_SPELL_CLICK_EVENT => [Type::NPC, Type::SPELL, null, null],
|
||||
self::SRC_QUEST_AVAILABLE => [null, Type::QUEST, null, null],
|
||||
self::SRC_QUEST_SHOW_MARK => [null, Type::QUEST, null, null],
|
||||
self::SRC_VEHICLE_SPELL => [Type::NPC, Type::SPELL, null, null],
|
||||
self::SRC_SMART_EVENT => [true, true, true, null],
|
||||
self::SRC_NPC_VENDOR => [Type::NPC, Type::ITEM, null, null],
|
||||
self::SRC_SPELL_PROC => [null, Type::SPELL, null, null],
|
||||
self::SRC_AREATRIGGER_CLIENT => [null, Type::AREATRIGGER, null, null]
|
||||
);
|
||||
|
||||
private const IDX_CND_VAL1 = 0;
|
||||
private const IDX_CND_VAL2 = 1;
|
||||
private const IDX_CND_VAL3 = 2;
|
||||
private const IDX_CND_FN = 3;
|
||||
|
||||
private static $conditions = array(// [Value1, Value2, Value3, handlerFn]
|
||||
self::NONE => [null, null, null, null],
|
||||
self::AURA => [Type::SPELL, null, null, null],
|
||||
self::ITEM => [Type::ITEM, true, true, null],
|
||||
self::ITEM_EQUIPPED => [Type::ITEM, null, null, null],
|
||||
self::ZONEID => [Type::ZONE, null, null, null],
|
||||
self::REPUTATION_RANK => [Type::FACTION, true, null, null],
|
||||
self::TEAM => [true, null, null, 'factionToSide'],
|
||||
self::SKILL => [Type::SKILL, true, null, null],
|
||||
self::QUESTREWARDED => [Type::QUEST, null, null, null],
|
||||
self::QUESTTAKEN => [Type::QUEST, null, null, null],
|
||||
self::DRUNKENSTATE => [true, null, null, null],
|
||||
self::WORLD_STATE => [true, true, null, null],
|
||||
self::ACTIVE_EVENT => [Type::WORLDEVENT, null, null, null],
|
||||
self::INSTANCE_INFO => [true, true, true, null],
|
||||
self::QUEST_NONE => [Type::QUEST, null, null, null],
|
||||
self::CHR_CLASS => [Type::CHR_CLASS, null, null, 'maskToBits'],
|
||||
self::CHR_RACE => [Type::CHR_RACE, null, null, 'maskToBits'],
|
||||
self::ACHIEVEMENT => [Type::ACHIEVEMENT, null, null, null],
|
||||
self::TITLE => [Type::TITLE, null, null, null],
|
||||
self::SPAWNMASK => [true, null, null, null],
|
||||
self::GENDER => [true, null, null, null],
|
||||
self::UNIT_STATE => [true, null, null, null],
|
||||
self::MAPID => [true, true, null, 'mapToZone'],
|
||||
self::AREAID => [Type::ZONE, null, null, null],
|
||||
self::CREATURE_TYPE => [true, null, null, null],
|
||||
self::SPELL => [Type::SPELL, null, null, null],
|
||||
self::PHASEMASK => [true, null, null, null],
|
||||
self::LEVEL => [true, true, null, null],
|
||||
self::QUEST_COMPLETE => [Type::QUEST, null, null, null],
|
||||
self::NEAR_CREATURE => [Type::NPC, true, true, null],
|
||||
self::NEAR_GAMEOBJECT => [Type::OBJECT, true, true, null],
|
||||
self::OBJECT_ENTRY_GUID => [true, true, true, 'typeidToId'],
|
||||
self::TYPE_MASK => [true, null, null, null],
|
||||
self::RELATION_TO => [true, true, null, null],
|
||||
self::REACTION_TO => [true, true, null, null],
|
||||
self::DISTANCE_TO => [true, true, true, null],
|
||||
self::ALIVE => [null, null, null, null],
|
||||
self::HP_VAL => [true, true, null, null],
|
||||
self::HP_PCT => [true, true, null, null],
|
||||
self::REALM_ACHIEVEMENT => [Type::ACHIEVEMENT, null, null, null],
|
||||
self::IN_WATER => [null, null, null, null],
|
||||
self::STAND_STATE => [true, true, null, null],
|
||||
self::DAILY_QUEST_DONE => [Type::QUEST, null, null, null],
|
||||
self::CHARMED => [null, null, null, null],
|
||||
self::PET_TYPE => [true, null, null, null],
|
||||
self::TAXI => [null, null, null, null],
|
||||
self::QUESTSTATE => [Type::QUEST, true, null, null],
|
||||
self::QUEST_OBJECTIVE_PROGRESS => [Type::QUEST, true, true, null],
|
||||
self::DIFFICULTY_ID => [true, null, null, null],
|
||||
self::GAMEMASTER => [true, null, null, null]
|
||||
);
|
||||
|
||||
private $jsGlobals = [];
|
||||
private $rows = [];
|
||||
private $result = [];
|
||||
private $resultExtra = [];
|
||||
|
||||
|
||||
/******/
|
||||
/* IN */
|
||||
/******/
|
||||
|
||||
public function getBySourceEntry(int $entry, int ...$srcType) : self
|
||||
{
|
||||
$this->rows = array_merge($this->rows, DB::World()->select(
|
||||
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
|
||||
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
|
||||
FROM conditions
|
||||
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceEntry` = ?d
|
||||
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
|
||||
$srcType, $entry
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBySourceGroup(int $group, int ...$srcType) : self
|
||||
{
|
||||
$this->rows = array_merge($this->rows, DB::World()->select(
|
||||
'SELECT `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `SourceId`, `ElseGroup`,
|
||||
`ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`
|
||||
FROM conditions
|
||||
WHERE `SourceTypeOrReferenceId` IN (?a) AND `SourceGroup` = ?d
|
||||
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
|
||||
$srcType, $group
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getByCondition(int $type, int $typeId/* , int ...$conditionIds */) : self
|
||||
{
|
||||
$lookups = []; // can only be in val1 for now
|
||||
foreach (self::$conditions as $cId => [$cVal1, , , ])
|
||||
if ($type === $cVal1 /* && (!$conditionIds || in_array($cId, $conditionIds)) */ )
|
||||
{
|
||||
if ($cId == self::CHR_CLASS || $cId == self::CHR_RACE)
|
||||
$lookups[] = sprintf("(c2.`ConditionTypeOrReference` = %d AND (c2.`ConditionValue1` & %d) > 0)", $cId, 1 << ($typeId - 1));
|
||||
else
|
||||
$lookups[] = sprintf("(c2.`ConditionTypeOrReference` = %d AND c2.`ConditionValue1` = %d)", $cId, $typeId);
|
||||
}
|
||||
|
||||
if (!$lookups)
|
||||
return $this;
|
||||
|
||||
$this->rows = array_merge($this->rows, DB::World()->select(sprintf(
|
||||
'SELECT c1.`SourceTypeOrReferenceId`, c1.`SourceEntry`, c1.`SourceGroup`, c1.`SourceId`, c1.`ElseGroup`,
|
||||
c1.`ConditionTypeOrReference`, c1.`ConditionTarget`, c1.`ConditionValue1`, c1.`ConditionValue2`, c1.`ConditionValue3`, c1.`NegativeCondition`
|
||||
FROM conditions c1
|
||||
JOIN conditions c2 ON c1.SourceTypeOrReferenceId = c2.SourceTypeOrReferenceId AND c1.SourceEntry = c2.SourceEntry AND c1.SourceGroup = c2.SourceGroup AND c1.SourceId = c2.SourceId
|
||||
WHERE %s
|
||||
GROUP BY `SourceTypeOrReferenceId`,`SourceGroup`,`SourceEntry`,`SourceId`,`ElseGroup`,`ConditionTypeOrReference`,`ConditionTarget`,`ConditionValue1`,`ConditionValue2`,`ConditionValue3`
|
||||
ORDER BY `SourceTypeOrReferenceId`, `SourceEntry`, `SourceGroup`, `ElseGroup` ASC',
|
||||
implode(' OR ', $lookups))
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addExternalCondition(int $srcType, string $groupKey, array $condition, bool $orGroup = false) : void
|
||||
{
|
||||
if (!isset(self::$source[$srcType]))
|
||||
return;
|
||||
|
||||
[$cId, $cVal1, $cVal2, $cVal3] = array_pad($condition, 5, 0);
|
||||
if (!isset(self::$conditions[abs($cId)]))
|
||||
return;
|
||||
|
||||
while (substr_count($groupKey, ':') < 3)
|
||||
$groupKey .= ':0'; // pad with missing srcEntry, SrcId, cndTarget to group key
|
||||
|
||||
if (!$this->prepareSource($srcType, ...explode(':', $groupKey)))
|
||||
return;
|
||||
|
||||
if ($c = $this->prepareCondition($cId, $cVal1, $cVal2, $cVal3))
|
||||
{
|
||||
if ($orGroup)
|
||||
$this->result[$srcType][$groupKey][] = [$c];
|
||||
else if (!isset($this->result[$srcType][$groupKey][0]))
|
||||
$this->result[$srcType][$groupKey][0] = [$c];
|
||||
else
|
||||
$this->result[$srcType][$groupKey][0][] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******/
|
||||
/* OUT */
|
||||
/*******/
|
||||
|
||||
public function toListviewTab(string $id = 'conditions', string $name = '') : array
|
||||
{
|
||||
if (!$this->result)
|
||||
return [];
|
||||
|
||||
$out = [];
|
||||
$nCnd = 0;
|
||||
foreach ($this->result as $srcType => $srcData)
|
||||
{
|
||||
foreach ($srcData as $grpKey => $grpData)
|
||||
{
|
||||
if (!isset($this->resultExtra[$srcType][$grpKey]))
|
||||
{
|
||||
$nCnd++;
|
||||
$out[$srcType][$grpKey] = $grpData;
|
||||
}
|
||||
else
|
||||
{
|
||||
$nCnd += count($this->resultExtra[$srcType][$grpKey]);
|
||||
foreach ($this->resultExtra[$srcType][$grpKey] as $extraGrp)
|
||||
$out[$srcType][$extraGrp] = $grpData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = "<script type=\"text/javascript\">\n" .
|
||||
" var markup = ConditionList.createTab(".Util::toJSON($out).");\n" .
|
||||
" Markup.printHtml(markup, 'tab-".$id."', { allow: Markup.CLASS_STAFF })\n" .
|
||||
"</script>";
|
||||
|
||||
$tab = array(
|
||||
'data' => $data,
|
||||
'id' => $id,
|
||||
'name' => ($name ?: '$LANG.tab_conditions') . '+" ('.$nCnd.')"'
|
||||
);
|
||||
|
||||
return [null, $tab];
|
||||
}
|
||||
|
||||
// $keyX params are string(ref to lv column) or int(fixed value)
|
||||
public function toListviewColumn(array &$lvRows, ?array &$extraCols = [], $keyGroup = 'id', $keyEntry = 0, $keyId = 0) : bool
|
||||
{
|
||||
if (!$this->result)
|
||||
return false;
|
||||
|
||||
$success = false;
|
||||
foreach ($lvRows as &$row)
|
||||
{
|
||||
$srcKey = implode(':', array(
|
||||
is_string($keyGroup) ? ($row[$keyGroup] ?? 0) : $keyGroup,
|
||||
is_string($keyEntry) ? ($row[$keyEntry] ?? 0) : $keyEntry,
|
||||
is_string($keyId) ? ($row[$keyId] ?? 0) : $keyId,
|
||||
'' // cndTarget - 0 / 1
|
||||
));
|
||||
|
||||
foreach ($this->result as $cndData)
|
||||
{
|
||||
if (isset($cndData[$srcKey.'0']))
|
||||
{
|
||||
$row['condition'][self::SRC_NONE][$srcKey.'0'] = $cndData[$srcKey.'0'];
|
||||
$success = true;
|
||||
}
|
||||
|
||||
if (isset($cndData[$srcKey.'1']))
|
||||
{
|
||||
$row['condition'][self::SRC_NONE][$srcKey.'1'] = $cndData[$srcKey.'1'];
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($success)
|
||||
$extraCols[] = '$Listview.extraCols.condition';
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function getJsGlobals() : array
|
||||
{
|
||||
return $this->jsGlobals;
|
||||
}
|
||||
|
||||
|
||||
/*********/
|
||||
/* Other */
|
||||
/*********/
|
||||
|
||||
public static function lootTableToConditionSource(string $lootTable) : int
|
||||
{
|
||||
switch ($lootTable)
|
||||
{
|
||||
case LOOT_FISHING: return self::SRC_FISHING_LOOT_TEMPLATE;
|
||||
case LOOT_CREATURE: return self::SRC_CREATURE_LOOT_TEMPLATE;
|
||||
case LOOT_GAMEOBJECT: return self::SRC_GAMEOBJECT_LOOT_TEMPLATE;
|
||||
case LOOT_ITEM: return self::SRC_ITEM_LOOT_TEMPLATE;
|
||||
case LOOT_DISENCHANT: return self::SRC_DISENCHANT_LOOT_TEMPLATE;
|
||||
case LOOT_PROSPECTING: return self::SRC_PROSPECTING_LOOT_TEMPLATE;
|
||||
case LOOT_MILLING: return self::SRC_MILLING_LOOT_TEMPLATE;
|
||||
case LOOT_PICKPOCKET: return self::SRC_PICKPOCKETING_LOOT_TEMPLATE;
|
||||
case LOOT_SKINNING: return self::SRC_SKINNING_LOOT_TEMPLATE;
|
||||
case LOOT_MAIL: return self::SRC_MAIL_LOOT_TEMPLATE;
|
||||
case LOOT_SPELL: return self::SRC_SPELL_LOOT_TEMPLATE;
|
||||
case LOOT_REFERENCE: return self::SRC_REFERENCE_LOOT_TEMPLATE;
|
||||
default: return self::SRC_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public static function extendListviewRow(array &$lvRow, int $srcType, int $groupKey, array $condition) : bool
|
||||
{
|
||||
if (!isset(self::$source[$srcType]))
|
||||
return false;
|
||||
|
||||
[$cId, $cVal1, $cVal2, $cVal3] = array_pad($condition, 5, 0);
|
||||
if (!isset(self::$conditions[abs($cId)]))
|
||||
return false;
|
||||
|
||||
while (substr_count($groupKey, ':') < 3)
|
||||
$groupKey .= ':0'; // pad with missing srcEntry, SrcId, cndTarget to group key
|
||||
|
||||
if ($c = (new self())->prepareCondition($cId, $cVal1, $cVal2, $cVal3))
|
||||
$lvRow['condition'][$srcType][$groupKey][] = [$c];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function prepare() : bool
|
||||
{
|
||||
// itr over rows and prep data
|
||||
if (!$this->rows)
|
||||
return !empty($this->result); // respect previously added externalCnd
|
||||
|
||||
foreach ($this->rows as $r)
|
||||
{
|
||||
if (!isset(self::$source[$r['SourceTypeOrReferenceId']]))
|
||||
{
|
||||
trigger_error('Conditions: skipping condition with unknown SourceTypeOrReferenceId #'.$r['SourceTypeOrReferenceId'], E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset(self::$conditions[$r['ConditionTypeOrReference']]))
|
||||
{
|
||||
trigger_error('Conditions: skipping condition with unknown ConditionTypeOrReference #'.$r['ConditionTypeOrReference'], E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
[$sType, $sGroup, $sEntry, $sId, $cTarget] = $this->prepareSource($r['SourceTypeOrReferenceId'], $r['SourceGroup'], $r['SourceEntry'], $r['SourceId'], $r['ConditionTarget']);
|
||||
if ($sType === null)
|
||||
continue;
|
||||
|
||||
$cnd = $this->prepareCondition(
|
||||
$r['NegativeCondition'] ? -$r['ConditionTypeOrReference'] : $r['ConditionTypeOrReference'],
|
||||
$r['ConditionValue1'],
|
||||
$r['ConditionValue2'],
|
||||
$r['ConditionValue3']
|
||||
);
|
||||
if (!$cnd)
|
||||
continue;
|
||||
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
$this->result[$r['SourceTypeOrReferenceId']] [$group] [$r['ElseGroup']] [] = $cnd;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function prepareSource(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : array
|
||||
{
|
||||
// only one entry in array expected
|
||||
if ($fn = self::$source[$sType][self::IDX_SRC_FN])
|
||||
if (!$this->$fn($sType, $sGroup, $sEntry, $sId, $cTarget))
|
||||
return [null, null, null, null, null];
|
||||
|
||||
[$grp, $entry, $id, $_] = self::$source[$sType];
|
||||
if (is_int($grp))
|
||||
$this->jsGlobals[$grp][$sGroup] = $sGroup;
|
||||
if (is_int($entry))
|
||||
$this->jsGlobals[$entry][$sEntry] = $sEntry;
|
||||
// Note: sourceId currently has no typed content
|
||||
// if (is_int($id))
|
||||
// $this->jsGlobals[$id][$sId] = $sId;
|
||||
|
||||
// more checks? not all sources can retarget
|
||||
$cTarget = min(1, max(0, $cTarget));
|
||||
|
||||
return [$sType, $sGroup, $sEntry, $sId, $cTarget];
|
||||
}
|
||||
|
||||
private function prepareCondition($cId, $cVal1, $cVal2, $cVal3) : array
|
||||
{
|
||||
if ($fn = self::$conditions[abs($cId)][self::IDX_CND_FN])
|
||||
if (!$this->$fn(abs($cId), $cVal1, $cVal2, $cVal3))
|
||||
return [];
|
||||
|
||||
$result = [$cId];
|
||||
|
||||
for ($i = 0; $i < 3; $i++)
|
||||
{
|
||||
$field = self::$conditions[abs($cId)][$i];
|
||||
|
||||
if (is_int($field))
|
||||
$this->jsGlobals[$field][${'cVal'.($i+1)}] = ${'cVal'.($i+1)};
|
||||
if ($field)
|
||||
$result[] = ${'cVal'.($i+1)}; // variable amount of condition values
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function factionToSide($cndId, &$cVal1, $cVal2, $cVal3) : bool
|
||||
{
|
||||
if ($cVal1 == 469)
|
||||
$cVal1 = SIDE_ALLIANCE;
|
||||
else if ($cVal1 == 67)
|
||||
$cVal1 = SIDE_HORDE;
|
||||
else
|
||||
$cVal1 = SIDE_BOTH;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function mapToZone($cndId, &$cVal1, &$cVal2, $cVal3) : bool
|
||||
{
|
||||
// use g_zone_categories id
|
||||
if ($cVal1 == 530) // outland
|
||||
$cVal1 = 8;
|
||||
else if ($cVal1 == 571) // northrend
|
||||
$cVal1 = 10;
|
||||
else if ($cVal1 == 0 || $cVal1 == 1) // eastern kingdoms / kalimdor
|
||||
; // cVal alrady correct - NOP
|
||||
else if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d AND `parentArea` = 0 AND (`cuFlags` & ?d) = 0', $cVal1, CUSTOM_EXCLUDE_FOR_LISTVIEW))
|
||||
{
|
||||
// remap for instanced area - do not use List (pointless overhead)
|
||||
$this->jsGlobals[Type::ZONE][$id] = $id;
|
||||
$cVal2 = $id;
|
||||
$cVal1 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('Conditions - CONDITION_MAPID has invalid mapId #'.$cVal1, E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function maskToBits($cndId, &$cVal1, $cVal2, $cVal3) : bool
|
||||
{
|
||||
if ($cndId == self::CHR_CLASS)
|
||||
{
|
||||
$cVal1 &= ChrClass::MASK_ALL;
|
||||
foreach (Util::mask2bits($cVal1, 1) as $cId)
|
||||
$this->jsGlobals[Type::CHR_CLASS][$cId] = $cId;
|
||||
}
|
||||
|
||||
if ($cndId == self::CHR_RACE)
|
||||
{
|
||||
$cVal1 &= ChrRace::MASK_ALL;
|
||||
foreach (Util::mask2bits($cVal1, 1) as $rId)
|
||||
$this->jsGlobals[Type::CHR_RACE][$rId] = $rId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function typeidToId($cndId, $cVal1, &$cVal2, &$cVal3) : bool
|
||||
{
|
||||
if ($cVal1 == self::TYPEID_UNIT)
|
||||
{
|
||||
if ($cVal3 && ($_ = DB::Aowow()->selectCell('SELECT `typeId` FROM ?_spawns WHERE `type` = ?d AND `guid` = ?d', Type::NPC, $cVal3)))
|
||||
$cVal2 = intVal($_);
|
||||
|
||||
if ($cVal2)
|
||||
$this->jsGlobals[Type::NPC][$cVal2] = $cVal2;
|
||||
}
|
||||
else if ($cVal1 == self::TYPEID_GAMEOBJECT)
|
||||
{
|
||||
if ($cVal3 && ($_ = DB::Aowow()->selectCell('SELECT `typeId` FROM ?_spawns WHERE `type` = ?d AND `guid` = ?d', Type::OBJECT, $cVal3)))
|
||||
$cVal2 = intVal($_);
|
||||
|
||||
if ($cVal2)
|
||||
$this->jsGlobals[Type::OBJECT][$cVal2] = $cVal2;
|
||||
}
|
||||
else // Player or Corpse .. no guid
|
||||
$cVal2 = $cVal3 = 0;
|
||||
|
||||
// maybe prepare other types?
|
||||
return true;
|
||||
}
|
||||
|
||||
private function lootIdToNpc(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::lootToNpc - skipping reference to creature_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($npcs = DB::Aowow()->selectCol('SELECT `id` FROM ?_creature WHERE `lootId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($npcs as $npcId)
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$npcId] = $npcId;
|
||||
$this->resultExtra[$sType][$group][] = $npcId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::lootToNpc - creature_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function disenchantIdToItem(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::disenchantIdToItem - skipping reference to disenchant_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($items = DB::Aowow()->selectCol('SELECT `id` FROM ?_items WHERE `disenchantId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($items as $itemId)
|
||||
{
|
||||
$this->jsGlobals[Type::ITEM][$itemId] = $itemId;
|
||||
$this->resultExtra[$sType][$group][] = $itemId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::disenchantIdToItem - disenchant_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function lootIdToGObject(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::lootIdToGObject - skipping reference to gameobject_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($gos = DB::Aowow()->selectCol('SELECT `id` FROM ?_objects WHERE `lootId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($gos as $goId)
|
||||
{
|
||||
$this->jsGlobals[Type::OBJECT][$goId] = $goId;
|
||||
$this->resultExtra[$sType][$group][] = $goId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::lootIdToGObject - gameobject_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function RewardTemplateToQuest(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::RewardTemplateToQuest - skipping reference to mail_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($quests = DB::Aowow()->selectCol('SELECT `id` FROM ?_quests WHERE `rewardMailTemplateId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($quests as $questId)
|
||||
{
|
||||
$this->jsGlobals[Type::QUEST][$questId] = $questId;
|
||||
$this->resultExtra[$sType][$group][] = $questId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::RewardTemplateToQuest - mail_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function PickpocketLootToNpc(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::PickpocketLootToNpc - skipping reference to pickpocketing_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($npcs = DB::Aowow()->selectCol('SELECT `id` FROM ?_creature WHERE `pickpocketLootId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($npcs as $npcId)
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$npcId] = $npcId;
|
||||
$this->resultExtra[$sType][$group][] = $npcId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::PickpocketLootToNpc - pickpocketing_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function SkinLootToNpc(int $sType, int $sGroup, int $sEntry, int $sId, int $cTarget) : bool
|
||||
{
|
||||
if (!$sGroup)
|
||||
{
|
||||
trigger_error('Conditions::SkinLootToNpc - skipping reference to skinning_loot_template entry 0', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($npcs = DB::Aowow()->selectCol('SELECT `id` FROM ?_creature WHERE `skinLootId` = ?d', $sGroup))
|
||||
{
|
||||
$group = $sGroup . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
foreach ($npcs as $npcId)
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$npcId] = $npcId;
|
||||
$this->resultExtra[$sType][$group][] = $npcId . ':' . $sEntry . ':' . $sId . ':' . $cTarget;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
trigger_error('Conditions::SkinLootToNpc - skinning_loot_template #'.$sGroup.' unreferenced?', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
756
includes/components/SmartAI/SmartAI.class.php
Normal file
756
includes/components/SmartAI/SmartAI.class.php
Normal file
@@ -0,0 +1,756 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
// TrinityCore - SmartAI
|
||||
|
||||
trait SmartHelper
|
||||
{
|
||||
private function resolveGuid(int $type, int $guid) : ?int
|
||||
{
|
||||
if ($_ = DB::Aowow()->selectCell('SELECT `typeId` FROM ?_spawns WHERE `type` = ?d AND `guid` = ?d', $type, $guid))
|
||||
return $_;
|
||||
|
||||
trigger_error('SmartAI::resolveGuid - failed to resolve guid '.$guid.' of type '.$type, E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
private function numRange(int $min, int $max, bool $isTime) : string
|
||||
{
|
||||
if (!$min && !$max)
|
||||
return '';
|
||||
|
||||
$str = $isTime ? Util::formatTime($min, true) : $min;
|
||||
if ($max > $min)
|
||||
$str .= ' – '.($isTime ? Util::formatTime($max, true) : $max);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function formatTime(int $time, int $_, bool $isMilliSec) : string
|
||||
{
|
||||
if (!$time)
|
||||
return '';
|
||||
|
||||
return Util::formatTime($time * ($isMilliSec ? 1 : 1000), false);
|
||||
}
|
||||
|
||||
private function castFlags(int $flags) : string
|
||||
{
|
||||
$cf = [];
|
||||
for ($i = 1; $i <= SmartAI::CAST_FLAG_COMBAT_MOVE; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::smartAI('castFlags', $i)))
|
||||
$cf[] = $x;
|
||||
|
||||
return Lang::concat($cf);
|
||||
}
|
||||
|
||||
private function npcFlags(int $flags) : string
|
||||
{
|
||||
$nf = [];
|
||||
for ($i = 1; $i <= NPC_FLAG_MAILBOX; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::npc('npcFlags', $i)))
|
||||
$nf[] = $x;
|
||||
|
||||
return Lang::concat($nf ?: [Lang::smartAI('empty')]);
|
||||
}
|
||||
|
||||
private function dynFlags(int $flags) : string
|
||||
{
|
||||
$df = [];
|
||||
for ($i = 1; $i <= UNIT_DYNFLAG_TAPPED_BY_ALL_THREAT_LIST; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::unit('dynFlags', $i)))
|
||||
$df[] = $x;
|
||||
|
||||
return Lang::concat($df ?: [Lang::smartAI('empty')]);
|
||||
}
|
||||
|
||||
private function goFlags(int $flags) : string
|
||||
{
|
||||
$gf = [];
|
||||
for ($i = 1; $i <= GO_FLAG_DESTROYED; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::gameObject('goFlags', $i)))
|
||||
$gf[] = $x;
|
||||
|
||||
return Lang::concat($gf ?: [Lang::smartAI('empty')]);
|
||||
}
|
||||
|
||||
private function spawnFlags(int $flags) : string
|
||||
{
|
||||
$sf = [];
|
||||
for ($i = 1; $i <= SmartAI::SPAWN_FLAG_NOSAVE_RESPAWN; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::smartAI('spawnFlags', $i)))
|
||||
$sf[] = $x;
|
||||
|
||||
return Lang::concat($sf ?: [Lang::smartAI('empty')]);
|
||||
}
|
||||
|
||||
private function unitFlags(int $flags, int $flags2) : string
|
||||
{
|
||||
$field = $flags2 ? 'flags2' : 'flags';
|
||||
$max = $flags2 ? UNIT_FLAG2_ALLOW_CHEAT_SPELLS : UNIT_FLAG_UNK_31;
|
||||
$uf = [];
|
||||
|
||||
for ($i = 1; $i <= $max; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::unit($field, $i)))
|
||||
$uf[] = $x;
|
||||
|
||||
return Lang::concat($uf ?: [Lang::smartAI('empty')]);
|
||||
}
|
||||
|
||||
private function unitFieldBytes1(int $flags, int $idx) : string
|
||||
{
|
||||
switch ($idx)
|
||||
{
|
||||
case 0:
|
||||
case 3:
|
||||
return Lang::unit('bytes1', 'bytesIdx', $idx).Lang::main('colon').(Lang::unit('bytes1', $idx, $flags) ?? Lang::unit('bytes1', 'valueUNK', [$flags, $idx]));
|
||||
case 2:
|
||||
$buff = [];
|
||||
for ($i = 1; $i <= 0x10; $i <<= 1)
|
||||
if (($flags & $i) && ($x = Lang::unit('bytes1', $idx, $flags)))
|
||||
$buff[] = $x;
|
||||
|
||||
return Lang::unit('bytes1', 'bytesIdx', $idx).Lang::main('colon').($buff ? Lang::concat($buff) : Lang::unit('bytes1', 'valueUNK', [$flags, $idx]));
|
||||
default:
|
||||
return Lang::unit('bytes1', 'idxUNK', [$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
private function summonType(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('summonTypes', $x) ?? Lang::smartAI('summonType', 'summonTypeUNK', [$x]);
|
||||
}
|
||||
|
||||
private function sheathState(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('sheaths', $x) ?? Lang::smartAI('sheathUNK', [$x]);
|
||||
}
|
||||
|
||||
private function aiTemplate(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('aiTpl', $x) ?? Lang::smartAI('aiTplUNK', [$x]);
|
||||
}
|
||||
|
||||
private function reactState(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('reactStates', $x) ?? Lang::smartAI('reactStateUNK', [$x]);
|
||||
}
|
||||
|
||||
private function powerType(int $x) : string
|
||||
{
|
||||
return Lang::spell('powerTypes', $x) ?? Lang::smartAI('powerTypeUNK', [$x]);
|
||||
}
|
||||
|
||||
private function hostilityMode(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('hostilityModes', $x) ?? Lang::smartAI('hostilityModeUNK', [$x]);
|
||||
}
|
||||
|
||||
private function motionType(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('motionTypes', $x) ?? Lang::smartAI('motionTypeUNK', [$x]);
|
||||
}
|
||||
|
||||
private function lootState(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('lootStates', $x) ?? Lang::smartAI('lootStateUNK', [$x]);
|
||||
}
|
||||
private function weatherState(int $x) : string
|
||||
{
|
||||
return Lang::smartAI('weatherStates', $x) ?? Lang::smartAI('weatherStateUNK', [$x]);
|
||||
}
|
||||
|
||||
private function magicSchool(int $x) : string
|
||||
{
|
||||
return Lang::getMagicSchools($x);
|
||||
}
|
||||
}
|
||||
|
||||
class SmartAI
|
||||
{
|
||||
public const SRC_TYPE_CREATURE = 0;
|
||||
public const SRC_TYPE_OBJECT = 1;
|
||||
public const SRC_TYPE_AREATRIGGER = 2;
|
||||
public const SRC_TYPE_ACTIONLIST = 9;
|
||||
|
||||
public const CAST_FLAG_INTERRUPT_PREV = 0x01; // Interrupt any spell casting
|
||||
public const CAST_FLAG_TRIGGERED = 0x02; // Triggered (this makes spell cost zero mana and have no cast time)
|
||||
// public const CAST_FORCE_CAST = 0x04; // Forces cast even if creature is out of mana or out of range
|
||||
// public const CAST_NO_MELEE_IF_OOM = 0x08; // Prevents creature from entering melee if out of mana or out of range
|
||||
// public const CAST_FORCE_TARGET_SELF = 0x10; // the target to cast this spell on itself
|
||||
public const CAST_FLAG_AURA_MISSING = 0x20; // Only casts the spell if the target does not have an aura from the spell
|
||||
public const CAST_FLAG_COMBAT_MOVE = 0x40; // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
|
||||
|
||||
public const REACT_PASSIVE = 0;
|
||||
public const REACT_DEFENSIVE = 1;
|
||||
public const REACT_AGGRESSIVE = 2;
|
||||
public const REACT_ASSIST = 3;
|
||||
|
||||
public const SUMMON_TIMED_OR_DEAD_DESPAWN = 1;
|
||||
public const SUMMON_TIMED_OR_CORPSE_DESPAWN = 2;
|
||||
public const SUMMON_TIMED_DESPAWN = 3;
|
||||
public const SUMMON_TIMED_DESPAWN_OOC = 4;
|
||||
public const SUMMON_CORPSE_DESPAWN = 5;
|
||||
public const SUMMON_CORPSE_TIMED_DESPAWN = 6;
|
||||
public const SUMMON_DEAD_DESPAWN = 7;
|
||||
public const SUMMON_MANUAL_DESPAWN = 8;
|
||||
|
||||
public const TEMPLATE_BASIC = 0; //
|
||||
public const TEMPLATE_CASTER = 1; // +JOIN: target_param1 as castFlag
|
||||
public const TEMPLATE_TURRET = 2; // +JOIN: target_param1 as castflag
|
||||
public const TEMPLATE_PASSIVE = 3; //
|
||||
public const TEMPLATE_CAGED_GO_PART = 4; //
|
||||
public const TEMPLATE_CAGED_NPC_PART = 5; //
|
||||
|
||||
public const SPAWN_FLAG_NONE = 0x00;
|
||||
public const SPAWN_FLAG_IGNORE_RESPAWN = 0x01; // onSpawnIn - ignore & reset respawn timer
|
||||
public const SPAWN_FLAG_FORCE_SPAWN = 0x02; // onSpawnIn - force additional spawn if already in world
|
||||
public const SPAWN_FLAG_NOSAVE_RESPAWN = 0x04; // onDespawn - remove respawn time
|
||||
|
||||
private array $jsGlobals = [];
|
||||
private array $rawData = [];
|
||||
private array $result = [];
|
||||
private array $tabs = [];
|
||||
private array $itr = [];
|
||||
|
||||
private array $quotes = [];
|
||||
|
||||
// misc data
|
||||
public readonly int $baseEntry; // I'm a timed action list belonging to this entry
|
||||
public readonly string $title; // title appendix for the [toggle]
|
||||
public readonly int $teleportTargetArea; // precalculated areaId so we don't have to look it up right now
|
||||
|
||||
public function __construct(public readonly int $srcType = 0, public readonly int $entry = 0, array $miscData = [])
|
||||
{
|
||||
$this->baseEntry = $miscData['baseEntry'] ?? 0;
|
||||
$this->title = $miscData['title'] ?? '';
|
||||
$this->teleportTargetArea = $miscData['teleportTargetArea'] ?? 0;
|
||||
|
||||
$raw = DB::World()->select(
|
||||
'SELECT `id`, `link`,
|
||||
`event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`,
|
||||
`action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`,
|
||||
`target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`
|
||||
FROM smart_scripts
|
||||
WHERE `entryorguid` = ?d AND `source_type` = ?d
|
||||
ORDER BY `id` ASC',
|
||||
$this->entry, $this->srcType);
|
||||
|
||||
foreach ($raw as $r)
|
||||
{
|
||||
$this->rawData[$r['id']] = array(
|
||||
'id' => $r['id'],
|
||||
'link' => $r['link'],
|
||||
'event' => new SmartEvent($r['id'], $r['event_type'], $r['event_phase_mask'], $r['event_chance'], $r['event_flags'], [$r['event_param1'], $r['event_param2'], $r['event_param3'], $r['event_param4'], $r['event_param5']], $this),
|
||||
'action' => new SmartAction($r['id'], $r['action_type'], [$r['action_param1'], $r['action_param2'], $r['action_param3'], $r['action_param4'], $r['action_param5'], $r['action_param6']], $this),
|
||||
'target' => new SmartTarget($r['id'], $r['target_type'], [$r['target_param1'], $r['target_param2'], $r['target_param3'], $r['target_param4']], [$r['target_x'], $r['target_y'], $r['target_z'], $r['target_o']], $this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* Lookups by action */
|
||||
/*********************/
|
||||
|
||||
public static function getOwnerOfNPCSummon(int $npcId, int $typeFilter = 0) : array
|
||||
{
|
||||
if ($npcId <= 0)
|
||||
return [];
|
||||
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SUMMON_CREATURE => [1 => $npcId],
|
||||
SmartAction::ACTION_MOUNT_TO_ENTRY_OR_MODEL => [1 => $npcId]
|
||||
);
|
||||
|
||||
if ($npcGuids = DB::Aowow()->selectCol('SELECT `guid` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::NPC, $npcId))
|
||||
if ($groups = DB::World()->selectCol('SELECT `groupId` FROM spawn_group WHERE `spawnType` = 0 AND `spawnId` IN (?a)', $npcGuids))
|
||||
foreach ($groups as $g)
|
||||
$lookup[SmartAction::ACTION_SPAWN_SPAWNGROUP][1] = $g;
|
||||
|
||||
$result = self::getActionOwner($lookup, $typeFilter);
|
||||
|
||||
// can skip lookups for SmartAction::ACTION_SUMMON_CREATURE_GROUP as creature_summon_groups already contains summoner info
|
||||
if ($sgs = DB::World()->select('SELECT `summonerType` AS "0", `summonerId` AS "1" FROM creature_summon_groups WHERE `entry` = ?d', $npcId))
|
||||
foreach ($sgs as [$type, $typeId])
|
||||
$result[$type][] = $typeId;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getOwnerOfObjectSummon(int $objectId, int $typeFilter = 0) : array
|
||||
{
|
||||
if ($objectId <= 0)
|
||||
return [];
|
||||
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SUMMON_GO => [1 => $objectId]
|
||||
);
|
||||
|
||||
if ($objGuids = DB::Aowow()->selectCol('SELECT `guid` FROM ?_spawns WHERE `type` = ?d AND `typeId` = ?d', Type::OBJECT, $objectId))
|
||||
if ($groups = DB::World()->selectCol('SELECT `groupId` FROM spawn_group WHERE `spawnType` = 1 AND `spawnId` IN (?a)', $objGuids))
|
||||
foreach ($groups as $g)
|
||||
$lookup[SmartAction::ACTION_SPAWN_SPAWNGROUP][1] = $g;
|
||||
|
||||
return self::getActionOwner($lookup, $typeFilter);
|
||||
}
|
||||
|
||||
public static function getOwnerOfSpellCast(int $spellId, int $typeFilter = 0) : array
|
||||
{
|
||||
if ($spellId <= 0)
|
||||
return [];
|
||||
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_CAST => [1 => $spellId],
|
||||
SmartAction::ACTION_ADD_AURA => [1 => $spellId],
|
||||
SmartAction::ACTION_SELF_CAST => [1 => $spellId],
|
||||
SmartAction::ACTION_CROSS_CAST => [1 => $spellId],
|
||||
SmartAction::ACTION_INVOKER_CAST => [1 => $spellId]
|
||||
);
|
||||
|
||||
return self::getActionOwner($lookup, $typeFilter);
|
||||
}
|
||||
|
||||
public static function getOwnerOfSoundPlayed(int $soundId, int $typeFilter = 0) : array
|
||||
{
|
||||
if ($soundId <= 0)
|
||||
return [];
|
||||
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SOUND => [1 => $soundId]
|
||||
);
|
||||
|
||||
return self::getActionOwner($lookup, $typeFilter);
|
||||
}
|
||||
|
||||
// lookup: SmartActionId => [[paramIdx => value], ...]
|
||||
private static function getActionOwner(array $lookup, int $typeFilter = 0) : array
|
||||
{
|
||||
$qParts = [];
|
||||
$result = [];
|
||||
$genFilter = $talFilter = [];
|
||||
switch ($typeFilter)
|
||||
{
|
||||
case Type::NPC:
|
||||
$genFilter = [self::SRC_TYPE_CREATURE, self::SRC_TYPE_ACTIONLIST];
|
||||
$talFilter = [self::SRC_TYPE_CREATURE];
|
||||
break;
|
||||
case Type::OBJECT:
|
||||
$genFilter = [self::SRC_TYPE_OBJECT, self::SRC_TYPE_ACTIONLIST];
|
||||
$talFilter = [self::SRC_TYPE_OBJECT];
|
||||
break;
|
||||
case Type::AREATRIGGER:
|
||||
$genFilter = [self::SRC_TYPE_AREATRIGGER, self::SRC_TYPE_ACTIONLIST];
|
||||
$talFilter = [self::SRC_TYPE_AREATRIGGER];
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($lookup as $action => $params)
|
||||
{
|
||||
$aq = '(`action_type` = '.(int)$action.' AND (';
|
||||
$pq = [];
|
||||
foreach ($params as $idx => $p)
|
||||
$pq[] = '`action_param'.(int)$idx.'` = '.(int)$p;
|
||||
|
||||
if ($pq)
|
||||
$qParts[] = $aq.implode(' OR ', $pq).'))';
|
||||
}
|
||||
|
||||
$smartS = DB::World()->select(sprintf('SELECT `source_type` AS "0", `entryOrGUID` AS "1" FROM smart_scripts WHERE (%s){ AND `source_type` IN (?a)}', $qParts ? implode(' OR ', $qParts) : '0'), $genFilter ?: DBSIMPLE_SKIP);
|
||||
|
||||
// filter for TAL shenanigans
|
||||
if ($smartTAL = array_filter($smartS, fn($x) => $x[0] == self::SRC_TYPE_ACTIONLIST))
|
||||
{
|
||||
$smartS = array_diff_key($smartS, $smartTAL);
|
||||
|
||||
$q = [];
|
||||
foreach ($smartTAL as [, $eog])
|
||||
{
|
||||
// SmartAction::ACTION_CALL_TIMED_ACTIONLIST
|
||||
$q[] = '`action_type` = '.SmartAction::ACTION_CALL_TIMED_ACTIONLIST.' AND `action_param1` = '.$eog;
|
||||
|
||||
// SmartAction::ACTION_CALL_RANDOM_TIMED_ACTIONLIST
|
||||
$q[] = '`action_type` = '.SmartAction::ACTION_CALL_RANDOM_TIMED_ACTIONLIST.' AND (`action_param1` = '.$eog.' OR `action_param2` = '.$eog.' OR `action_param3` = '.$eog.' OR `action_param4` = '.$eog.' OR `action_param5` = '.$eog.')';
|
||||
|
||||
// SmartAction::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST
|
||||
$q[] = '`action_type` = '.SmartAction::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST.' AND `action_param1` <= '.$eog.' AND `action_param2` >= '.$eog;
|
||||
}
|
||||
|
||||
if ($_ = DB::World()->select(sprintf('SELECT `source_type` AS "0", `entryOrGUID` AS "1" FROM smart_scripts WHERE ((%s)){ AND `source_type` IN (?a)}', $q ? implode(') OR (', $q) : '0'), $talFilter ?: DBSIMPLE_SKIP))
|
||||
$smartS = array_merge($smartS, $_);
|
||||
}
|
||||
|
||||
// filter guids for entries
|
||||
if ($smartG = array_filter($smartS, fn($x) => $x[1] < 0))
|
||||
{
|
||||
$smartS = array_diff_key($smartS, $smartG);
|
||||
|
||||
$q = [];
|
||||
foreach ($smartG as [$st, $eog])
|
||||
{
|
||||
if ($st == self::SRC_TYPE_CREATURE)
|
||||
$q[] = '`type` = '.Type::NPC.' AND `guid` = '.-$eog;
|
||||
else if ($st == self::SRC_TYPE_OBJECT)
|
||||
$q[] = '`type` = '.Type::OBJECT.' AND `guid` = '.-$eog;
|
||||
}
|
||||
|
||||
if ($q)
|
||||
{
|
||||
$owner = DB::Aowow()->select(sprintf('SELECT `type`, `typeId` FROM ?_spawns WHERE (%s)', implode(') OR (', $q)));
|
||||
foreach ($owner as $o)
|
||||
$result[$o['type']][] = $o['typeId'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($smartS as [$st, $eog])
|
||||
{
|
||||
if ($st == self::SRC_TYPE_CREATURE)
|
||||
$result[Type::NPC][] = $eog;
|
||||
else if ($st == self::SRC_TYPE_OBJECT)
|
||||
$result[Type::OBJECT][] = $eog;
|
||||
else if ($st == self::SRC_TYPE_AREATRIGGER)
|
||||
$result[Type::AREATRIGGER][] = $eog;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/********************/
|
||||
/* Lookups by owner */
|
||||
/********************/
|
||||
|
||||
public static function getNPCSummonsForOwner(int $entry, int $srcType = self::SRC_TYPE_CREATURE) : array
|
||||
{
|
||||
// action => paramIdx with npcIds/spawnGoupIds
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SUMMON_CREATURE => [1],
|
||||
SmartAction::ACTION_MOUNT_TO_ENTRY_OR_MODEL => [1],
|
||||
SmartAction::ACTION_SPAWN_SPAWNGROUP => [1]
|
||||
);
|
||||
|
||||
$result = self::getOwnerAction($srcType, $entry, $lookup, $moreInfo);
|
||||
|
||||
// can skip lookups for SmartAction::ACTION_SUMMON_CREATURE_GROUP as creature_summon_groups already contains summoner info
|
||||
if ($srcType == self::SRC_TYPE_CREATURE || $srcType == self::SRC_TYPE_OBJECT)
|
||||
{
|
||||
$st = $srcType == self::SRC_TYPE_CREATURE ? SUMMONER_TYPE_CREATURE : SUMMONER_TYPE_GAMEOBJECT;
|
||||
if ($csg = DB::World()->selectCol('SELECT `entry` FROM creature_summon_groups WHERE `summonerType` = ?d AND `summonerId` = ?d', $st, $entry))
|
||||
$result = array_merge($result, $csg);
|
||||
}
|
||||
|
||||
if (!empty($moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP]))
|
||||
{
|
||||
$grp = $moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP];
|
||||
if ($sgs = DB::World()->selectCol('SELECT `spawnId` FROM spawn_group WHERE `spawnType` = ?d AND `groupId` IN (?a)', SUMMONER_TYPE_CREATURE, $grp))
|
||||
if ($ids = DB::Aowow()->selectCol('SELECT DISTINCT `typeId` FROM ?_spawns WHERE `type` = ?d AND `guid` IN (?a)', Type::NPC, $sgs))
|
||||
$result = array_merge($result, $ids);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getObjectSummonsForOwner(int $entry, int $srcType = self::SRC_TYPE_CREATURE) : array
|
||||
{
|
||||
// action => paramIdx with gobIds/spawnGoupIds
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SUMMON_GO => [1],
|
||||
SmartAction::ACTION_SPAWN_SPAWNGROUP => [1]
|
||||
);
|
||||
|
||||
$result = self::getOwnerAction($srcType, $entry, $lookup, $moreInfo);
|
||||
|
||||
if (!empty($moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP]))
|
||||
{
|
||||
$grp = $moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP];
|
||||
if ($sgs = DB::World()->selectCol('SELECT `spawnId` FROM spawn_group WHERE `spawnType` = ?d AND `groupId` IN (?a)', SUMMONER_TYPE_GAMEOBJECT, $grp))
|
||||
if ($ids = DB::Aowow()->selectCol('SELECT DISTINCT `typeId` FROM ?_spawns WHERE `type` = ?d AND `guid` IN (?a)', Type::OBJECT, $sgs))
|
||||
$result = array_merge($result, $ids);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getSpellCastsForOwner(int $entry, int $srcType = self::SRC_TYPE_CREATURE) : array
|
||||
{
|
||||
// action => paramIdx with spellIds
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_CAST => [1],
|
||||
SmartAction::ACTION_ADD_AURA => [1],
|
||||
SmartAction::ACTION_INVOKER_CAST => [1],
|
||||
SmartAction::ACTION_CROSS_CAST => [1]
|
||||
);
|
||||
|
||||
return self::getOwnerAction($srcType, $entry, $lookup);
|
||||
}
|
||||
|
||||
public static function getSoundsPlayedForOwner(int $entry, int $srcType = self::SRC_TYPE_CREATURE) : array
|
||||
{
|
||||
// action => paramIdx with soundIds
|
||||
$lookup = array(
|
||||
SmartAction::ACTION_SOUND => [1]
|
||||
);
|
||||
|
||||
return self::getOwnerAction($srcType, $entry, $lookup);
|
||||
}
|
||||
|
||||
// lookup: [SmartActionId => [paramIdx, ...], ...]
|
||||
private static function getOwnerAction(int $sourceType, int $entry, array $lookup, ?array &$moreInfo = []) : array
|
||||
{
|
||||
if ($entry < 0) // no lookup by GUID
|
||||
return [];
|
||||
|
||||
$actionQuery = 'SELECT `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6` FROM smart_scripts WHERE `source_type` = ?d AND `action_type` IN (?a) AND `entryOrGUID` IN (?a)';
|
||||
|
||||
$smartScripts = DB::World()->select($actionQuery, $sourceType, array_merge(array_keys($lookup), SmartAction::ACTION_ALL_TIMED_ACTION_LISTS), [$entry]);
|
||||
$smartResults = [];
|
||||
$smartTALs = [];
|
||||
foreach ($smartScripts as $s)
|
||||
{
|
||||
if ($s['action_type'] == SmartAction::ACTION_SPAWN_SPAWNGROUP)
|
||||
$moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP][] = $s['action_param1'];
|
||||
else if (in_array($s['action_type'], array_keys($lookup)))
|
||||
{
|
||||
foreach ($lookup[$s['action_type']] as $p)
|
||||
$smartResults[] = $s['action_param'.$p];
|
||||
}
|
||||
else if ($s['action_type'] == SmartAction::ACTION_CALL_TIMED_ACTIONLIST)
|
||||
$smartTALs[] = $s['action_param1'];
|
||||
else if ($s['action_type'] == SmartAction::ACTION_CALL_RANDOM_TIMED_ACTIONLIST)
|
||||
{
|
||||
for ($i = 1; $i < 7; $i++)
|
||||
if ($s['action_param'.$i])
|
||||
$smartTALs[] = $s['action_param'.$i];
|
||||
}
|
||||
else if ($s['action_type'] == SmartAction::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST)
|
||||
{
|
||||
for ($i = $s['action_param1']; $i <= $s['action_param2']; $i++)
|
||||
$smartTALs[] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
if ($smartTALs)
|
||||
{
|
||||
if ($TALActList = DB::World()->select($actionQuery, self::SRC_TYPE_ACTIONLIST, array_keys($lookup), $smartTALs))
|
||||
{
|
||||
foreach ($TALActList as $e)
|
||||
{
|
||||
foreach ($lookup[$e['action_type']] as $i)
|
||||
{
|
||||
if ($e['action_type'] == SmartAction::ACTION_SPAWN_SPAWNGROUP)
|
||||
$moreInfo[SmartAction::ACTION_SPAWN_SPAWNGROUP][] = $e['action_param'.$i];
|
||||
else
|
||||
$smartResults[] = $e['action_param'.$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $smartResults;
|
||||
}
|
||||
|
||||
|
||||
/******************************/
|
||||
/* Structured Lisview Display */
|
||||
/******************************/
|
||||
|
||||
private function &iterate() : \Generator
|
||||
{
|
||||
reset($this->rawData);
|
||||
|
||||
foreach ($this->rawData as $k => $__)
|
||||
{
|
||||
$this->itr = &$this->rawData[$k];
|
||||
|
||||
yield $this->itr;
|
||||
}
|
||||
}
|
||||
|
||||
public function prepare() : bool
|
||||
{
|
||||
if (!$this->rawData)
|
||||
return false;
|
||||
|
||||
if ($this->result)
|
||||
return true;
|
||||
|
||||
$hidePhase =
|
||||
$hideChance = true;
|
||||
|
||||
foreach ($this->iterate() as $id => $__)
|
||||
{
|
||||
$rowIdx = Util::createHash(8);
|
||||
|
||||
if ($this->itr['action']->type == SmartAction::ACTION_TALK || $this->itr['action']->type == SmartAction::ACTION_SIMPLE_TALK)
|
||||
if ($ts = $this->itr['target']->getTalkSource())
|
||||
$this->initQuotes($ts);
|
||||
|
||||
[$evtBody, $evtFooter] = $this->itr['event']->process();
|
||||
[$actBody, $actFooter] = $this->itr['action']->process();
|
||||
|
||||
$evtBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $evtBody);
|
||||
$actBody = str_replace(['#target#', '#rowIdx#'], [$this->itr['target']->process(), $rowIdx], $actBody);
|
||||
|
||||
if (!$this->itr['event']->hasPhases())
|
||||
$hidePhase = false;
|
||||
|
||||
if ($this->itr['event']->chance != 100)
|
||||
$hideChance = false;
|
||||
|
||||
$this->result[] = array(
|
||||
$this->itr['id'],
|
||||
implode(', ', Util::mask2bits($this->itr['event']->phaseMask, 1)),
|
||||
$evtBody.($evtFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$evtFooter.'[/small][/i][/div]' : null),
|
||||
$this->itr['event']->chance.'%',
|
||||
$actBody.($actFooter ? '[div float=right margin=0px clear=both][i][small class=q0]'.$actFooter.'[/small][/i][/div]' : null)
|
||||
);
|
||||
}
|
||||
|
||||
$th = array(
|
||||
'#' => 16,
|
||||
'Phase' => 32,
|
||||
'Event' => 350,
|
||||
'Chance' => 24,
|
||||
'Action' => 0
|
||||
);
|
||||
|
||||
if ($hidePhase)
|
||||
{
|
||||
unset($th['Phase']);
|
||||
foreach ($this->result as &$r)
|
||||
unset($r[1]);
|
||||
}
|
||||
unset($r);
|
||||
|
||||
if ($hideChance)
|
||||
{
|
||||
unset($th['Chance']);
|
||||
foreach ($this->result as &$r)
|
||||
unset($r[3]);
|
||||
}
|
||||
unset($r);
|
||||
|
||||
$tbl = '[tr]';
|
||||
foreach ($th as $n => $w)
|
||||
$tbl .= '[td header '.($w ? 'width='.$w.'px' : null).']'.$n.'[/td]';
|
||||
$tbl .= '[/tr]';
|
||||
|
||||
foreach ($this->result as $r)
|
||||
$tbl .= '[tr][td]'.implode('[/td][td]', $r).'[/td][/tr]';
|
||||
|
||||
if ($this->srcType == self::SRC_TYPE_ACTIONLIST)
|
||||
$this->tabs[$this->entry] = $tbl;
|
||||
else
|
||||
$this->tabs[0] = $tbl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getMarkdown() : string
|
||||
{
|
||||
# id | event (footer phase) | chance | action + target
|
||||
|
||||
if (!$this->rawData)
|
||||
return '';
|
||||
|
||||
$return = '[style]#text-generic .grid { clear:left; } #text-generic .tabbed-contents { padding:0px; clear:left; }[/style][pad][h3][toggler id=sai]SmartAI'.$this->title.'[/toggler][/h3][div id=sai clear=left]%s[/div]';
|
||||
if (count($this->tabs) > 1)
|
||||
{
|
||||
$wrapper = '[tabs name=sai width=942px]%s[/tabs]';
|
||||
$return = '[script]function TalTabClick(id) { $(\'#dsf67g4d-sai\').find(\\\'[href=\\\\\'#sai-actionlist-\' + id + \'\\\\\']\\\').click(); }[/script]' . $return;
|
||||
$tabs = '';
|
||||
foreach ($this->tabs as $guid => $data)
|
||||
{
|
||||
$buff = '[tab name=\"'.($guid ? 'ActionList #'.$guid : 'Main').'\"][table class=grid width=940px]'.$data.'[/table][/tab]';
|
||||
if ($guid)
|
||||
$tabs .= $buff;
|
||||
else
|
||||
$tabs = $buff . $tabs;
|
||||
}
|
||||
|
||||
return sprintf($return, sprintf($wrapper, $tabs));
|
||||
}
|
||||
else
|
||||
return sprintf($return, '[table class=grid width=940px]'.$this->tabs[0].'[/table]');
|
||||
}
|
||||
|
||||
public function addJsGlobals(array $jsg) : void
|
||||
{
|
||||
Util::mergeJsGlobals($this->jsGlobals, $jsg);
|
||||
}
|
||||
|
||||
public function getJSGlobals() : array
|
||||
{
|
||||
return $this->jsGlobals;
|
||||
}
|
||||
|
||||
public function getTabs() : array
|
||||
{
|
||||
return $this->tabs;
|
||||
}
|
||||
|
||||
public function addTab(int $guid, string $tt) : void
|
||||
{
|
||||
$this->tabs[$guid] = $tt;
|
||||
}
|
||||
|
||||
public function getTarget(int $id = -1) : ?SmartTarget
|
||||
{
|
||||
if ($id < 0)
|
||||
return $this->itr['target'];
|
||||
|
||||
return $this->rawData[$id]['target'] ?? null;
|
||||
}
|
||||
|
||||
public function getAction(int $id = -1) : ?SmartAction
|
||||
{
|
||||
if ($id < 0)
|
||||
return $this->itr['action'];
|
||||
|
||||
return $this->rawData[$id]['action'] ?? null;
|
||||
}
|
||||
|
||||
public function getEvent(int $id = -1) : ?SmartEvent
|
||||
{
|
||||
if ($id < 0)
|
||||
return $this->itr['event'];
|
||||
|
||||
return $this->rawData[$id]['event'] ?? null;
|
||||
}
|
||||
|
||||
public function getEntry() : int
|
||||
{
|
||||
return $this->baseEntry ?: $this->entry;
|
||||
}
|
||||
|
||||
private function initQuotes(int $creatureId) : void
|
||||
{
|
||||
if (isset($this->quotes[$creatureId]))
|
||||
return;
|
||||
|
||||
[$quotes, , ] = Game::getQuotesForCreature($creatureId);
|
||||
|
||||
$this->quotes[$creatureId] = $quotes;
|
||||
|
||||
if (!empty($this->quotes[$creatureId]))
|
||||
$this->quotes[$creatureId]['src'] = CreatureList::getName($creatureId);
|
||||
}
|
||||
|
||||
public function getQuote(int $creatureId, int $group, ?string &$npcSrc) : array
|
||||
{
|
||||
if (isset($this->quotes[$creatureId][$group]))
|
||||
{
|
||||
$npcSrc = $this->quotes[$creatureId]['src'];
|
||||
return $this->quotes[$creatureId][$group];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
748
includes/components/SmartAI/SmartAction.class.php
Normal file
748
includes/components/SmartAI/SmartAction.class.php
Normal file
@@ -0,0 +1,748 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
// TrinityCore - SmartAI
|
||||
class SmartAction
|
||||
{
|
||||
use SmartHelper;
|
||||
|
||||
public const ACTION_NONE = 0; // Do nothing
|
||||
public const ACTION_TALK = 1; // Param2 in Milliseconds.
|
||||
public const ACTION_SET_FACTION = 2; // Sets faction to creature.
|
||||
public const ACTION_MORPH_TO_ENTRY_OR_MODEL = 3; // Take DisplayID of creature (param1) OR Turn to DisplayID (param2) OR Both = 0 for Demorph
|
||||
public const ACTION_SOUND = 4; // TextRange = 0 only sends sound to self, TextRange = 1 sends sound to everyone in visibility range
|
||||
public const ACTION_PLAY_EMOTE = 5; // Play Emote
|
||||
public const ACTION_FAIL_QUEST = 6; // Fail Quest of Target
|
||||
public const ACTION_OFFER_QUEST = 7; // Add Quest to Target
|
||||
public const ACTION_SET_REACT_STATE = 8; // React State. Can be Passive (0), Defensive (1), Aggressive (2), Assist (3).
|
||||
public const ACTION_ACTIVATE_GOBJECT = 9; // Activate Object
|
||||
public const ACTION_RANDOM_EMOTE = 10; // Play Random Emote
|
||||
public const ACTION_CAST = 11; // Cast Spell ID at Target
|
||||
public const ACTION_SUMMON_CREATURE = 12; // Summon Unit
|
||||
public const ACTION_THREAT_SINGLE_PCT = 13; // Change Threat Percentage for Single Target
|
||||
public const ACTION_THREAT_ALL_PCT = 14; // Change Threat Percentage for All Enemies
|
||||
public const ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS = 15; //
|
||||
public const ACTION_SET_INGAME_PHASE_ID = 16; // [RESERVED] For 4.3.4 + only
|
||||
public const ACTION_SET_EMOTE_STATE = 17; // Play Emote Continuously
|
||||
public const ACTION_SET_UNIT_FLAG = 18; // [DEPRECATED] Can set Multi-able flags at once
|
||||
public const ACTION_REMOVE_UNIT_FLAG = 19; // [DEPRECATED] Can Remove Multi-able flags at once
|
||||
public const ACTION_AUTO_ATTACK = 20; // Stop or Continue Automatic Attack.
|
||||
public const ACTION_ALLOW_COMBAT_MOVEMENT = 21; // Allow or Disable Combat Movement
|
||||
public const ACTION_SET_EVENT_PHASE = 22; //
|
||||
public const ACTION_INC_EVENT_PHASE = 23; // Set param1 OR param2 (not both). Value 0 has no effect.
|
||||
public const ACTION_EVADE = 24; // Evade Incoming Attack
|
||||
public const ACTION_FLEE_FOR_ASSIST = 25; // If you want the fleeing NPC to say '%s attempts to run away in fear' on flee, use 1 on param1. 0 for no message.
|
||||
public const ACTION_CALL_GROUPEVENTHAPPENS = 26; //
|
||||
public const ACTION_COMBAT_STOP = 27; //
|
||||
public const ACTION_REMOVEAURASFROMSPELL = 28; // 0 removes all auras
|
||||
public const ACTION_FOLLOW = 29; // Follow Target
|
||||
public const ACTION_RANDOM_PHASE = 30; //
|
||||
public const ACTION_RANDOM_PHASE_RANGE = 31; //
|
||||
public const ACTION_RESET_GOBJECT = 32; // Reset Gameobject
|
||||
public const ACTION_CALL_KILLEDMONSTER = 33; // This is the ID from quest_template.RequiredNpcOrGo
|
||||
public const ACTION_SET_INST_DATA = 34; // Set Instance Data
|
||||
public const ACTION_SET_INST_DATA64 = 35; // Set Instance Data uint64
|
||||
public const ACTION_UPDATE_TEMPLATE = 36; // Updates creature_template to given entry
|
||||
public const ACTION_DIE = 37; // Kill Target
|
||||
public const ACTION_SET_IN_COMBAT_WITH_ZONE = 38; //
|
||||
public const ACTION_CALL_FOR_HELP = 39; // If you want the NPC to say '%s calls for help!'. Use 1 on param1, 0 for no message.
|
||||
public const ACTION_SET_SHEATH = 40; //
|
||||
public const ACTION_FORCE_DESPAWN = 41; // Despawn Target after param1 in Milliseconds. If you want to set respawn time set param2 in seconds.
|
||||
public const ACTION_SET_INVINCIBILITY_HP_LEVEL = 42; // If you use both params, only percent will be used.
|
||||
public const ACTION_MOUNT_TO_ENTRY_OR_MODEL = 43; // Mount to Creature Entry (param1) OR Mount to Creature Display (param2) Or both = 0 for Unmount
|
||||
public const ACTION_SET_INGAME_PHASE_MASK = 44; //
|
||||
public const ACTION_SET_DATA = 45; // Set Data For Target, can be used with SMART_EVENT_DATA_SET
|
||||
public const ACTION_ATTACK_STOP = 46; //
|
||||
public const ACTION_SET_VISIBILITY = 47; // Makes creature Visible = 1 or Invisible = 0
|
||||
public const ACTION_SET_ACTIVE = 48; //
|
||||
public const ACTION_ATTACK_START = 49; // Allows basic melee swings to creature.
|
||||
public const ACTION_SUMMON_GO = 50; // Spawns Gameobject, use target_type to set spawn position.
|
||||
public const ACTION_KILL_UNIT = 51; // Kills Creature.
|
||||
public const ACTION_ACTIVATE_TAXI = 52; // Sends player to flight path. You have to be close to Flight Master, which gives Taxi ID you need.
|
||||
public const ACTION_WP_START = 53; // Creature starts Waypoint Movement. Use waypoints table to create movement.
|
||||
public const ACTION_WP_PAUSE = 54; // Creature pauses its Waypoint Movement for given time.
|
||||
public const ACTION_WP_STOP = 55; // Creature stops its Waypoint Movement.
|
||||
public const ACTION_ADD_ITEM = 56; // Adds item(s) to player.
|
||||
public const ACTION_REMOVE_ITEM = 57; // Removes item(s) from player.
|
||||
public const ACTION_INSTALL_AI_TEMPLATE = 58; // [DEPRECATED]
|
||||
public const ACTION_SET_RUN = 59; //
|
||||
public const ACTION_SET_DISABLE_GRAVITY = 60; // Only works for creatures with inhabit air.
|
||||
public const ACTION_SET_SWIM = 61; // [DEPRECATED]
|
||||
public const ACTION_TELEPORT = 62; // Continue this action with the TARGET_TYPE column. Use any target_type (except 0), and use target_x, target_y, target_z, target_o as the coordinates
|
||||
public const ACTION_SET_COUNTER = 63; //
|
||||
public const ACTION_STORE_TARGET_LIST = 64; //
|
||||
public const ACTION_WP_RESUME = 65; // Creature continues in its Waypoint Movement.
|
||||
public const ACTION_SET_ORIENTATION = 66; //
|
||||
public const ACTION_CREATE_TIMED_EVENT = 67; //
|
||||
public const ACTION_PLAYMOVIE = 68; //
|
||||
public const ACTION_MOVE_TO_POS = 69; // PointId is called by SMART_EVENT_MOVEMENTINFORM. Continue this action with the TARGET_TYPE column. Use any target_type, and use target_x, target_y, target_z, target_o as the coordinates
|
||||
public const ACTION_ENABLE_TEMP_GOBJ = 70; // param1 = duration
|
||||
public const ACTION_EQUIP = 71; // only slots with mask set will be sent to client, bits are 1, 2, 4, leaving mask 0 is defaulted to mask 7 (send all), Slots1-3 are only used if no Param1 is set
|
||||
public const ACTION_CLOSE_GOSSIP = 72; // Closes gossip window.
|
||||
public const ACTION_TRIGGER_TIMED_EVENT = 73; //
|
||||
public const ACTION_REMOVE_TIMED_EVENT = 74; //
|
||||
public const ACTION_ADD_AURA = 75; // [DEPRECATED] Adds aura to player(s). Use target_type 17 to make AoE aura.
|
||||
public const ACTION_OVERRIDE_SCRIPT_BASE_OBJECT = 76; // [DEPRECATED] WARNING: CAN CRASH CORE, do not use if you dont know what you are doing
|
||||
public const ACTION_RESET_SCRIPT_BASE_OBJECT = 77; // [DEPRECATED]
|
||||
public const ACTION_CALL_SCRIPT_RESET = 78; //
|
||||
public const ACTION_SET_RANGED_MOVEMENT = 79; // Sets movement to follow at a specific range to the target.
|
||||
public const ACTION_CALL_TIMED_ACTIONLIST = 80; //
|
||||
public const ACTION_SET_NPC_FLAG = 81; //
|
||||
public const ACTION_ADD_NPC_FLAG = 82; //
|
||||
public const ACTION_REMOVE_NPC_FLAG = 83; //
|
||||
public const ACTION_SIMPLE_TALK = 84; // Makes a player say text. SMART_EVENT_TEXT_OVER is not triggered and whispers can not be used.
|
||||
public const ACTION_SELF_CAST = 85; // spellID, castFlags
|
||||
public const ACTION_CROSS_CAST = 86; // This action is used to make selected caster (in CasterTargetType) to cast spell. Actual target is entered in target_type as normally.
|
||||
public const ACTION_CALL_RANDOM_TIMED_ACTIONLIST = 87; // Will select one entry from the ones provided. 0 is ignored.
|
||||
public const ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST = 88; // 0 is ignored.
|
||||
public const ACTION_RANDOM_MOVE = 89; // Creature moves to random position in given radius.
|
||||
public const ACTION_SET_UNIT_FIELD_BYTES_1 = 90; //
|
||||
public const ACTION_REMOVE_UNIT_FIELD_BYTES_1 = 91; //
|
||||
public const ACTION_INTERRUPT_SPELL = 92; // This action allows you to interrupt the current spell being cast. If you do not set the spellId, the core will find the current spell depending on the withDelay and the withInstant values.
|
||||
public const ACTION_SEND_GO_CUSTOM_ANIM = 93; // [DEPRECATED] oldFlag = newFlag
|
||||
public const ACTION_SET_DYNAMIC_FLAG = 94; // [DEPRECATED] oldFlag |= newFlag
|
||||
public const ACTION_ADD_DYNAMIC_FLAG = 95; // [DEPRECATED] oldFlag &= ~newFlag
|
||||
public const ACTION_REMOVE_DYNAMIC_FLAG = 96; // [DEPRECATED]
|
||||
public const ACTION_JUMP_TO_POS = 97; //
|
||||
public const ACTION_SEND_GOSSIP_MENU = 98; // Can be used together with 'SMART_EVENT_GOSSIP_HELLO' to set custom gossip.
|
||||
public const ACTION_GO_SET_LOOT_STATE = 99; //
|
||||
public const ACTION_SEND_TARGET_TO_TARGET = 100; // Send targets previously stored with SMART_ACTION_STORE_TARGET, to another npc/go, the other npc/go can then access them as if it was its own stored list
|
||||
public const ACTION_SET_HOME_POS = 101; // Use with SMART_TARGET_SELF or SMART_TARGET_POSITION
|
||||
public const ACTION_SET_HEALTH_REGEN = 102; // Sets the current creatures health regen on or off.
|
||||
public const ACTION_SET_ROOT = 103; // Enables or disables creature movement
|
||||
public const ACTION_SET_GO_FLAG = 104; // [DEPRECATED] oldFlag = newFlag
|
||||
public const ACTION_ADD_GO_FLAG = 105; // [DEPRECATED] oldFlag |= newFlag
|
||||
public const ACTION_REMOVE_GO_FLAG = 106; // [DEPRECATED] oldFlag &= ~newFlag
|
||||
public const ACTION_SUMMON_CREATURE_GROUP = 107; // Use creature_summon_groups table. SAI target has no effect, use 0
|
||||
public const ACTION_SET_POWER = 108; //
|
||||
public const ACTION_ADD_POWER = 109; //
|
||||
public const ACTION_REMOVE_POWER = 110; //
|
||||
public const ACTION_GAME_EVENT_STOP = 111; //
|
||||
public const ACTION_GAME_EVENT_START = 112; //
|
||||
public const ACTION_START_CLOSEST_WAYPOINT = 113; // Make target follow closest waypoint to its location
|
||||
public const ACTION_MOVE_OFFSET = 114; // Use target_x, target_y, target_z With target_type=1
|
||||
public const ACTION_RANDOM_SOUND = 115; //
|
||||
public const ACTION_SET_CORPSE_DELAY = 116; //
|
||||
public const ACTION_DISABLE_EVADE = 117; //
|
||||
public const ACTION_GO_SET_GO_STATE = 118; //
|
||||
public const ACTION_SET_CAN_FLY = 119; // [DEPRECATED]
|
||||
public const ACTION_REMOVE_AURAS_BY_TYPE = 120; // [DEPRECATED]
|
||||
public const ACTION_SET_SIGHT_DIST = 121; // [DEPRECATED]
|
||||
public const ACTION_FLEE = 122; // [DEPRECATED]
|
||||
public const ACTION_ADD_THREAT = 123; //
|
||||
public const ACTION_LOAD_EQUIPMENT = 124; //
|
||||
public const ACTION_TRIGGER_RANDOM_TIMED_EVENT = 125; //
|
||||
public const ACTION_REMOVE_ALL_GAMEOBJECTS = 126; // [DEPRECATED]
|
||||
public const ACTION_PAUSE_MOVEMENT = 127; // MovementSlot (default = 0, active = 1, controlled = 2), PauseTime (ms), Force
|
||||
public const ACTION_PLAY_ANIMKIT = 128; // [RESERVED] don't use on 3.3.5a
|
||||
public const ACTION_SCENE_PLAY = 129; // [RESERVED] don't use on 3.3.5a
|
||||
public const ACTION_SCENE_CANCEL = 130; // [RESERVED] don't use on 3.3.5a
|
||||
public const ACTION_SPAWN_SPAWNGROUP = 131; //
|
||||
public const ACTION_DESPAWN_SPAWNGROUP = 132; //
|
||||
public const ACTION_RESPAWN_BY_SPAWNID = 133; // type, typeGuid - Use to respawn npcs and gobs, the target in this case is always=1 and only a single unit could be a target via the spawnId (action_param1, action_param2)
|
||||
public const ACTION_INVOKER_CAST = 134; // spellID, castFlags
|
||||
public const ACTION_PLAY_CINEMATIC = 135; // cinematic
|
||||
public const ACTION_SET_MOVEMENT_SPEED = 136; // movementType, speedInteger, speedFraction
|
||||
public const ACTION_PLAY_SPELL_VISUAL_KIT = 137; // [RESERVED] spellVisualKitId
|
||||
public const ACTION_OVERRIDE_LIGHT = 138; // zoneId, areaLightId, overrideLightID, transitionMilliseconds
|
||||
public const ACTION_OVERRIDE_WEATHER = 139; // zoneId, weatherId, intensity
|
||||
public const ACTION_SET_AI_ANIM_KIT = 140; // [RESERVED]
|
||||
public const ACTION_SET_HOVER = 141; // Enable/Disable hover for target units.
|
||||
public const ACTION_SET_HEALTH_PCT = 142; // Set current health percentage of target units.
|
||||
public const ACTION_CREATE_CONVERSATION = 143; // [RESERVED]
|
||||
public const ACTION_SET_IMMUNE_PC = 144; // Enable/Disable immunity to players of target units.
|
||||
public const ACTION_SET_IMMUNE_NPC = 145; // Enable/Disable immunity to creatures of target units.
|
||||
public const ACTION_SET_UNINTERACTIBLE = 146; // Make/Reset target units uninteractible.
|
||||
public const ACTION_ACTIVATE_GAMEOBJECT = 147; // Activate target gameobjects, using given action.
|
||||
public const ACTION_ADD_TO_STORED_TARGET_LIST = 148; // Add selected targets to varID for later use.
|
||||
public const ACTION_BECOME_PERSONAL_CLONE_FOR_PLAYER = 149; // [RESERVED]
|
||||
public const ACTION_TRIGGER_GAME_EVENT = 150; // [RESERVED]
|
||||
public const ACTION_DO_ACTION = 151; // [RESERVED]
|
||||
|
||||
public const ACTION_ALL_SPELLCASTS = [self::ACTION_CAST, self::ACTION_ADD_AURA, self::ACTION_INVOKER_CAST, self::ACTION_SELF_CAST, self::ACTION_CROSS_CAST];
|
||||
public const ACTION_ALL_TIMED_ACTION_LISTS = [self::ACTION_CALL_TIMED_ACTIONLIST, self::ACTION_CALL_RANDOM_TIMED_ACTIONLIST, self::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST];
|
||||
|
||||
private const ACTION_CELL_TPL = '[tooltip name=a-#rowIdx#]%1$s[/tooltip][span tooltip=a-#rowIdx#]%2$s[/span]';
|
||||
private const TAL_TAB_ANCHOR = '[url=#sai-actionlist-%1$d onclick=TalTabClick(%1$d)]#%1$d[/url]';
|
||||
|
||||
private array $data = array(
|
||||
self::ACTION_NONE => [null, null, null, null, null, null, 0], // No action
|
||||
self::ACTION_TALK => [null, ['formatTime', -1, true], null, null, null, null, 0], // groupID from creature_text, duration to wait before TEXT_OVER event is triggered, useTalkTarget (0/1) - use target as talk target
|
||||
self::ACTION_SET_FACTION => [null, null, null, null, null, null, 0], // FactionId (or 0 for default)
|
||||
self::ACTION_MORPH_TO_ENTRY_OR_MODEL => [Type::NPC, null, null, null, null, null, 0], // Creature_template entry(param1) OR ModelId (param2) (or 0 for both to demorph)
|
||||
self::ACTION_SOUND => [Type::SOUND, null, null, null, null, null, 0], // SoundId, onlySelf
|
||||
self::ACTION_PLAY_EMOTE => [null, null, null, null, null, null, 0], // EmoteId
|
||||
self::ACTION_FAIL_QUEST => [Type::QUEST, null, null, null, null, null, 0], // QuestID
|
||||
self::ACTION_OFFER_QUEST => [Type::QUEST, null, null, null, null, null, 0], // QuestID, directAdd
|
||||
self::ACTION_SET_REACT_STATE => [['reactState', 10, false], null, null, null, null, null, 0], // state
|
||||
self::ACTION_ACTIVATE_GOBJECT => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_RANDOM_EMOTE => [null, null, null, null, null, null, 0], // EmoteId1, EmoteId2, EmoteId3...
|
||||
self::ACTION_CAST => [Type::SPELL, ['castFlags', -1, false], null, null, null, null, 0], // SpellId, CastFlags, TriggeredFlags
|
||||
self::ACTION_SUMMON_CREATURE => [Type::NPC, ['summonType', -1, false], ['formatTime', 10, true], null, null, null, 0], // CreatureID, summonType, duration in ms, attackInvoker, flags(SmartActionSummonCreatureFlags)
|
||||
self::ACTION_THREAT_SINGLE_PCT => [null, null, null, null, null, null, 0], // Threat%
|
||||
self::ACTION_THREAT_ALL_PCT => [null, null, null, null, null, null, 0], // Threat%
|
||||
self::ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS => [Type::QUEST, null, null, null, null, null, 0], // QuestID
|
||||
self::ACTION_SET_INGAME_PHASE_ID => [null, null, null, null, null, null, 2], // used on 4.3.4 and higher scripts
|
||||
self::ACTION_SET_EMOTE_STATE => [null, null, null, null, null, null, 0], // emoteID
|
||||
self::ACTION_SET_UNIT_FLAG => [['unitFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_REMOVE_UNIT_FLAG => [['unitFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_AUTO_ATTACK => [null, null, null, null, null, null, 0], // AllowAttackState (0 = stop attack, anything else means continue attacking)
|
||||
self::ACTION_ALLOW_COMBAT_MOVEMENT => [null, null, null, null, null, null, 0], // AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
|
||||
self::ACTION_SET_EVENT_PHASE => [null, null, null, null, null, null, 0], // Phase
|
||||
self::ACTION_INC_EVENT_PHASE => [null, null, null, null, null, null, 0], // Value (may be negative to decrement phase, should not be 0)
|
||||
self::ACTION_EVADE => [null, null, null, null, null, null, 0], // toRespawnPosition (0 = Move to RespawnPosition, 1 = Move to last stored home position)
|
||||
self::ACTION_FLEE_FOR_ASSIST => [null, null, null, null, null, null, 0], // With Emote
|
||||
self::ACTION_CALL_GROUPEVENTHAPPENS => [Type::QUEST, null, null, null, null, null, 0], // QuestID
|
||||
self::ACTION_COMBAT_STOP => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_REMOVEAURASFROMSPELL => [Type::SPELL, null, null, null, null, null, 0], // Spellid (0 removes all auras), charges (0 removes aura)
|
||||
self::ACTION_FOLLOW => [null, null, null, null, null, null, 0], // Distance (0 = default), Angle (0 = default), EndCreatureEntry, credit, creditType (0monsterkill, 1event)
|
||||
self::ACTION_RANDOM_PHASE => [null, null, null, null, null, null, 0], // PhaseId1, PhaseId2, PhaseId3...
|
||||
self::ACTION_RANDOM_PHASE_RANGE => [null, null, null, null, null, null, 0], // PhaseMin, PhaseMax
|
||||
self::ACTION_RESET_GOBJECT => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_CALL_KILLEDMONSTER => [Type::NPC, null, null, null, null, null, 0], // CreatureId,
|
||||
self::ACTION_SET_INST_DATA => [null, null, null, null, null, null, 0], // Field, Data, Type (0 = SetData, 1 = SetBossState)
|
||||
self::ACTION_SET_INST_DATA64 => [null, null, null, null, null, null, 0], // Field,
|
||||
self::ACTION_UPDATE_TEMPLATE => [Type::NPC, null, null, null, null, null, 0], // Entry
|
||||
self::ACTION_DIE => [null, null, null, null, null, null, 0], // No Params
|
||||
self::ACTION_SET_IN_COMBAT_WITH_ZONE => [null, null, null, null, null, null, 0], // No Params
|
||||
self::ACTION_CALL_FOR_HELP => [null, null, null, null, null, null, 0], // Radius, With Emote
|
||||
self::ACTION_SET_SHEATH => [['sheathState', 10, false], null, null, null, null, null, 0], // Sheath (0-unarmed, 1-melee, 2-ranged)
|
||||
self::ACTION_FORCE_DESPAWN => [['formatTime', 10, true], ['formatTime', 11, false], null, null, null, null, 0], // timer
|
||||
self::ACTION_SET_INVINCIBILITY_HP_LEVEL => [null, null, null, null, null, null, 0], // MinHpValue(+pct, -flat)
|
||||
self::ACTION_MOUNT_TO_ENTRY_OR_MODEL => [Type::NPC, null, null, null, null, null, 0], // Creature_template entry(param1) OR ModelId (param2) (or 0 for both to dismount)
|
||||
self::ACTION_SET_INGAME_PHASE_MASK => [null, null, null, null, null, null, 0], // mask
|
||||
self::ACTION_SET_DATA => [null, null, null, null, null, null, 0], // Field, Data (only creature @todo)
|
||||
self::ACTION_ATTACK_STOP => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_SET_VISIBILITY => [null, null, null, null, null, null, 0], // on/off
|
||||
self::ACTION_SET_ACTIVE => [null, null, null, null, null, null, 0], // on/off
|
||||
self::ACTION_ATTACK_START => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_SUMMON_GO => [Type::OBJECT, ['formatTime', 10, false], null, null, null, null, 0], // GameObjectID, DespawnTime in s
|
||||
self::ACTION_KILL_UNIT => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_ACTIVATE_TAXI => [null, null, null, null, null, null, 0], // TaxiID
|
||||
self::ACTION_WP_START => [null, null, null, Type::QUEST, ['formatTime', 10, true], ['reactState', 11, false], 0], // run/walk, pathID, canRepeat, quest, despawntime
|
||||
self::ACTION_WP_PAUSE => [['formatTime', 10, true], null, null, null, null, null, 0], // time
|
||||
self::ACTION_WP_STOP => [['formatTime', 10, true], Type::QUEST, null, null, null, null, 0], // despawnTime, quest, fail?
|
||||
self::ACTION_ADD_ITEM => [Type::ITEM, null, null, null, null, null, 0], // itemID, count
|
||||
self::ACTION_REMOVE_ITEM => [Type::ITEM, null, null, null, null, null, 0], // itemID, count
|
||||
self::ACTION_INSTALL_AI_TEMPLATE => [['aiTemplate', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_SET_RUN => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_DISABLE_GRAVITY => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_SWIM => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_TELEPORT => [null, null, null, null, null, null, 0], // mapID,
|
||||
self::ACTION_SET_COUNTER => [null, null, null, null, null, null, 0], // id, value, reset (0/1)
|
||||
self::ACTION_STORE_TARGET_LIST => [null, null, null, null, null, null, 0], // varID,
|
||||
self::ACTION_WP_RESUME => [null, null, null, null, null, null, 0], // none
|
||||
self::ACTION_SET_ORIENTATION => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_CREATE_TIMED_EVENT => [null, ['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // id, InitialMin, InitialMax, RepeatMin(only if it repeats), RepeatMax(only if it repeats), chance
|
||||
self::ACTION_PLAYMOVIE => [null, null, null, null, null, null, 0], // entry
|
||||
self::ACTION_MOVE_TO_POS => [null, null, null, null, null, null, 0], // PointId, transport, disablePathfinding, ContactDistance
|
||||
self::ACTION_ENABLE_TEMP_GOBJ => [['formatTime', 10, false], null, null, null, null, null, 0], // despawnTimer (sec)
|
||||
self::ACTION_EQUIP => [null, null, Type::ITEM, Type::ITEM, Type::ITEM, null, 0], // entry, slotmask slot1, slot2, slot3 , only slots with mask set will be sent to client, bits are 1, 2, 4, leaving mask 0 is defaulted to mask 7 (send all), slots1-3 are only used if no entry is set
|
||||
self::ACTION_CLOSE_GOSSIP => [null, null, null, null, null, null, 0], // none
|
||||
self::ACTION_TRIGGER_TIMED_EVENT => [null, null, null, null, null, null, 0], // id(>1)
|
||||
self::ACTION_REMOVE_TIMED_EVENT => [null, null, null, null, null, null, 0], // id(>1)
|
||||
self::ACTION_ADD_AURA => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_OVERRIDE_SCRIPT_BASE_OBJECT => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_RESET_SCRIPT_BASE_OBJECT => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_CALL_SCRIPT_RESET => [null, null, null, null, null, null, 0], // none
|
||||
self::ACTION_SET_RANGED_MOVEMENT => [null, null, null, null, null, null, 0], // Distance, angle
|
||||
self::ACTION_CALL_TIMED_ACTIONLIST => [null, null, null, null, null, null, 0], // ID (overwrites already running actionlist), stop after combat?(0/1), timer update type(0-OOC, 1-IC, 2-ALWAYS)
|
||||
self::ACTION_SET_NPC_FLAG => [['npcFlags', 10, false], null, null, null, null, null, 0], // Flags
|
||||
self::ACTION_ADD_NPC_FLAG => [['npcFlags', 10, false], null, null, null, null, null, 0], // Flags
|
||||
self::ACTION_REMOVE_NPC_FLAG => [['npcFlags', 10, false], null, null, null, null, null, 0], // Flags
|
||||
self::ACTION_SIMPLE_TALK => [null, null, null, null, null, null, 0], // groupID, can be used to make players say groupID, Text_over event is not triggered, whisper can not be used (Target units will say the text)
|
||||
self::ACTION_SELF_CAST => [Type::SPELL, ['castFlags', -1, false], null, null, null, null, 0], // spellID, castFlags
|
||||
self::ACTION_CROSS_CAST => [Type::SPELL, ['castFlags', -1, false], null, null, null, null, 0], // spellID, castFlags, CasterTargetType, CasterTarget param1, CasterTarget param2, CasterTarget param3, ( + the origonal target fields as Destination target), CasterTargets will cast spellID on all Targets (use with caution if targeting multiple * multiple units)
|
||||
self::ACTION_CALL_RANDOM_TIMED_ACTIONLIST => [null, null, null, null, null, null, 0], // script9 ids 1-9
|
||||
self::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST => [null, null, null, null, null, null, 0], // script9 id min, max
|
||||
self::ACTION_RANDOM_MOVE => [null, null, null, null, null, null, 0], // maxDist
|
||||
self::ACTION_SET_UNIT_FIELD_BYTES_1 => [['unitFieldBytes1', 10, false], null, null, null, null, null, 0], // bytes, target
|
||||
self::ACTION_REMOVE_UNIT_FIELD_BYTES_1 => [['unitFieldBytes1', 10, false], null, null, null, null, null, 0], // bytes, target
|
||||
self::ACTION_INTERRUPT_SPELL => [null, Type::SPELL, null, null, null, null, 0], //
|
||||
self::ACTION_SEND_GO_CUSTOM_ANIM => [['dynFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_SET_DYNAMIC_FLAG => [['dynFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_ADD_DYNAMIC_FLAG => [['dynFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_REMOVE_DYNAMIC_FLAG => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_JUMP_TO_POS => [null, null, null, null, null, null, 0], // speedXY, speedZ, targetX, targetY, targetZ
|
||||
self::ACTION_SEND_GOSSIP_MENU => [null, null, null, null, null, null, 0], // menuId, optionId
|
||||
self::ACTION_GO_SET_LOOT_STATE => [['lootState', 10, false], null, null, null, null, null, 0], // state
|
||||
self::ACTION_SEND_TARGET_TO_TARGET => [null, null, null, null, null, null, 0], // id
|
||||
self::ACTION_SET_HOME_POS => [null, null, null, null, null, null, 0], // none
|
||||
self::ACTION_SET_HEALTH_REGEN => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_ROOT => [null, null, null, null, null, null, 0], // off/on
|
||||
self::ACTION_SET_GO_FLAG => [['goFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_ADD_GO_FLAG => [['goFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_REMOVE_GO_FLAG => [['goFlags', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_SUMMON_CREATURE_GROUP => [null, null, null, null, null, null, 0], // Group, attackInvoker
|
||||
self::ACTION_SET_POWER => [['powerType', 10, false], null, null, null, null, null, 0], // PowerType, newPower
|
||||
self::ACTION_ADD_POWER => [['powerType', 10, false], null, null, null, null, null, 0], // PowerType, newPower
|
||||
self::ACTION_REMOVE_POWER => [['powerType', 10, false], null, null, null, null, null, 0], // PowerType, newPower
|
||||
self::ACTION_GAME_EVENT_STOP => [Type::WORLDEVENT, null, null, null, null, null, 0], // GameEventId
|
||||
self::ACTION_GAME_EVENT_START => [Type::WORLDEVENT, null, null, null, null, null, 0], // GameEventId
|
||||
self::ACTION_START_CLOSEST_WAYPOINT => [null, null, null, null, null, null, 0], // wp1, wp2, wp3, wp4, wp5, wp6, wp7
|
||||
self::ACTION_MOVE_OFFSET => [null, null, null, null, null, null, 0], //
|
||||
self::ACTION_RANDOM_SOUND => [Type::SOUND, Type::SOUND, Type::SOUND, Type::SOUND, null, null, 0], // soundId1, soundId2, soundId3, soundId4, soundId5, onlySelf
|
||||
self::ACTION_SET_CORPSE_DELAY => [['formatTime', 10, false], null, null, null, null, null, 0], // timer
|
||||
self::ACTION_DISABLE_EVADE => [null, null, null, null, null, null, 0], // 0/1 (1 = disabled, 0 = enabled)
|
||||
self::ACTION_GO_SET_GO_STATE => [null, null, null, null, null, null, 0], // state
|
||||
self::ACTION_SET_CAN_FLY => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_REMOVE_AURAS_BY_TYPE => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_SET_SIGHT_DIST => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_FLEE => [['formatTime', 10, false], null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_ADD_THREAT => [null, null, null, null, null, null, 0], // +threat, -threat
|
||||
self::ACTION_LOAD_EQUIPMENT => [null, null, null, null, null, null, 0], // id
|
||||
self::ACTION_TRIGGER_RANDOM_TIMED_EVENT => [['numRange', 10, false], null, null, null, null, null, 0], // id min range, id max range
|
||||
self::ACTION_REMOVE_ALL_GAMEOBJECTS => [null, null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::ACTION_PAUSE_MOVEMENT => [null, ['formatTime', 10, true], null, null, null, null, 0], // MovementSlot (default = 0, active = 1, controlled = 2), PauseTime (ms), Force
|
||||
self::ACTION_PLAY_ANIMKIT => [null, null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::ACTION_SCENE_PLAY => [null, null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::ACTION_SCENE_CANCEL => [null, null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::ACTION_SPAWN_SPAWNGROUP => [null, null, null, ['spawnFlags', 11, false], null, null, 0], // Group ID, min secs, max secs, spawnflags
|
||||
self::ACTION_DESPAWN_SPAWNGROUP => [null, null, null, ['spawnFlags', 11, false], null, null, 0], // Group ID, min secs, max secs, spawnflags
|
||||
self::ACTION_RESPAWN_BY_SPAWNID => [null, null, null, null, null, null, 0], // spawnType, spawnId
|
||||
self::ACTION_INVOKER_CAST => [Type::SPELL, ['castFlags', -1, false], null, null, null, null, 0], // spellID, castFlags
|
||||
self::ACTION_PLAY_CINEMATIC => [null, null, null, null, null, null, 0], // entry, cinematic
|
||||
self::ACTION_SET_MOVEMENT_SPEED => [null, null, null, null, null, null, 0], // movementType, speedInteger, speedFraction
|
||||
self::ACTION_PLAY_SPELL_VISUAL_KIT => [null, null, null, null, null, null, 2], // spellVisualKitId (RESERVED, PENDING CHERRYPICK)
|
||||
self::ACTION_OVERRIDE_LIGHT => [Type::ZONE, null, null, ['formatTime', -1, true], null, null, 0], // zoneId, overrideLightID, transitionMilliseconds
|
||||
self::ACTION_OVERRIDE_WEATHER => [Type::ZONE, ['weatherState', 10, false], null, null, null, null, 0], // zoneId, weatherId, intensity
|
||||
self::ACTION_SET_AI_ANIM_KIT => [null, null, null, null, null, null, 2], // DEPRECATED, DO REUSE (it was never used in any branch, treat as free action id)
|
||||
self::ACTION_SET_HOVER => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_HEALTH_PCT => [null, null, null, null, null, null, 0], // percent
|
||||
self::ACTION_CREATE_CONVERSATION => [null, null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::ACTION_SET_IMMUNE_PC => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_IMMUNE_NPC => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_SET_UNINTERACTIBLE => [null, null, null, null, null, null, 0], // 0/1
|
||||
self::ACTION_ACTIVATE_GAMEOBJECT => [null, null, null, null, null, null, 0], // GameObjectActions
|
||||
self::ACTION_ADD_TO_STORED_TARGET_LIST => [null, null, null, null, null, null, 0], // varID
|
||||
self::ACTION_BECOME_PERSONAL_CLONE_FOR_PLAYER => [null, null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::ACTION_TRIGGER_GAME_EVENT => [null, null, null, null, null, null, 2], // eventId, useSaiTargetAsGameEventSource (RESERVED, PENDING CHERRYPICK)
|
||||
self::ACTION_DO_ACTION => [null, null, null, null, null, null, 2] // actionId (RESERVED, PENDING CHERRYPICK)
|
||||
);
|
||||
|
||||
private array $jsGlobals = [];
|
||||
private ?array $summons = null;
|
||||
|
||||
public function __construct(
|
||||
private int $id,
|
||||
public readonly int $type,
|
||||
private array $param,
|
||||
private SmartAI &$smartAI)
|
||||
{
|
||||
// init additional parameters
|
||||
Util::checkNumeric($this->param, NUM_CAST_INT);
|
||||
$this->param = array_pad($this->param, 15, '');
|
||||
}
|
||||
|
||||
public function process() : array
|
||||
{
|
||||
$body =
|
||||
$footer = '';
|
||||
|
||||
$actionTT = Lang::smartAI('actionTT', array_merge([$this->type], $this->param));
|
||||
|
||||
for ($i = 0; $i < 5; $i++)
|
||||
{
|
||||
$aParams = $this->data[$this->type];
|
||||
|
||||
if (is_array($aParams[$i]))
|
||||
{
|
||||
[$fn, $idx, $extraParam] = $aParams[$i];
|
||||
|
||||
if ($idx < 0)
|
||||
$footer = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam);
|
||||
else
|
||||
$this->param[$idx] = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam);
|
||||
}
|
||||
else if (is_int($aParams[$i]) && $this->param[$i])
|
||||
$this->jsGlobals[$aParams[$i]][$this->param[$i]] = $this->param[$i];
|
||||
}
|
||||
|
||||
// non-generic cases
|
||||
switch ($this->type)
|
||||
{
|
||||
case self::ACTION_FLEE_FOR_ASSIST: // 25 -> none
|
||||
case self::ACTION_CALL_FOR_HELP: // 39 -> self
|
||||
if ($this->param[0])
|
||||
$footer = $this->param;
|
||||
break;
|
||||
case self::ACTION_INTERRUPT_SPELL: // 92 -> self
|
||||
if (!$this->param[1])
|
||||
$footer = $this->param;
|
||||
break;
|
||||
case self::ACTION_UPDATE_TEMPLATE: // 36
|
||||
case self::ACTION_SET_CORPSE_DELAY: // 116
|
||||
if ($this->param[1])
|
||||
$footer = $this->param;
|
||||
break;
|
||||
case self::ACTION_PAUSE_MOVEMENT: // 127 -> any target [ye, not gonna resolve this nonsense]
|
||||
case self::ACTION_REMOVEAURASFROMSPELL: // 28 -> any target
|
||||
case self::ACTION_SOUND: // 4 -> self [param3 set in DB but not used in core?]
|
||||
case self::ACTION_SUMMON_GO: // 50 -> self, world coords
|
||||
case self::ACTION_MOVE_TO_POS: // 69 -> any target
|
||||
if ($this->param[2])
|
||||
$footer = $this->param;
|
||||
break;
|
||||
case self::ACTION_WP_START: // 53 -> any .. why tho?
|
||||
if ($this->param[2] || $this->param[5])
|
||||
$footer = $this->param;
|
||||
break;
|
||||
case self::ACTION_PLAY_EMOTE: // 5 -> any target
|
||||
case self::ACTION_SET_EMOTE_STATE: // 17 -> any target
|
||||
if ($this->param[0])
|
||||
{
|
||||
$this->param[0] *= -1; // handle creature emote
|
||||
$this->jsGlobals[Type::EMOTE][$this->param[0]] = $this->param[0];
|
||||
}
|
||||
break;
|
||||
case self::ACTION_RANDOM_EMOTE: // 10 -> any target
|
||||
$buff = [];
|
||||
for ($i = 0; $i < 6; $i++)
|
||||
{
|
||||
if (empty($this->param[$i]))
|
||||
continue;
|
||||
|
||||
$this->param[$i] *= -1; // handle creature emote
|
||||
$buff[] = '[emote='.$this->param[$i].']';
|
||||
$this->jsGlobals[Type::EMOTE][$this->param[$i]] = $this->param[$i];
|
||||
}
|
||||
$this->param[10] = Lang::concat($buff, Lang::CONCAT_OR);
|
||||
break;
|
||||
case self::ACTION_SET_FACTION: // 2 -> any target
|
||||
if ($this->param[0])
|
||||
{
|
||||
$this->param[10] = DB::Aowow()->selectCell('SELECT `factionId` FROM ?_factiontemplate WHERE `id` = ?d', $this->param[0]);
|
||||
$this->jsGlobals[Type::FACTION][$this->param[10]] = $this->param[10];
|
||||
}
|
||||
break;
|
||||
case self::ACTION_MORPH_TO_ENTRY_OR_MODEL: // 3 -> self
|
||||
case self::ACTION_MOUNT_TO_ENTRY_OR_MODEL: // 43 -> self
|
||||
if (!$this->param[0] && !$this->param[1])
|
||||
$this->param[10] = 1;
|
||||
break;
|
||||
case self::ACTION_THREAT_SINGLE_PCT: // 13 -> victim
|
||||
case self::ACTION_THREAT_ALL_PCT: // 14 -> self
|
||||
case self::ACTION_ADD_THREAT: // 123 -> any target
|
||||
$this->param[10] = $this->param[0] - $this->param[1];
|
||||
break;
|
||||
case self::ACTION_FOLLOW: // 29 -> any target
|
||||
if ($this->param[1])
|
||||
{
|
||||
$this->param[10] = Util::O2Deg($this->param[1])[0];
|
||||
$footer = $this->param;
|
||||
}
|
||||
if ($this->param[3])
|
||||
{
|
||||
if ($this->param[4])
|
||||
{
|
||||
$this->jsGlobals[Type::QUEST][$this->param[3]] = $this->param[3];
|
||||
$this->param[11] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$this->param[3]] = $this->param[3];
|
||||
$this->param[12] = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case self::ACTION_RANDOM_PHASE: // 30 -> self
|
||||
$buff = [];
|
||||
for ($i = 0; $i < 7; $i++)
|
||||
if ($_ = $this->param[$i])
|
||||
$buff[] = $_;
|
||||
|
||||
$this->param[10] = Lang::concat($buff);
|
||||
break;
|
||||
case self::ACTION_ACTIVATE_TAXI: // 52 -> invoker
|
||||
$nodes = DB::Aowow()->selectRow(
|
||||
'SELECT tn1.`name_loc0` AS "start_loc0", tn1.name_loc?d AS start_loc?d, tn2.`name_loc0` AS "end_loc0", tn2.name_loc?d AS end_loc?d
|
||||
FROM ?_taxipath tp
|
||||
JOIN ?_taxinodes tn1 ON tp.`startNodeId` = tn1.`id`
|
||||
JOIN ?_taxinodes tn2 ON tp.`endNodeId` = tn2.`id`
|
||||
WHERE tp.`id` = ?d',
|
||||
Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, Lang::getLocale()->value, $this->param[0]
|
||||
);
|
||||
$this->param[10] = Util::jsEscape(Util::localizedString($nodes, 'start'));
|
||||
$this->param[11] = Util::jsEscape(Util::localizedString($nodes, 'end'));
|
||||
break;
|
||||
case self::ACTION_SET_INGAME_PHASE_MASK: // 44 -> any target
|
||||
if ($this->param[0])
|
||||
$this->param[10] = Lang::concat(Util::mask2bits($this->param[0]));
|
||||
break;
|
||||
case self::ACTION_TELEPORT: // 62 -> invoker
|
||||
[$x, $y, $z, $o] = $this->smartAI->getTarget()->getWorldPos();
|
||||
// try from areatrigger setup data
|
||||
if ($this->smartAI->teleportTargetArea)
|
||||
$this->param[10] = $this->smartAI->teleportTargetArea;
|
||||
// try calc from SmartTarget data
|
||||
else if ($pos = WorldPosition::toZonePos($this->param[0], $x, $y))
|
||||
{
|
||||
$this->param[10] = $pos[0]['areaId'];
|
||||
$this->param[11] = str_pad($pos[0]['posX'] * 10, 3, '0', STR_PAD_LEFT).str_pad($pos[0]['posY'] * 10, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
// maybe the mapId is an instane map
|
||||
else if ($areaId = DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d', $this->param[0]))
|
||||
$this->param[10] = $areaId;
|
||||
// ...whelp
|
||||
else
|
||||
trigger_error('SmartAction::process - could not resolve teleport target: map:'.$this->param[0].' x:'.$x.' y:'.$y);
|
||||
|
||||
if ($this->param[10])
|
||||
$this->jsGlobals[Type::ZONE][$this->param[10]] = $this->param[10];
|
||||
break;
|
||||
case self::ACTION_SET_ORIENTATION: // 66 -> any target
|
||||
if ($this->smartAI->getTarget()->type == SmartTarget::TARGET_POSITION)
|
||||
$this->param[10] = Util::O2Deg($this->smartAI->getTarget()->getWorldPos()[3])[1];
|
||||
else if ($this->smartAI->getTarget()->type != SmartTarget::TARGET_SELF)
|
||||
$this->param[10] = '#target#';
|
||||
break;
|
||||
case self::ACTION_EQUIP: // 71 -> any
|
||||
$equip = [];
|
||||
|
||||
if ($this->param[0])
|
||||
{
|
||||
$slots = $this->param[1] ? Util::mask2bits($this->param[1], 1) : [1, 2, 3];
|
||||
$items = DB::World()->selectRow('SELECT `ItemID1`, `ItemID2`, `ItemID3` FROM creature_equip_template WHERE `CreatureID` = ?d AND `ID` = ?d', $this->smartAI->getEntry(), $this->param[0]);
|
||||
|
||||
foreach ($slots as $s)
|
||||
if ($_ = $items['ItemID'.$s])
|
||||
$equip[] = $_;
|
||||
}
|
||||
else if ($this->param[2] || $this->param[3] || $this->param[4])
|
||||
{
|
||||
if ($_ = $this->param[2])
|
||||
$equip[] = $_;
|
||||
if ($_ = $this->param[3])
|
||||
$equip[] = $_;
|
||||
if ($_ = $this->param[4])
|
||||
$equip[] = $_;
|
||||
}
|
||||
|
||||
if ($equip)
|
||||
{
|
||||
$this->param[10] = Lang::concat($equip, callback: fn($x) => '[item='.$x.']');
|
||||
$footer = true;
|
||||
|
||||
foreach ($equip as $_)
|
||||
$this->jsGlobals[Type::ITEM][$_] = $_;
|
||||
}
|
||||
break;
|
||||
case self::ACTION_LOAD_EQUIPMENT: // 124 -> any target
|
||||
$buff = [];
|
||||
if ($this->param[0])
|
||||
{
|
||||
$items = DB::World()->selectRow('SELECT `ItemID1`, `ItemID2`, `ItemID3` FROM creature_equip_template WHERE `CreatureID` = ?d AND `ID` = ?d', $this->smartAI->getEntry(), $this->param[0]);
|
||||
foreach ($items as $i)
|
||||
{
|
||||
if (!$i)
|
||||
continue;
|
||||
|
||||
$this->jsGlobals[Type::ITEM][$i] = $i;
|
||||
$buff[] = '[item='.$i.']';
|
||||
}
|
||||
}
|
||||
else if (!$this->param[1])
|
||||
trigger_error('SmartAI::action - action #124 (SmartAction::ACTION_LOAD_EQIPMENT) is malformed');
|
||||
|
||||
$this->param[10] = Lang::concat($buff);
|
||||
$footer = true;
|
||||
break;
|
||||
case self::ACTION_CALL_TIMED_ACTIONLIST: // 80 -> any target
|
||||
$this->param[10] = match ($this->param[1])
|
||||
{
|
||||
0, 1, 2 => Lang::smartAI('saiUpdate', $this->param[1]),
|
||||
default => Lang::smartAI('saiUpdateUNK', [$this->param[1]])
|
||||
};
|
||||
|
||||
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[0], ['baseEntry' => $this->smartAI->getEntry()]);
|
||||
$tal->prepare();
|
||||
|
||||
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
|
||||
|
||||
foreach ($tal->getTabs() as $guid => $tt)
|
||||
$this->smartAI->addTab($guid, $tt);
|
||||
|
||||
break;
|
||||
case self::ACTION_CALL_KILLEDMONSTER: // 33: Note: If target is SMART_TARGET_NONE (0) or SMART_TARGET_SELF (1), the kill is credited to all players eligible for loot from this creature.
|
||||
if ($this->smartAI->getTarget()->type == SmartTarget::TARGET_SELF || $this->smartAI->getTarget()->type == SmartTarget::TARGET_NONE)
|
||||
$this->param[10] = (new SmartTarget($this->id, SmartTarget::TARGET_LOOT_RECIPIENTS, [], [], $this->smartAI))->process();
|
||||
break;
|
||||
case self::ACTION_CROSS_CAST: // 86 -> entity by TargetingBlock(param3, param4, param5, param6) cross cast spell <param1> at any target
|
||||
$this->param[10] = (new SmartTarget($this->id, $this->param[2], [$this->param[3], $this->param[4], $this->param[5]], [], $this->smartAI))->process();
|
||||
break;
|
||||
case self::ACTION_CALL_RANDOM_TIMED_ACTIONLIST: // 87 -> self
|
||||
$talBuff = [];
|
||||
for ($i = 0; $i < 6; $i++)
|
||||
{
|
||||
if (!$this->param[$i])
|
||||
continue;
|
||||
|
||||
$talBuff[] = sprintf(self::TAL_TAB_ANCHOR, $this->param[$i]);
|
||||
|
||||
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $this->param[$i], ['baseEntry' => $this->smartAI->getEntry()]);
|
||||
$tal->prepare();
|
||||
|
||||
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
|
||||
|
||||
foreach ($tal->getTabs() as $guid => $tt)
|
||||
$this->smartAI->addTab($guid, $tt);
|
||||
}
|
||||
$this->param[10] = Lang::concat($talBuff, Lang::CONCAT_OR);
|
||||
break;
|
||||
case self::ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST:// 88 -> self
|
||||
$talBuff = [];
|
||||
for ($i = $this->param[0]; $i <= $this->param[1]; $i++)
|
||||
{
|
||||
$talBuff[] = sprintf(self::TAL_TAB_ANCHOR, $i);
|
||||
|
||||
$tal = new SmartAI(SmartAI::SRC_TYPE_ACTIONLIST, $i, ['baseEntry' => $this->smartAI->getEntry()]);
|
||||
$tal->prepare();
|
||||
|
||||
Util::mergeJsGlobals($this->jsGlobals, $tal->getJSGlobals());
|
||||
|
||||
foreach ($tal->getTabs() as $guid => $tt)
|
||||
$this->smartAI->addTab($guid, $tt);
|
||||
}
|
||||
$this->param[10] = Lang::concat($talBuff, Lang::CONCAT_OR);
|
||||
break;
|
||||
case self::ACTION_SET_HOME_POS: // 101 -> self
|
||||
if ($this->smartAI->getTarget()?->type == Smarttarget::TARGET_SELF)
|
||||
$this->param[10] = 1;
|
||||
// do not break;
|
||||
case self::ACTION_JUMP_TO_POS: // 97 -> self
|
||||
case self::ACTION_MOVE_OFFSET: // 114 -> self
|
||||
array_splice($this->param, 11, replacement: $this->smartAI->getTarget()->getWorldPos());
|
||||
break;
|
||||
case self::ACTION_SUMMON_CREATURE_GROUP: // 107 -> untargeted
|
||||
if ($this->summons === null)
|
||||
$this->summons = DB::World()->selectCol('SELECT `groupId` AS ARRAY_KEY, `entry` AS ARRAY_KEY2, COUNT(*) AS "n" FROM creature_summon_groups WHERE `summonerId` = ?d GROUP BY `groupId`, `entry`', $this->smartAI->getEntry());
|
||||
|
||||
$buff = [];
|
||||
if (!empty($this->summons[$this->param[0]]))
|
||||
{
|
||||
foreach ($this->summons[$this->param[0]] as $id => $n)
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$id] = $id;
|
||||
$buff[] = $n.'x [npc='.$id.']';
|
||||
}
|
||||
}
|
||||
|
||||
if ($buff)
|
||||
$this->param[10] = Lang::concat($buff);
|
||||
break;
|
||||
case self::ACTION_START_CLOSEST_WAYPOINT: // 113 -> any target
|
||||
$this->param[10] = Lang::concat(array_filter($this->param), Lang::CONCAT_OR, fn($x) => '#[b]'.$x.'[/b]');
|
||||
break;
|
||||
case self::ACTION_RANDOM_SOUND: // 115 -> self
|
||||
for ($i = 0; $i < 4; $i++)
|
||||
{
|
||||
if ($x = $this->param[$i])
|
||||
{
|
||||
$this->jsGlobals[Type::SOUND][$x] = $x;
|
||||
$this->param[10] .= '[sound='.$x.']';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->param[5])
|
||||
$footer = true;
|
||||
break;
|
||||
case self::ACTION_GO_SET_GO_STATE: // 118 -> ???
|
||||
$this->param[10] = match ($this->param[0])
|
||||
{
|
||||
0, 1, 2 => Lang::smartAI('GOStates', $this->param[0]),
|
||||
default => Lang::smartAI('GOStateUNK', [$this->param[0]])
|
||||
};
|
||||
break;
|
||||
case self::ACTION_REMOVE_AURAS_BY_TYPE: // 120 -> any target
|
||||
$this->param[10] = Lang::spell('auras', $this->param[0]);
|
||||
break;
|
||||
case self::ACTION_SPAWN_SPAWNGROUP: // 131
|
||||
case self::ACTION_DESPAWN_SPAWNGROUP: // 132
|
||||
$this->param[10] = Util::jsEscape(DB::World()->selectCell('SELECT `GroupName` FROM spawn_group_template WHERE `groupId` = ?d', $this->param[0]));
|
||||
$entities = DB::World()->select('SELECT `spawnType` AS "0", `spawnId` AS "1" FROM spawn_group WHERE `groupId` = ?d', $this->param[0]);
|
||||
|
||||
$n = 5;
|
||||
$buff = [];
|
||||
foreach ($entities as [$spawnType, $guid])
|
||||
{
|
||||
$type = Type::NPC;
|
||||
if ($spawnType == 1)
|
||||
$type == Type::OBJECT;
|
||||
|
||||
if ($_ = $this->resolveGuid($type, $guid))
|
||||
{
|
||||
$this->jsGlobals[$type][$_] = $_;
|
||||
$buff[] = '['.Type::getFileString($type).'='.$_.'][small class=q0] (GUID: '.$guid.')[/small]';
|
||||
}
|
||||
else
|
||||
$buff[] = Lang::smartAI('entityUNK').'[small class=q0] (GUID: '.$guid.')[/small]';
|
||||
|
||||
if (!--$n)
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($entities) > 5)
|
||||
$buff[] = '+'.(count($entities) - 5).'…';
|
||||
|
||||
$this->param[12] = '[ul][li]'.implode('[/li][li]', $buff).'[/li][/ul]';
|
||||
|
||||
// i'd like this stored in $data but numRange can only handle msec
|
||||
if ($time = $this->numRange($this->param[1] * 1000, $this->param[2] * 1000, true))
|
||||
$footer = [$time];
|
||||
break;
|
||||
case self::ACTION_RESPAWN_BY_SPAWNID: // 133
|
||||
$type = Type::NPC;
|
||||
if ($this->param[0] == 1)
|
||||
$type == Type::OBJECT;
|
||||
|
||||
if ($_ = $this->resolveGuid($type, $this->param[1]))
|
||||
{
|
||||
$this->param[10] = '['.Type::getFileString($type).'='.$_.']';
|
||||
$this->jsGlobals[$type][$_] = $_;
|
||||
}
|
||||
else
|
||||
$this->param[10] = Lang::smartAI('entityUNK');
|
||||
break;
|
||||
case self::ACTION_SET_MOVEMENT_SPEED: // 136
|
||||
$this->param[10] = $this->param[1] + $this->param[2] / pow(10, floor(log10($this->param[2] ?: 1.0) + 1)); // i know string concatenation is a thing. don't @ me!
|
||||
break;
|
||||
case self::ACTION_TALK: // 1 -> any target
|
||||
case self::ACTION_SIMPLE_TALK: // 84 -> any target
|
||||
$noSrc = false;
|
||||
if ($npcId = $this->smartAI->getTarget()->getTalkSource($noSrc))
|
||||
{
|
||||
if ($quotes = $this->smartAI->getQuote($npcId, $this->param[0], $npcSrc))
|
||||
foreach ($quotes as ['text' => $text, 'prefix' => $prefix])
|
||||
$this->param[10] .= sprintf($text, $noSrc ? '' : sprintf($prefix, $npcSrc), $npcSrc);
|
||||
}
|
||||
else
|
||||
trigger_error('SmartAI::action - could not determine talk source for action #'.$this->type);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->smartAI->addJsGlobals($this->jsGlobals);
|
||||
|
||||
$body = Lang::smartAI('actions', $this->type, 0, $this->param) ?? Lang::smartAI('actionUNK', [$this->type]);
|
||||
if ($footer)
|
||||
$footer = Lang::smartAI('actions', $this->type, 1, (array)$footer);
|
||||
|
||||
// resolve conditionals
|
||||
$i = 0;
|
||||
while (strstr($body, ')?') && $i++ < 3)
|
||||
$body = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $body);
|
||||
|
||||
$i = 0;
|
||||
while (strstr($footer, ')?') && $i++ < 3)
|
||||
$footer = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $footer);
|
||||
|
||||
// wrap body in tooltip
|
||||
return [sprintf(self::ACTION_CELL_TPL, $actionTT, $body), $footer];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
382
includes/components/SmartAI/SmartEvent.class.php
Normal file
382
includes/components/SmartAI/SmartEvent.class.php
Normal file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
// TrinityCore - SmartAI
|
||||
class SmartEvent
|
||||
{
|
||||
use SmartHelper;
|
||||
|
||||
public const EVENT_UPDATE_IC = 0; // In combat.
|
||||
public const EVENT_UPDATE_OOC = 1; // Out of combat.
|
||||
public const EVENT_HEALTH_PCT = 2; // Health Percentage
|
||||
public const EVENT_MANA_PCT = 3; // Mana Percentage
|
||||
public const EVENT_AGGRO = 4; // On Creature Aggro
|
||||
public const EVENT_KILL = 5; // On Creature Kill
|
||||
public const EVENT_DEATH = 6; // On Creature Death
|
||||
public const EVENT_EVADE = 7; // On Creature Evade Attack
|
||||
public const EVENT_SPELLHIT = 8; // On Creature/Gameobject Spell Hit
|
||||
public const EVENT_RANGE = 9; // On Target In Range
|
||||
public const EVENT_OOC_LOS = 10; // On Target In Distance Out of Combat
|
||||
public const EVENT_RESPAWN = 11; // On Creature/Gameobject Respawn
|
||||
public const EVENT_TARGET_HEALTH_PCT = 12; // [DEPRECATED] On Target Health Percentage
|
||||
public const EVENT_VICTIM_CASTING = 13; // On Target Casting Spell
|
||||
public const EVENT_FRIENDLY_HEALTH = 14; // [DEPRECATED] On Friendly Health Deficit
|
||||
public const EVENT_FRIENDLY_IS_CC = 15; //
|
||||
public const EVENT_FRIENDLY_MISSING_BUFF = 16; // On Friendly Lost Buff
|
||||
public const EVENT_SUMMONED_UNIT = 17; // On Creature/Gameobject Summoned Unit
|
||||
public const EVENT_TARGET_MANA_PCT = 18; // [DEPRECATED] On Target Mana Percentage
|
||||
public const EVENT_ACCEPTED_QUEST = 19; // On Target Accepted Quest
|
||||
public const EVENT_REWARD_QUEST = 20; // On Target Rewarded Quest
|
||||
public const EVENT_REACHED_HOME = 21; // On Creature Reached Home
|
||||
public const EVENT_RECEIVE_EMOTE = 22; // On Receive Emote.
|
||||
public const EVENT_HAS_AURA = 23; // On Creature Has Aura
|
||||
public const EVENT_TARGET_BUFFED = 24; // On Target Buffed With Spell
|
||||
public const EVENT_RESET = 25; // After Combat, On Respawn or Spawn
|
||||
public const EVENT_IC_LOS = 26; // On Target In Distance In Combat
|
||||
public const EVENT_PASSENGER_BOARDED = 27; //
|
||||
public const EVENT_PASSENGER_REMOVED = 28; //
|
||||
public const EVENT_CHARMED = 29; // On Creature Charmed
|
||||
public const EVENT_CHARMED_TARGET = 30; // [DEPRECATED] On Target Charmed
|
||||
public const EVENT_SPELLHIT_TARGET = 31; // On Target Spell Hit
|
||||
public const EVENT_DAMAGED = 32; // On Creature Damaged
|
||||
public const EVENT_DAMAGED_TARGET = 33; // On Target Damaged
|
||||
public const EVENT_MOVEMENTINFORM = 34; // WAYPOINT_MOTION_TYPE = 2, POINT_MOTION_TYPE = 8
|
||||
public const EVENT_SUMMON_DESPAWNED = 35; // On Summoned Unit Despawned
|
||||
public const EVENT_CORPSE_REMOVED = 36; // On Creature Corpse Removed
|
||||
public const EVENT_AI_INIT = 37; //
|
||||
public const EVENT_DATA_SET = 38; // On Creature/Gameobject Data Set, Can be used with SMART_ACTION_SET_DATA
|
||||
public const EVENT_WAYPOINT_START = 39; // [DEPRECATED] On Creature Waypoint ID Started
|
||||
public const EVENT_WAYPOINT_REACHED = 40; // On Creature Waypoint ID Reached
|
||||
public const EVENT_TRANSPORT_ADDPLAYER = 41; // [RESERVED]
|
||||
public const EVENT_TRANSPORT_ADDCREATURE = 42; // [RESERVED]
|
||||
public const EVENT_TRANSPORT_REMOVE_PLAYER = 43; // [RESERVED]
|
||||
public const EVENT_TRANSPORT_RELOCATE = 44; // [RESERVED]
|
||||
public const EVENT_INSTANCE_PLAYER_ENTER = 45; // [RESERVED]
|
||||
public const EVENT_AREATRIGGER_ONTRIGGER = 46; //
|
||||
public const EVENT_QUEST_ACCEPTED = 47; // [RESERVED] On Target Quest Accepted
|
||||
public const EVENT_QUEST_OBJ_COMPLETION = 48; // [RESERVED] On Target Quest Objective Completed
|
||||
public const EVENT_QUEST_COMPLETION = 49; // [RESERVED] On Target Quest Completed
|
||||
public const EVENT_QUEST_REWARDED = 50; // [RESERVED] On Target Quest Rewarded
|
||||
public const EVENT_QUEST_FAIL = 51; // [RESERVED] On Target Quest Field
|
||||
public const EVENT_TEXT_OVER = 52; // On TEXT_OVER Event Triggered After SMART_ACTION_TALK
|
||||
public const EVENT_RECEIVE_HEAL = 53; // On Creature Received Healing
|
||||
public const EVENT_JUST_SUMMONED = 54; // On Creature Just spawned
|
||||
public const EVENT_WAYPOINT_PAUSED = 55; // On Creature Paused at Waypoint ID
|
||||
public const EVENT_WAYPOINT_RESUMED = 56; // On Creature Resumed after Waypoint ID
|
||||
public const EVENT_WAYPOINT_STOPPED = 57; // On Creature Stopped On Waypoint ID
|
||||
public const EVENT_WAYPOINT_ENDED = 58; // On Creature Waypoint Path Ended
|
||||
public const EVENT_TIMED_EVENT_TRIGGERED = 59; //
|
||||
public const EVENT_UPDATE = 60; //
|
||||
public const EVENT_LINK = 61; // Used to link together multiple events as a chain of events.
|
||||
public const EVENT_GOSSIP_SELECT = 62; // On gossip clicked (gossip_menu_option335).
|
||||
public const EVENT_JUST_CREATED = 63; //
|
||||
public const EVENT_GOSSIP_HELLO = 64; // On Right-Click Creature/Gameobject that have gossip enabled.
|
||||
public const EVENT_FOLLOW_COMPLETED = 65; //
|
||||
public const EVENT_EVENT_PHASE_CHANGE = 66; // [DEPRECATED] On event phase mask set
|
||||
public const EVENT_IS_BEHIND_TARGET = 67; // [DEPRECATED] On Creature is behind target.
|
||||
public const EVENT_GAME_EVENT_START = 68; // On game_event started.
|
||||
public const EVENT_GAME_EVENT_END = 69; // On game_event ended.
|
||||
public const EVENT_GO_LOOT_STATE_CHANGED = 70; //
|
||||
public const EVENT_GO_EVENT_INFORM = 71; //
|
||||
public const EVENT_ACTION_DONE = 72; //
|
||||
public const EVENT_ON_SPELLCLICK = 73; //
|
||||
public const EVENT_FRIENDLY_HEALTH_PCT = 74; //
|
||||
public const EVENT_DISTANCE_CREATURE = 75; // On creature guid OR any instance of creature entry is within distance.
|
||||
public const EVENT_DISTANCE_GAMEOBJECT = 76; // On gameobject guid OR any instance of gameobject entry is within distance.
|
||||
public const EVENT_COUNTER_SET = 77; // If the value of specified counterID is equal to a specified value
|
||||
public const EVENT_SCENE_START = 78; // [RESERVED] don't use on 3.3.5a
|
||||
public const EVENT_SCENE_TRIGGER = 79; // [RESERVED] don't use on 3.3.5a
|
||||
public const EVENT_SCENE_CANCEL = 80; // [RESERVED] don't use on 3.3.5a
|
||||
public const EVENT_SCENE_COMPLETE = 81; // [RESERVED] don't use on 3.3.5a
|
||||
public const EVENT_SUMMONED_UNIT_DIES = 82; //
|
||||
public const EVENT_ON_SPELL_CAST = 83; // On Spell::cast
|
||||
public const EVENT_ON_SPELL_FAILED = 84; // On Unit::InterruptSpell
|
||||
public const EVENT_ON_SPELL_START = 85; // On Spell::prapare
|
||||
public const EVENT_ON_DESPAWN = 86; // On before creature removed
|
||||
|
||||
public const FLAG_NO_REPEAT = 0x0001;
|
||||
public const FLAG_DIFFICULTY_0 = 0x0002;
|
||||
public const FLAG_DIFFICULTY_1 = 0x0004;
|
||||
public const FLAG_DIFFICULTY_2 = 0x0008;
|
||||
public const FLAG_DIFFICULTY_3 = 0x0010;
|
||||
public const FLAG_DEBUG_ONLY = 0x0080;
|
||||
public const FLAG_NO_RESET = 0x0100;
|
||||
public const FLAG_WHILE_CHARMED = 0x0200;
|
||||
public const FLAG_ALL_DIFFICULTIES = self::FLAG_DIFFICULTY_0 | self::FLAG_DIFFICULTY_1 | self::FLAG_DIFFICULTY_2 | self::FLAG_DIFFICULTY_3;
|
||||
|
||||
private const EVENT_CELL_TPL = '[tooltip name=e-#rowIdx#]%1$s[/tooltip][span tooltip=e-#rowIdx#]%2$s[/span]';
|
||||
|
||||
private array $data = array( // param 1-5 - int > 0: type, array: [fn, newIdx, extraParam]; error class: int
|
||||
self::EVENT_UPDATE_IC => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax
|
||||
self::EVENT_UPDATE_OOC => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax
|
||||
self::EVENT_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // HPMin%, HPMax%, RepeatMin, RepeatMax
|
||||
self::EVENT_MANA_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // ManaMin%, ManaMax%, RepeatMin, RepeatMax
|
||||
self::EVENT_AGGRO => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_KILL => [['numRange', -1, true], null, null, Type::NPC, null, 0], // CooldownMin0, CooldownMax1, playerOnly2, else creature entry3
|
||||
self::EVENT_DEATH => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_EVADE => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_SPELLHIT => [Type::SPELL, ['magicSchool', 10, false], ['numRange', -1, true], null, null, 0], // SpellID, School, CooldownMin, CooldownMax
|
||||
self::EVENT_RANGE => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDist, MaxDist, RepeatMin, RepeatMax
|
||||
self::EVENT_OOC_LOS => [['hostilityMode', 10, false], null, ['numRange', -1, true], null, null, 0], // hostilityModes, MaxRange, CooldownMin, CooldownMax
|
||||
self::EVENT_RESPAWN => [null, null, Type::ZONE, null, null, 0], // type, MapId, ZoneId
|
||||
self::EVENT_TARGET_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_VICTIM_CASTING => [['numRange', -1, true], null, Type::SPELL, null, null, 0], // RepeatMin, RepeatMax, spellid
|
||||
self::EVENT_FRIENDLY_HEALTH => [null, null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_FRIENDLY_IS_CC => [null, ['numRange', -1, true], null, null, null, 0], // Radius, RepeatMin, RepeatMax
|
||||
self::EVENT_FRIENDLY_MISSING_BUFF => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // SpellId, Radius, RepeatMin, RepeatMax
|
||||
self::EVENT_SUMMONED_UNIT => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // CreatureId(0 all), CooldownMin, CooldownMax
|
||||
self::EVENT_TARGET_MANA_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_ACCEPTED_QUEST => [Type::QUEST, ['numRange', -1, true], null, null, null, 0], // QuestID (0 = any), CooldownMin, CooldownMax
|
||||
self::EVENT_REWARD_QUEST => [Type::QUEST, ['numRange', -1, true], null, null, null, 0], // QuestID (0 = any), CooldownMin, CooldownMax
|
||||
self::EVENT_REACHED_HOME => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_RECEIVE_EMOTE => [Type::EMOTE, ['numRange', -1, true], null, null, null, 0], // EmoteId, CooldownMin, CooldownMax, condition, val1, val2, val3
|
||||
self::EVENT_HAS_AURA => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // Param1 = SpellID, Param2 = Stack amount, Param3/4 RepeatMin, RepeatMax
|
||||
self::EVENT_TARGET_BUFFED => [Type::SPELL, null, ['numRange', -1, true], null, null, 0], // Param1 = SpellID, Param2 = Stack amount, Param3/4 RepeatMin, RepeatMax
|
||||
self::EVENT_RESET => [null, null, null, null, null, 0], // Called after combat, when the creature respawn and spawn.
|
||||
self::EVENT_IC_LOS => [['hostilityMode', 10, false], null, ['numRange', -1, true], null, null, 0], // hostilityModes, MaxRnage, CooldownMin, CooldownMax
|
||||
self::EVENT_PASSENGER_BOARDED => [['numRange', -1, true], null, null, null, null, 0], // CooldownMin, CooldownMax
|
||||
self::EVENT_PASSENGER_REMOVED => [['numRange', -1, true], null, null, null, null, 0], // CooldownMin, CooldownMax
|
||||
self::EVENT_CHARMED => [null, null, null, null, null, 0], // onRemove (0 - on apply, 1 - on remove)
|
||||
self::EVENT_CHARMED_TARGET => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_SPELLHIT_TARGET => [Type::SPELL, ['magicSchool', 10, false], ['numRange', -1, true], null, null, 0], // SpellID, School, CooldownMin, CooldownMax
|
||||
self::EVENT_DAMAGED => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDmg, MaxDmg, CooldownMin, CooldownMax
|
||||
self::EVENT_DAMAGED_TARGET => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinDmg, MaxDmg, CooldownMin, CooldownMax
|
||||
self::EVENT_MOVEMENTINFORM => [['motionType', 10, false], null, null, null, null, 0], // MovementType(any), PointID
|
||||
self::EVENT_SUMMON_DESPAWNED => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // Entry, CooldownMin, CooldownMax
|
||||
self::EVENT_CORPSE_REMOVED => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_AI_INIT => [null, null, null, null, null, 0], // NONE
|
||||
self::EVENT_DATA_SET => [null, null, ['numRange', -1, true], null, null, 0], // Id, Value, CooldownMin, CooldownMax
|
||||
self::EVENT_WAYPOINT_START => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_WAYPOINT_REACHED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any)
|
||||
self::EVENT_TRANSPORT_ADDPLAYER => [null, null, null, null, null, 2], // NONE
|
||||
self::EVENT_TRANSPORT_ADDCREATURE => [null, null, null, null, null, 2], // Entry (0 any)
|
||||
self::EVENT_TRANSPORT_REMOVE_PLAYER => [null, null, null, null, null, 2], // NONE
|
||||
self::EVENT_TRANSPORT_RELOCATE => [null, null, null, null, null, 2], // PointId
|
||||
self::EVENT_INSTANCE_PLAYER_ENTER => [null, null, null, null, null, 2], // Team (0 any), CooldownMin, CooldownMax
|
||||
self::EVENT_AREATRIGGER_ONTRIGGER => [Type::AREATRIGGER, null, null, null, null, 0], // TriggerId(0 any)
|
||||
self::EVENT_QUEST_ACCEPTED => [null, null, null, null, null, 2], // none
|
||||
self::EVENT_QUEST_OBJ_COMPLETION => [null, null, null, null, null, 2], // none
|
||||
self::EVENT_QUEST_COMPLETION => [null, null, null, null, null, 2], // none
|
||||
self::EVENT_QUEST_REWARDED => [null, null, null, null, null, 2], // none
|
||||
self::EVENT_QUEST_FAIL => [null, null, null, null, null, 2], // none
|
||||
self::EVENT_TEXT_OVER => [null, Type::NPC, null, null, null, 0], // GroupId from creature_text, creature entry who talks (0 any)
|
||||
self::EVENT_RECEIVE_HEAL => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // MinHeal, MaxHeal, CooldownMin, CooldownMax
|
||||
self::EVENT_JUST_SUMMONED => [null, null, null, null, null, 0], // none
|
||||
self::EVENT_WAYPOINT_PAUSED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any)
|
||||
self::EVENT_WAYPOINT_RESUMED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any)
|
||||
self::EVENT_WAYPOINT_STOPPED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any)
|
||||
self::EVENT_WAYPOINT_ENDED => [null, null, null, null, null, 0], // PointId(0any), pathID(0any)
|
||||
self::EVENT_TIMED_EVENT_TRIGGERED => [null, null, null, null, null, 0], // id
|
||||
self::EVENT_UPDATE => [['numRange', 10, true], null, ['numRange', -1, true], null, null, 0], // InitialMin, InitialMax, RepeatMin, RepeatMax
|
||||
self::EVENT_LINK => [null, null, null, null, null, 0], // INTERNAL USAGE, no params, used to link together multiple events, does not use any extra resources to iterate event lists needlessly
|
||||
self::EVENT_GOSSIP_SELECT => [null, null, null, null, null, 0], // menuID, actionID
|
||||
self::EVENT_JUST_CREATED => [null, null, null, null, null, 0], // none
|
||||
self::EVENT_GOSSIP_HELLO => [null, null, null, null, null, 0], // noReportUse (for GOs)
|
||||
self::EVENT_FOLLOW_COMPLETED => [null, null, null, null, null, 0], // none
|
||||
self::EVENT_EVENT_PHASE_CHANGE => [null, null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_IS_BEHIND_TARGET => [['numRange', -1, true], null, null, null, null, 1], // UNUSED, DO NOT REUSE
|
||||
self::EVENT_GAME_EVENT_START => [Type::WORLDEVENT, null, null, null, null, 0], // game_event.Entry
|
||||
self::EVENT_GAME_EVENT_END => [Type::WORLDEVENT, null, null, null, null, 0], // game_event.Entry
|
||||
self::EVENT_GO_LOOT_STATE_CHANGED => [['lootState', 10, false], null, null, null, null, 0], // go LootState
|
||||
self::EVENT_GO_EVENT_INFORM => [null, null, null, null, null, 0], // eventId
|
||||
self::EVENT_ACTION_DONE => [null, null, null, null, null, 0], // eventId (SharedDefines.EventId)
|
||||
self::EVENT_ON_SPELLCLICK => [null, null, null, null, null, 0], // clicker (unit)
|
||||
self::EVENT_FRIENDLY_HEALTH_PCT => [['numRange', 10, false], null, ['numRange', -1, true], null, null, 0], // minHpPct, maxHpPct, repeatMin, repeatMax
|
||||
self::EVENT_DISTANCE_CREATURE => [null, Type::NPC, null, ['numRange', -1, true], null, 0], // guid, entry, distance, repeat
|
||||
self::EVENT_DISTANCE_GAMEOBJECT => [null, Type::OBJECT, null, ['numRange', -1, true], null, 0], // guid, entry, distance, repeat
|
||||
self::EVENT_COUNTER_SET => [null, null, ['numRange', -1, true], null, null, 0], // id, value, cooldownMin, cooldownMax
|
||||
self::EVENT_SCENE_START => [null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::EVENT_SCENE_TRIGGER => [null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::EVENT_SCENE_CANCEL => [null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::EVENT_SCENE_COMPLETE => [null, null, null, null, null, 2], // don't use on 3.3.5a
|
||||
self::EVENT_SUMMONED_UNIT_DIES => [Type::NPC, ['numRange', -1, true], null, null, null, 0], // CreatureId(0 all), CooldownMin, CooldownMax
|
||||
self::EVENT_ON_SPELL_CAST => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
|
||||
self::EVENT_ON_SPELL_FAILED => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
|
||||
self::EVENT_ON_SPELL_START => [Type::SPELL, ['numRange', -1, true], null, null, null, 0], // SpellID, CooldownMin, CooldownMax
|
||||
self::EVENT_ON_DESPAWN => [null, null, null, null, null, 0] // NONE
|
||||
);
|
||||
|
||||
private array $jsGlobals = [];
|
||||
|
||||
public function __construct(
|
||||
private int $id,
|
||||
public readonly int $type,
|
||||
public readonly int $phaseMask,
|
||||
public readonly int $chance,
|
||||
private int $flags,
|
||||
private array $param,
|
||||
private SmartAI &$smartAI)
|
||||
{
|
||||
// additional parameters
|
||||
Util::checkNumeric($this->param, NUM_CAST_INT);
|
||||
$this->param = array_pad($this->param, 15, '');
|
||||
}
|
||||
|
||||
public function process() : array
|
||||
{
|
||||
$body =
|
||||
$footer = '';
|
||||
|
||||
$phases = Util::mask2bits($this->phaseMask, 1) ?: [0];
|
||||
$eventTT = Lang::smartAI('eventTT', array_merge([$this->type, $phases, $this->chance, $this->flags], $this->param));
|
||||
|
||||
for ($i = 0; $i < 5; $i++)
|
||||
{
|
||||
$eParams = $this->data[$this->type];
|
||||
|
||||
if (is_array($eParams[$i]))
|
||||
{
|
||||
[$fn, $idx, $extraParam] = $eParams[$i];
|
||||
|
||||
if ($idx < 0)
|
||||
$footer = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam);
|
||||
else
|
||||
$this->param[$idx] = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam);
|
||||
}
|
||||
else if (is_int($eParams[$i]) && $this->param[$i])
|
||||
$this->jsGlobals[$eParams[$i]][$this->param[$i]] = $this->param[$i];
|
||||
}
|
||||
|
||||
// non-generic cases
|
||||
switch ($this->type)
|
||||
{
|
||||
case self::EVENT_UPDATE_IC: // 0 - In combat.
|
||||
case self::EVENT_UPDATE_OOC: // 1 - Out of combat.
|
||||
if ($this->smartAI->srcType == SmartAI::SRC_TYPE_ACTIONLIST)
|
||||
$this->param[11] = 1;
|
||||
// do not break;
|
||||
case self::EVENT_GOSSIP_HELLO: // 64 - On Right-Click Creature/Gameobject that have gossip enabled.
|
||||
if ($this->smartAI->srcType == SmartAI::SRC_TYPE_OBJECT)
|
||||
$footer = array(
|
||||
$this->param[0] == 1,
|
||||
$this->param[0] == 2,
|
||||
);
|
||||
break;
|
||||
case self::EVENT_RESPAWN: // 11 - On Creature/Gameobject Respawn in Zone/Map
|
||||
if ($this->param[0] == 1) // per map
|
||||
{
|
||||
switch ($this->param[1])
|
||||
{
|
||||
case 0: $this->param[10] = Lang::maps('EasternKingdoms'); break;
|
||||
case 1: $this->param[10] = Lang::maps('Kalimdor'); break;
|
||||
case 530: $this->param[10] = Lang::maps('Outland'); break;
|
||||
case 571: $this->param[10] = Lang::maps('Northrend'); break;
|
||||
default:
|
||||
if ($aId = DB::Aowow()->selectCell('SELECT `id` FROM ?_zones WHERE `mapId` = ?d', $this->param[1]))
|
||||
{
|
||||
$this->param[11] = $aId;
|
||||
$this->jsGlobals[Type::ZONE][$aId] = $aId;
|
||||
}
|
||||
else
|
||||
$this->param[11] = '[span class=q10]Unknown Map[/span] #'.$this->param[1];
|
||||
};
|
||||
}
|
||||
else if ($this->param[0] == 2) // per zone
|
||||
$this->param[11] = $this->param[2];
|
||||
|
||||
break;
|
||||
case self::EVENT_LINK: // 61 - Used to link together multiple events as a chain of events.
|
||||
if ($links = DB::World()->selectCol('SELECT `id` FROM smart_scripts WHERE `link` = ?d AND `entryorguid` = ?d AND `source_type` = ?d', $this->id, $this->smartAI->entry, $this->smartAI->srcType))
|
||||
$this->param[10] = Lang::concat($links, Lang::CONCAT_OR, fn($x) => "#[b]".$x."[/b]");
|
||||
break;
|
||||
case self::EVENT_GOSSIP_SELECT: // 62 - On gossip clicked (gossip_menu_option335).
|
||||
$gmo = DB::World()->selectRow(
|
||||
'SELECT gmo.`OptionText` AS "text_loc0" {, gmol.`OptionText` AS text_loc?d }
|
||||
FROM gossip_menu_option gmo
|
||||
LEFT JOIN gossip_menu_option_locale gmol ON gmo.`MenuID` = gmol.`MenuID` AND gmo.`OptionID` = gmol.`OptionID` AND gmol.`Locale` = ?d
|
||||
WHERE gmo.`MenuId` = ?d AND gmo.`OptionID` = ?d',
|
||||
Lang::getLocale() != Locale::EN ? Lang::getLocale()->value : DBSIMPLE_SKIP,
|
||||
Lang::getLocale()->json(),
|
||||
$this->param[0], $this->param[1]
|
||||
);
|
||||
|
||||
if ($gmo)
|
||||
$this->param[10] = Util::jsEscape(Util::localizedString($gmo, 'text'));
|
||||
else
|
||||
trigger_error('SmartAI::event - could not find gossip menu option for event #'.$this->type);
|
||||
break;
|
||||
case self::EVENT_DISTANCE_CREATURE: // 75 - On creature guid OR any instance of creature entry is within distance.
|
||||
if ($this->param[0])
|
||||
if ($_ = $this->resolveGuid(Type::NPC, $this->param[0]))
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$this->param[0]] = $this->param[0];
|
||||
$this->param[10] = $_;
|
||||
}
|
||||
// do not break;
|
||||
case self::EVENT_DISTANCE_GAMEOBJECT: // 76 - On gameobject guid OR any instance of gameobject entry is within distance.
|
||||
if ($this->param[0] && !$this->param[10])
|
||||
{
|
||||
if ($_ = $this->resolveGuid(Type::OBJECT, $this->param[0]))
|
||||
{
|
||||
$this->jsGlobals[Type::OBJECT][$this->param[0]] = $this->param[0];
|
||||
$this->param[10] = $_;
|
||||
}
|
||||
}
|
||||
else if ($this->param[1])
|
||||
$this->param[10] = $this->param[1];
|
||||
else if (!$this->param[10])
|
||||
trigger_error('SmartAI::event - entity for event #'.$this->type.' not defined');
|
||||
break;
|
||||
case self::EVENT_EVENT_PHASE_CHANGE: // 66 - On event phase mask set
|
||||
$this->param[10] = Lang::concat(Util::mask2bits($this->param[0]), Lang::CONCAT_OR);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->smartAI->addJsGlobals($this->jsGlobals);
|
||||
|
||||
$body = Lang::smartAI('events', $this->type, 0, $this->param) ?? Lang::smartAI('eventUNK', [$this->type]);
|
||||
if ($footer)
|
||||
$footer = Lang::smartAI('events', $this->type, 1, (array)$footer);
|
||||
|
||||
// resolve conditionals
|
||||
$i = 0;
|
||||
while (strstr($body, ')?') && $i++ < 3)
|
||||
$body = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $body);
|
||||
|
||||
$i = 0;
|
||||
while (strstr($footer, ')?') && $i++ < 3)
|
||||
$footer = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $footer);
|
||||
|
||||
if ($_ = $this->formatFlags())
|
||||
$footer = $_ . ($footer ? '; '.$footer : '');
|
||||
|
||||
if (User::isInGroup(U_GROUP_EMPLOYEE))
|
||||
{
|
||||
if ($eParams[5] == 1)
|
||||
$footer = '[span class=rep2]DEPRECATED[/span] ' . $footer;
|
||||
else if ($eParams[5] == 2)
|
||||
$footer = '[span class=rep0]RESERVED[/span] ' . $footer;
|
||||
}
|
||||
|
||||
// wrap body in tooltip
|
||||
return [sprintf(self::EVENT_CELL_TPL, $eventTT, $body), $footer];
|
||||
}
|
||||
|
||||
public function hasPhases() : bool
|
||||
{
|
||||
return $this->phaseMask == 0;
|
||||
}
|
||||
|
||||
private function formatFlags() : string
|
||||
{
|
||||
$flags = $this->flags;
|
||||
|
||||
if (($flags & self::FLAG_ALL_DIFFICULTIES) == self::FLAG_ALL_DIFFICULTIES)
|
||||
$flags &= ~self::FLAG_ALL_DIFFICULTIES;
|
||||
|
||||
$ef = [];
|
||||
for ($i = 1; $i <= self::FLAG_WHILE_CHARMED; $i <<= 1)
|
||||
if ($flags & $i)
|
||||
if ($x = Lang::smartAI('eventFlags', $i))
|
||||
$ef[] = $x;
|
||||
|
||||
return Lang::concat($ef);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
185
includes/components/SmartAI/SmartTarget.class.php
Normal file
185
includes/components/SmartAI/SmartTarget.class.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
// TrinityCore - SmartAI
|
||||
class SmartTarget
|
||||
{
|
||||
use SmartHelper;
|
||||
|
||||
public const TARGET_NONE = 0; // None.
|
||||
public const TARGET_SELF = 1; // Self cast.
|
||||
public const TARGET_VICTIM = 2; // Our current target. (ie: highest aggro)
|
||||
public const TARGET_HOSTILE_SECOND_AGGRO = 3; // Second highest aggro.
|
||||
public const TARGET_HOSTILE_LAST_AGGRO = 4; // Dead last on aggro.
|
||||
public const TARGET_HOSTILE_RANDOM = 5; // Just any random target on our threat list.
|
||||
public const TARGET_HOSTILE_RANDOM_NOT_TOP = 6; // Any random target except top threat.
|
||||
public const TARGET_ACTION_INVOKER = 7; // Unit who caused this Event to occur.
|
||||
public const TARGET_POSITION = 8; // Use xyz from event params.
|
||||
public const TARGET_CREATURE_RANGE = 9; // (Random?) creature with specified ID within specified range.
|
||||
public const TARGET_CREATURE_GUID = 10; // Creature with specified GUID.
|
||||
public const TARGET_CREATURE_DISTANCE = 11; // Creature with specified ID within distance. (Different from #9?)
|
||||
public const TARGET_STORED = 12; // Uses pre-stored target(list)
|
||||
public const TARGET_GAMEOBJECT_RANGE = 13; // (Random?) object with specified ID within specified range.
|
||||
public const TARGET_GAMEOBJECT_GUID = 14; // Object with specified GUID.
|
||||
public const TARGET_GAMEOBJECT_DISTANCE = 15; // Object with specified ID within distance. (Different from #13?)
|
||||
public const TARGET_INVOKER_PARTY = 16; // Invoker's party members
|
||||
public const TARGET_PLAYER_RANGE = 17; // (Random?) player within specified range.
|
||||
public const TARGET_PLAYER_DISTANCE = 18; // (Random?) player within specified distance. (Different from #17?)
|
||||
public const TARGET_CLOSEST_CREATURE = 19; // Closest creature with specified ID within specified range.
|
||||
public const TARGET_CLOSEST_GAMEOBJECT = 20; // Closest object with specified ID within specified range.
|
||||
public const TARGET_CLOSEST_PLAYER = 21; // Closest player within specified range.
|
||||
public const TARGET_ACTION_INVOKER_VEHICLE = 22; // Unit's vehicle who caused this Event to occur
|
||||
public const TARGET_OWNER_OR_SUMMONER = 23; // Unit's owner or summoner
|
||||
public const TARGET_THREAT_LIST = 24; // All units on creature's threat list
|
||||
public const TARGET_CLOSEST_ENEMY = 25; // Any attackable target (creature or player) within maxDist
|
||||
public const TARGET_CLOSEST_FRIENDLY = 26; // Any friendly unit (creature, player or pet) within maxDist
|
||||
public const TARGET_LOOT_RECIPIENTS = 27; // All tagging players
|
||||
public const TARGET_FARTHEST = 28; // Farthest unit on the threat list
|
||||
public const TARGET_VEHICLE_PASSENGER = 29; // Vehicle can target unit in given seat
|
||||
public const TARGET_CLOSEST_UNSPAWNED_GO = 30; // entry(0any), maxDist
|
||||
|
||||
private const TARGET_TPL = '[tooltip name=t-#rowIdx#]%1$s[/tooltip][span class=tip tooltip=t-#rowIdx#]%2$s[/span]';
|
||||
|
||||
private array $targets = array(
|
||||
self::TARGET_NONE => [null, null, null, null], // NONE
|
||||
self::TARGET_SELF => [null, null, null, null], // Self cast
|
||||
self::TARGET_VICTIM => [null, null, null, null], // Our current target (ie: highest aggro)
|
||||
self::TARGET_HOSTILE_SECOND_AGGRO => [null, null, null, null], // Second highest aggro, maxdist, playerOnly, powerType + 1
|
||||
self::TARGET_HOSTILE_LAST_AGGRO => [null, null, null, null], // Dead last on aggro, maxdist, playerOnly, powerType + 1
|
||||
self::TARGET_HOSTILE_RANDOM => [null, null, null, null], // Just any random target on our threat list, maxdist, playerOnly, powerType + 1
|
||||
self::TARGET_HOSTILE_RANDOM_NOT_TOP => [null, null, null, null], // Any random target except top threat, maxdist, playerOnly, powerType + 1
|
||||
self::TARGET_ACTION_INVOKER => [null, null, null, null], // Unit who caused this Event to occur
|
||||
self::TARGET_POSITION => [null, null, null, null], // use xyz from event params
|
||||
self::TARGET_CREATURE_RANGE => [Type::NPC, ['numRange', 10, false], null, null], // CreatureEntry(0any), minDist, maxDist
|
||||
self::TARGET_CREATURE_GUID => [null, Type::NPC, null, null], // guid, entry
|
||||
self::TARGET_CREATURE_DISTANCE => [Type::NPC, null, null, null], // CreatureEntry(0any), maxDist
|
||||
self::TARGET_STORED => [null, null, null, null], // id, uses pre-stored target(list)
|
||||
self::TARGET_GAMEOBJECT_RANGE => [Type::OBJECT, ['numRange', 10, false], null, null], // entry(0any), min, max
|
||||
self::TARGET_GAMEOBJECT_GUID => [null, Type::OBJECT, null, null], // guid, entry
|
||||
self::TARGET_GAMEOBJECT_DISTANCE => [Type::OBJECT, null, null, null], // entry(0any), maxDist
|
||||
self::TARGET_INVOKER_PARTY => [null, null, null, null], // invoker's party members
|
||||
self::TARGET_PLAYER_RANGE => [['numRange', 10, false], null, null, null], // min, max
|
||||
self::TARGET_PLAYER_DISTANCE => [null, null, null, null], // maxDist
|
||||
self::TARGET_CLOSEST_CREATURE => [Type::NPC, null, null, null], // CreatureEntry(0any), maxDist, dead?
|
||||
self::TARGET_CLOSEST_GAMEOBJECT => [Type::OBJECT, null, null, null], // entry(0any), maxDist
|
||||
self::TARGET_CLOSEST_PLAYER => [null, null, null, null], // maxDist
|
||||
self::TARGET_ACTION_INVOKER_VEHICLE => [null, null, null, null], // Unit's vehicle who caused this Event to occur
|
||||
self::TARGET_OWNER_OR_SUMMONER => [null, null, null, null], // Unit's owner or summoner, Use Owner/Charmer of this unit
|
||||
self::TARGET_THREAT_LIST => [null, null, null, null], // All units on creature's threat list, maxdist
|
||||
self::TARGET_CLOSEST_ENEMY => [null, null, null, null], // maxDist, playerOnly
|
||||
self::TARGET_CLOSEST_FRIENDLY => [null, null, null, null], // maxDist, playerOnly
|
||||
self::TARGET_LOOT_RECIPIENTS => [null, null, null, null], // all players that have tagged this creature (for kill credit)
|
||||
self::TARGET_FARTHEST => [null, null, null, null], // maxDist, playerOnly, isInLos
|
||||
self::TARGET_VEHICLE_PASSENGER => [null, null, null, null], // seatMask (0 - all seats)
|
||||
self::TARGET_CLOSEST_UNSPAWNED_GO => [Type::OBJECT, null, null, null] // entry(0any), maxDist
|
||||
);
|
||||
|
||||
private array $jsGlobals = [];
|
||||
|
||||
public function __construct(
|
||||
private int $id,
|
||||
public readonly int $type,
|
||||
private array $param,
|
||||
private array $worldPos,
|
||||
private SmartAI &$smartAI)
|
||||
{
|
||||
// additional parameters
|
||||
Util::checkNumeric($this->param, NUM_CAST_INT);
|
||||
Util::checkNumeric($this->worldPos, NUM_CAST_FLOAT);
|
||||
$this->param = array_pad($this->param, 15, '');
|
||||
$this->worldPos = array_pad($this->worldPos, 4, 0.0);
|
||||
}
|
||||
|
||||
public function process() : string
|
||||
{
|
||||
$target = '';
|
||||
|
||||
$targetTT = Lang::smartAI('targetTT', array_merge([$this->type], $this->param, $this->worldPos));
|
||||
|
||||
for ($i = 0; $i < 4; $i++)
|
||||
{
|
||||
$tParams = $this->targets[$this->type];
|
||||
|
||||
if (is_array($tParams[$i]))
|
||||
{
|
||||
[$fn, $idx, $extraParam] = $tParams[$i];
|
||||
|
||||
$this->param[$idx] = $this->{$fn}($this->param[$i], $this->param[$i + 1], $extraParam);
|
||||
}
|
||||
else if (is_int($tParams[$i]) && $this->param[$i])
|
||||
$this->jsGlobals[$tParams[$i]][$this->param[$i]] = $this->param[$i];
|
||||
}
|
||||
|
||||
// non-generic cases
|
||||
switch ($this->type)
|
||||
{
|
||||
case self::TARGET_HOSTILE_SECOND_AGGRO:
|
||||
case self::TARGET_HOSTILE_LAST_AGGRO:
|
||||
case self::TARGET_HOSTILE_RANDOM:
|
||||
case self::TARGET_HOSTILE_RANDOM_NOT_TOP:
|
||||
if ($this->param[2])
|
||||
$this->param[10] = Lang::spell('powerTypes', $this->param[2] - 1);
|
||||
break;
|
||||
case self::TARGET_VEHICLE_PASSENGER:
|
||||
if ($this->param[0])
|
||||
$this->param[10] = Lang::concat(Util::mask2bits($this->param[0]));
|
||||
break;
|
||||
case self::TARGET_CREATURE_GUID:
|
||||
if ($_ = $this->resolveGuid(Type::NPC, $this->param[0]))
|
||||
{
|
||||
$this->jsGlobals[Type::NPC][$_] = $_;
|
||||
$this->param[10] = $_;
|
||||
}
|
||||
break;
|
||||
case self::TARGET_GAMEOBJECT_GUID:
|
||||
if ($_ = $this->resolveGuid(Type::OBJECT, $this->param[0]))
|
||||
{
|
||||
$this->jsGlobals[Type::OBJECT][$_] = $_;
|
||||
$this->param[10] = $_;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$this->smartAI->addJsGlobals($this->jsGlobals);
|
||||
|
||||
$target = Lang::smartAI('targets', $this->type, $this->param) ?? Lang::smartAI('targetUNK', [$this->type]);
|
||||
|
||||
// resolve conditionals
|
||||
$i = 0;
|
||||
while (strstr($target, ')?') && $i++ < 3)
|
||||
$target = preg_replace_callback('/\(([^\)]*?)\)\?([^:]*):(([^;]*);*);/i', fn($m) => $m[1] ? $m[2] : $m[3], $target);
|
||||
|
||||
// wrap in tooltip (suspend action-tooltip)
|
||||
return '[/span]'.sprintf(self::TARGET_TPL, $targetTT, $target).'[span tooltip=a-#rowIdx#]';
|
||||
}
|
||||
|
||||
public function getWorldPos() : array
|
||||
{
|
||||
return $this->worldPos;
|
||||
}
|
||||
|
||||
// not really feasable. Too many target types can be players or creatures, depending on context
|
||||
public function getTalkSource(bool &$playerSrc = false) : int
|
||||
{
|
||||
if ($this->type == SmartTarget::TARGET_CLOSEST_PLAYER)
|
||||
$playerSrc = true;
|
||||
|
||||
return match ($this->type)
|
||||
{
|
||||
SmartTarget::TARGET_CREATURE_GUID => $this->resolveGuid(Type::NPC, $this->param[0]) ?? 0,
|
||||
SmartTarget::TARGET_CREATURE_RANGE,
|
||||
SmartTarget::TARGET_CREATURE_DISTANCE,
|
||||
SmartTarget::TARGET_CLOSEST_CREATURE => $this->param[0],
|
||||
SmartTarget::TARGET_CLOSEST_PLAYER,
|
||||
SmartTarget::TARGET_SELF => $this->smartAI->getEntry(),
|
||||
default => $this->smartAI->getEntry()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -21,88 +23,68 @@ class CommunityContent
|
||||
private static array $jsGlobals = [];
|
||||
private static array $subjCache = [];
|
||||
|
||||
private static string $coQuery = '
|
||||
SELECT
|
||||
c.*,
|
||||
a1.displayName AS user,
|
||||
a2.displayName AS editUser,
|
||||
a3.displayName AS deleteUser,
|
||||
a4.displayName AS responseUser,
|
||||
IFNULL(SUM(ur.value), 0) AS rating,
|
||||
SUM(IF(ur.userId > 0 AND ur.userId = ?d, ur.value, 0)) AS userRating,
|
||||
SUM(IF( r.userId > 0 AND r.userId = ?d, 1, 0)) AS userReported
|
||||
FROM
|
||||
?_comments c
|
||||
JOIN
|
||||
?_account a1 ON c.userId = a1.id
|
||||
LEFT JOIN
|
||||
?_account a2 ON c.editUserId = a2.id
|
||||
LEFT JOIN
|
||||
?_account a3 ON c.deleteUserId = a3.id
|
||||
LEFT JOIN
|
||||
?_account a4 ON c.responseUserId = a4.id
|
||||
LEFT JOIN
|
||||
?_user_ratings ur ON c.id = ur.entry AND ur.type = ?d
|
||||
LEFT JOIN
|
||||
?_reports r ON r.subject = c.id AND r.mode = 1 AND r.reason = 19
|
||||
WHERE
|
||||
c.replyTo = ?d AND c.type = ?d AND c.typeId = ?d AND
|
||||
((c.flags & ?d) = 0 OR c.userId = ?d OR ?d)
|
||||
GROUP BY
|
||||
c.id
|
||||
ORDER BY
|
||||
rating ASC
|
||||
';
|
||||
private static string $coCountQuery =
|
||||
'SELECT COUNT(1)
|
||||
FROM ?_comments c
|
||||
WHERE c.`replyTo` = ?d AND c.`type` = ?d AND c.`typeId` = ?d AND
|
||||
((c.`flags` & ?d) = 0 OR c.`userId` = ?d OR ?d)';
|
||||
|
||||
private static string $ssQuery = '
|
||||
SELECT s.id AS ARRAY_KEY, s.id, a.displayName AS user, s.date, s.width, s.height, s.caption, IF(s.status & ?d, 1, 0) AS "sticky", s.type, s.typeId
|
||||
private static string $coQuery =
|
||||
'SELECT c.*,
|
||||
a1.`username` AS "user",
|
||||
a2.`username` AS "editUser",
|
||||
a3.`username` AS "deleteUser",
|
||||
a4.`username` AS "responseUser",
|
||||
IFNULL(SUM(ur.`value`), 0) AS "rating",
|
||||
SUM(IF(ur.`userId` > 0 AND ur.`userId` = ?d, ur.`value`, 0)) AS "userRating",
|
||||
IF(r.`id` IS NULL, 0, 1) AS "userReported"
|
||||
FROM ?_comments c
|
||||
JOIN ?_account a1 ON c.`userId` = a1.`id`
|
||||
LEFT JOIN ?_account a2 ON c.`editUserId` = a2.`id`
|
||||
LEFT JOIN ?_account a3 ON c.`deleteUserId` = a3.`id`
|
||||
LEFT JOIN ?_account a4 ON c.`responseUserId` = a4.`id`
|
||||
LEFT JOIN ?_user_ratings ur ON c.`id` = ur.`entry` AND ur.`type` = ?d
|
||||
LEFT JOIN ?_reports r ON r.`subject` = c.`id` AND r.`mode` = ?d AND r.`userId` = ?d
|
||||
WHERE c.`replyTo` = ?d AND c.`type` = ?d AND c.`typeId` = ?d AND
|
||||
((c.`flags` & ?d) = 0 OR c.`userId` = ?d OR ?d)
|
||||
GROUP BY c.`id`
|
||||
ORDER BY c.`date` ASC';
|
||||
|
||||
private static string $ssQuery =
|
||||
'SELECT s.`id` AS ARRAY_KEY, s.`id`, a.`username` AS "user", s.`date`, s.`width`, s.`height`, s.`caption`, IF(s.`status` & ?d, 1, 0) AS "sticky", s.`type`, s.`typeId`
|
||||
FROM ?_screenshots s
|
||||
LEFT JOIN ?_account a ON s.userIdOwner = a.id
|
||||
WHERE {s.userIdOwner = ?d AND }{s.type = ? AND }{s.typeId = ? AND }s.status & ?d AND (s.status & ?d) = 0
|
||||
{ORDER BY ?# DESC}
|
||||
{LIMIT ?d}
|
||||
';
|
||||
LEFT JOIN ?_account a ON s.`userIdOwner` = a.`id`
|
||||
WHERE { s.`userIdOwner` = ?d AND }{ s.`type` = ? AND }{ s.`typeId` = ? AND } s.`status` & ?d AND (s.`status` & ?d) = 0
|
||||
{ ORDER BY ?# DESC }
|
||||
{ LIMIT ?d }';
|
||||
|
||||
private static string $viQuery = '
|
||||
SELECT v.id AS ARRAY_KEY, v.id, a.displayName AS user, v.date, v.videoId, v.caption, IF(v.status & ?d, 1, 0) AS "sticky", v.type, v.typeId
|
||||
private static string $viQuery =
|
||||
'SELECT v.`id` AS ARRAY_KEY, v.`id`, a.`username` AS "user", v.`date`, v.`videoId`, v.`caption`, IF(v.`status` & ?d, 1, 0) AS "sticky", v.`type`, v.`typeId`
|
||||
FROM ?_videos v
|
||||
LEFT JOIN ?_account a ON v.userIdOwner = a.id
|
||||
WHERE {v.userIdOwner = ?d AND }{v.type = ? AND }{v.typeId = ? AND }v.status & ?d AND (v.status & ?d) = 0
|
||||
{ORDER BY ?# DESC}
|
||||
{LIMIT ?d}
|
||||
';
|
||||
LEFT JOIN ?_account a ON v.`userIdOwner` = a.`id`
|
||||
WHERE { v.`userIdOwner` = ?d AND }{ v.`type` = ? AND }{ v.`typeId` = ? AND } v.`status` & ?d AND (v.`status` & ?d) = 0
|
||||
{ ORDER BY ?# DESC }
|
||||
{ LIMIT ?d }';
|
||||
|
||||
private static string $previewQuery = '
|
||||
SELECT
|
||||
c.id,
|
||||
c.body AS preview,
|
||||
c.date,
|
||||
c.replyTo AS commentid,
|
||||
IF(c.flags & ?d, 1, 0) AS deleted,
|
||||
IF(c.type <> 0, c.type, c2.type) AS type,
|
||||
IF(c.typeId <> 0, c.typeId, c2.typeId) AS typeId,
|
||||
IFNULL(SUM(ur.value), 0) AS rating,
|
||||
a.displayName AS user
|
||||
FROM
|
||||
?_comments c
|
||||
JOIN
|
||||
?_account a ON c.userId = a.id
|
||||
LEFT JOIN
|
||||
?_user_ratings ur ON ur.entry = c.id AND ur.userId <> 0 AND ur.`type` = 1
|
||||
LEFT JOIN
|
||||
?_comments c2 ON c.replyTo = c2.id
|
||||
WHERE
|
||||
{c.userId = ?d AND}
|
||||
{c.replyTo <> ?d AND}
|
||||
{c.replyTo = ?d AND}
|
||||
((c.flags & ?d) = 0 OR c.userId = ?d OR ?d)
|
||||
GROUP BY
|
||||
c.id
|
||||
ORDER BY
|
||||
date DESC
|
||||
LIMIT
|
||||
?d
|
||||
';
|
||||
private static string $previewQuery =
|
||||
'SELECT c.`id`,
|
||||
c.`body` AS "preview",
|
||||
c.`date`,
|
||||
c.`replyTo` AS "commentid",
|
||||
IF(c.`flags` & ?d, 1, 0) AS "deleted",
|
||||
IF(c.`type` <> 0, c.`type`, c2.`type`) AS "type",
|
||||
IF(c.`typeId` <> 0, c.`typeId`, c2.`typeId`) AS "typeId",
|
||||
IFNULL(SUM(ur.`value`), 0) AS "rating",
|
||||
a.`username` AS "user"
|
||||
FROM ?_comments c
|
||||
JOIN ?_account a ON c.`userId` = a.`id`
|
||||
LEFT JOIN ?_user_ratings ur ON ur.`entry` = c.`id` AND ur.`userId` <> 0 AND ur.`type` = 1
|
||||
LEFT JOIN ?_comments c2 ON c.`replyTo` = c2.`id`
|
||||
WHERE %s
|
||||
((c.`flags` & ?d) = 0 OR c.`userId` = ?d OR ?d)
|
||||
GROUP BY c.`id`
|
||||
ORDER BY c.`date` DESC
|
||||
{ LIMIT ?d }';
|
||||
|
||||
private static function addSubject(int $type, int $typeId) : void
|
||||
{
|
||||
@@ -118,7 +100,7 @@ class CommunityContent
|
||||
if (!$_)
|
||||
continue;
|
||||
|
||||
$obj = Type::newList($type, [CFG_SQL_LIMIT_NONE, ['id', $_]]);
|
||||
$obj = Type::newList($type, [Cfg::get('SQL_LIMIT_NONE'), ['id', $_]]);
|
||||
if (!$obj)
|
||||
continue;
|
||||
|
||||
@@ -127,24 +109,48 @@ class CommunityContent
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCommentPreviews(array $params = [], ?int &$nFound = 0, bool $dateFmt = true) : array
|
||||
public static function getCommentPreviews(array $opt = [], ?int &$nFound = 0, bool $dateFmt = true) : array
|
||||
{
|
||||
/*
|
||||
purged:0, <- doesnt seem to be used anymore
|
||||
domain:'live' <- irrelevant for our case
|
||||
*/
|
||||
|
||||
$comments = DB::Aowow()->selectPage(
|
||||
$nFound,
|
||||
self::$previewQuery,
|
||||
// add default values
|
||||
$opt += ['user' => 0, 'unrated' => 0, 'comments' => 0, 'replies' => 0];
|
||||
|
||||
$w = [];
|
||||
if ($opt['user'])
|
||||
$w[] = sprintf('c.userId = %d AND', $opt['user']);
|
||||
if ($opt['unrated'])
|
||||
$w[] = 'ur.entry IS NULL AND';
|
||||
if ($opt['comments'] && !$opt['replies'])
|
||||
$w[] = 'c.replyTo = 0 AND';
|
||||
else if (!$opt['comments'] && $opt['replies'])
|
||||
$w[] = 'c.replyTo <> 0 AND';
|
||||
// else
|
||||
// pick both and no extra constraint needed for that
|
||||
|
||||
$query = sprintf(self::$previewQuery, implode(' ', $w));
|
||||
|
||||
$comments = DB::Aowow()->select(
|
||||
$query,
|
||||
CC_FLAG_DELETED,
|
||||
empty($params['user']) ? DBSIMPLE_SKIP : $params['user'],
|
||||
empty($params['replies']) ? DBSIMPLE_SKIP : 0, // i dont know, how to switch the sign around
|
||||
!empty($params['replies']) ? DBSIMPLE_SKIP : 0,
|
||||
CC_FLAG_DELETED,
|
||||
User::$id,
|
||||
User::isInGroup(U_GROUP_COMMENTS_MODERATOR),
|
||||
CFG_SQL_LIMIT_DEFAULT
|
||||
Cfg::get('SQL_LIMIT_DEFAULT')
|
||||
);
|
||||
|
||||
if (!$comments)
|
||||
return [];
|
||||
|
||||
$nFound = DB::Aowow()->selectCell(
|
||||
substr_replace($query, 'SELECT COUNT(*) ', 0, strpos($query, 'FROM')),
|
||||
CC_FLAG_DELETED,
|
||||
User::$id,
|
||||
User::isInGroup(U_GROUP_COMMENTS_MODERATOR),
|
||||
DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
foreach ($comments as $c)
|
||||
@@ -160,10 +166,11 @@ class CommunityContent
|
||||
$c['subject'] = self::$subjCache[$c['type']][$c['typeId']];
|
||||
|
||||
// format date
|
||||
$c['elapsed'] = time() - $c['date'];
|
||||
$c['date'] = $dateFmt ? date(Util::$dateFormatInternal, $c['date']) : intVal($c['date']);
|
||||
|
||||
// remove commentid if not looking for replies
|
||||
if (empty($params['replies']))
|
||||
if (empty($opt['replies']))
|
||||
unset($c['commentid']);
|
||||
|
||||
// format text for listview
|
||||
@@ -176,7 +183,7 @@ class CommunityContent
|
||||
}
|
||||
}
|
||||
|
||||
return $comments;
|
||||
return array_values($comments);
|
||||
}
|
||||
|
||||
public static function getCommentReplies(int $commentId, int $limit = 0, ?int &$nFound = 0) : array
|
||||
@@ -185,10 +192,13 @@ class CommunityContent
|
||||
$query = $limit > 0 ? self::$coQuery.' LIMIT '.$limit : self::$coQuery;
|
||||
|
||||
// get replies
|
||||
$results = DB::Aowow()->selectPage($nFound, $query, User::$id, User::$id, RATING_COMMENT, $commentId, 0, 0, CC_FLAG_DELETED, User::$id, User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
|
||||
if ($results = DB::Aowow()->select($query, User::$id, RATING_COMMENT, Report::MODE_COMMENT, User::$id, $commentId, 0, 0, CC_FLAG_DELETED, User::$id, User::isInGroup(U_GROUP_COMMENTS_MODERATOR)))
|
||||
{
|
||||
$nFound = DB::Aowow()->selectCell(self::$coCountQuery, $commentId, 0, 0, CC_FLAG_DELETED, User::$id, User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
|
||||
|
||||
foreach ($results as $r)
|
||||
{
|
||||
(new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals);
|
||||
Markup::parseTags($r['body'], self::$jsGlobals);
|
||||
|
||||
$reply = array(
|
||||
'commentid' => $commentId,
|
||||
@@ -211,20 +221,21 @@ class CommunityContent
|
||||
|
||||
$replies[] = $reply;
|
||||
}
|
||||
}
|
||||
|
||||
return $replies;
|
||||
}
|
||||
|
||||
public static function getScreenshotsForManager($type, $typeId, $userId = 0)
|
||||
{
|
||||
$screenshots = DB::Aowow()->select('
|
||||
SELECT s.id, a.displayName AS user, s.date, s.width, s.height, s.type, s.typeId, s.caption, s.status, s.status AS "flags"
|
||||
$screenshots = DB::Aowow()->select(
|
||||
'SELECT s.`id`, a.`username` AS "user", s.`date`, s.`width`, s.`height`, s.`type`, s.`typeId`, s.`caption`, s.`status`, s.`status` AS "flags"
|
||||
FROM ?_screenshots s
|
||||
LEFT JOIN ?_account a ON s.userIdOwner = a.id
|
||||
LEFT JOIN ?_account a ON s.`userIdOwner` = a.`id`
|
||||
WHERE
|
||||
{ s.type = ?d}
|
||||
{ AND s.typeId = ?d}
|
||||
{ s.userIdOwner = ?d}
|
||||
{ s.`type` = ?d}
|
||||
{ AND s.`typeId` = ?d}
|
||||
{ s.`userIdOwner` = ?d}
|
||||
LIMIT 100',
|
||||
$userId ? DBSIMPLE_SKIP : $type,
|
||||
$userId ? DBSIMPLE_SKIP : $typeId,
|
||||
@@ -289,10 +300,10 @@ class CommunityContent
|
||||
{
|
||||
// i GUESS .. ss_getALL ? everything : pending
|
||||
$nFound = 0;
|
||||
$pages = DB::Aowow()->select('
|
||||
SELECT s.`type`, s.`typeId`, count(1) AS "count", MIN(s.`date`) AS "date"
|
||||
$pages = DB::Aowow()->select(
|
||||
'SELECT s.`type`, s.`typeId`, COUNT(1) AS "count", MIN(s.`date`) AS "date"
|
||||
FROM ?_screenshots s
|
||||
{WHERE (s.status & ?d) = 0}
|
||||
{ WHERE (s.`status` & ?d) = 0 }
|
||||
GROUP BY s.`type`, s.`typeId`',
|
||||
$all ? DBSIMPLE_SKIP : CC_FLAG_APPROVED | CC_FLAG_DELETED
|
||||
);
|
||||
@@ -310,7 +321,7 @@ class CommunityContent
|
||||
if (!$ids)
|
||||
continue;
|
||||
|
||||
$obj = Type::newList($t, [CFG_SQL_LIMIT_NONE, ['id', $ids]]);
|
||||
$obj = Type::newList($t, [Cfg::get('SQL_LIMIT_NONE'), ['id', $ids]]);
|
||||
if (!$obj || $obj->error)
|
||||
continue;
|
||||
|
||||
@@ -341,14 +352,14 @@ class CommunityContent
|
||||
public static function getComments(int $type, int $typeId) : array
|
||||
{
|
||||
|
||||
$results = DB::Aowow()->query(self::$coQuery, User::$id, User::$id, RATING_COMMENT, 0, $type, $typeId, CC_FLAG_DELETED, User::$id, (int)User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
|
||||
$results = DB::Aowow()->query(self::$coQuery, User::$id, RATING_COMMENT, Report::MODE_COMMENT, User::$id, 0, $type, $typeId, CC_FLAG_DELETED, User::$id, (int)User::isInGroup(U_GROUP_COMMENTS_MODERATOR));
|
||||
$comments = [];
|
||||
|
||||
// additional informations
|
||||
$i = 0;
|
||||
foreach ($results as $r)
|
||||
{
|
||||
(new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals);
|
||||
Markup::parseTags($r['body'], self::$jsGlobals);
|
||||
|
||||
self::$jsGlobals[Type::USER][$r['userId']] = $r['userId'];
|
||||
|
||||
@@ -362,6 +373,7 @@ class CommunityContent
|
||||
'rating' => $r['rating'],
|
||||
'userRating' => $r['userRating'],
|
||||
'user' => $r['user'],
|
||||
'nreplies' => 0
|
||||
);
|
||||
|
||||
$c['replies'] = self::getCommentReplies($r['id'], 5, $c['nreplies']);
|
||||
@@ -372,7 +384,7 @@ class CommunityContent
|
||||
$c['responseroles'] = $r['responseRoles'];
|
||||
$c['responseuser'] = $r['responseUser'];
|
||||
|
||||
(new Markup($r['responseBody']))->parseGlobalsFromText(self::$jsGlobals);
|
||||
Markup::parseTags($r['responseBody'], self::$jsGlobals);
|
||||
}
|
||||
|
||||
if ($r['editCount']) // lastEdit
|
||||
@@ -396,9 +408,9 @@ class CommunityContent
|
||||
return $comments;
|
||||
}
|
||||
|
||||
public static function getVideos(int $typeOrUser = 0, int $typeId = 0, int &$nFound = 0, bool $dateFmt = true) : array
|
||||
public static function getVideos(int $typeOrUser = 0, int $typeId = 0, ?int &$nFound = 0, bool $dateFmt = true) : array
|
||||
{
|
||||
$videos = DB::Aowow()->selectPage($nFound, self::$viQuery,
|
||||
$videos = DB::Aowow()->select(self::$viQuery,
|
||||
CC_FLAG_STICKY,
|
||||
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
|
||||
@@ -406,7 +418,21 @@ class CommunityContent
|
||||
CC_FLAG_APPROVED,
|
||||
CC_FLAG_DELETED,
|
||||
!$typeOrUser ? 'date' : DBSIMPLE_SKIP,
|
||||
!$typeOrUser ? CFG_SQL_LIMIT_SEARCH : DBSIMPLE_SKIP
|
||||
!$typeOrUser ? Cfg::get('SQL_LIMIT_SEARCH') : DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
if (!$videos)
|
||||
return [];
|
||||
|
||||
$nFound = DB::Aowow()->selectCell(
|
||||
substr_replace(self::$viQuery, 'SELECT COUNT(*) ', 0, strpos(self::$viQuery, 'FROM')),
|
||||
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeId : DBSIMPLE_SKIP,
|
||||
CC_FLAG_APPROVED,
|
||||
CC_FLAG_DELETED,
|
||||
!$typeOrUser ? 'date' : DBSIMPLE_SKIP,
|
||||
DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
if ($typeOrUser <= 0) // not for search by type/typeId
|
||||
@@ -438,12 +464,12 @@ class CommunityContent
|
||||
unset($v['user']);
|
||||
}
|
||||
|
||||
return $videos;
|
||||
return array_values($videos);
|
||||
}
|
||||
|
||||
public static function getScreenshots(int $typeOrUser = 0, int $typeId = 0, int &$nFound = 0, bool $dateFmt = true) : array
|
||||
public static function getScreenshots(int $typeOrUser = 0, int $typeId = 0, ?int &$nFound = 0, bool $dateFmt = true) : array
|
||||
{
|
||||
$screenshots = DB::Aowow()->selectPage($nFound, self::$ssQuery,
|
||||
$screenshots = DB::Aowow()->select(self::$ssQuery,
|
||||
CC_FLAG_STICKY,
|
||||
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
|
||||
@@ -451,7 +477,21 @@ class CommunityContent
|
||||
CC_FLAG_APPROVED,
|
||||
CC_FLAG_DELETED,
|
||||
!$typeOrUser ? 'date' : DBSIMPLE_SKIP,
|
||||
!$typeOrUser ? CFG_SQL_LIMIT_SEARCH : DBSIMPLE_SKIP
|
||||
!$typeOrUser ? Cfg::get('SQL_LIMIT_SEARCH') : DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
if (!$screenshots)
|
||||
return [];
|
||||
|
||||
$nFound = DB::Aowow()->selectCell(
|
||||
substr_replace(self::$ssQuery, 'SELECT COUNT(*) ', 0, strpos(self::$ssQuery, 'FROM')),
|
||||
$typeOrUser < 0 ? -$typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeOrUser : DBSIMPLE_SKIP,
|
||||
$typeOrUser > 0 ? $typeId : DBSIMPLE_SKIP,
|
||||
CC_FLAG_APPROVED,
|
||||
CC_FLAG_DELETED,
|
||||
!$typeOrUser ? 'date' : DBSIMPLE_SKIP,
|
||||
DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
if ($typeOrUser <= 0) // not for search by type/typeId
|
||||
@@ -482,7 +522,7 @@ class CommunityContent
|
||||
unset($s['user']);
|
||||
}
|
||||
|
||||
return $screenshots;
|
||||
return array_values($screenshots);
|
||||
}
|
||||
|
||||
public static function getAll(int $type, int $typeId, array &$jsg) : array
|
||||
726
includes/components/filter.class.php
Normal file
726
includes/components/filter.class.php
Normal file
@@ -0,0 +1,726 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die("illegal access");
|
||||
|
||||
|
||||
trait TrProfilerFilter
|
||||
{
|
||||
protected array $parentCats = []; // used to validate ty-filter
|
||||
|
||||
protected function cbRegionCheck(string &$v) : bool
|
||||
{
|
||||
if (in_array($v, Util::$regions))
|
||||
{
|
||||
$this->parentCats[0] = $v; // directly redirect onto this region
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbServerCheck(string &$v) : bool
|
||||
{
|
||||
foreach (Profiler::getRealms() as $realm)
|
||||
if (Profiler::urlize($realm['name'], true) == $v)
|
||||
{
|
||||
$this->parentCats[1] = $v; // directly redirect onto this server
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Filter
|
||||
{
|
||||
private static $wCards = ['*' => '%', '?' => '_'];
|
||||
|
||||
public const CR_BOOLEAN = 1;
|
||||
public const CR_FLAG = 2;
|
||||
public const CR_NUMERIC = 3;
|
||||
public const CR_STRING = 4;
|
||||
public const CR_ENUM = 5;
|
||||
public const CR_STAFFFLAG = 6;
|
||||
public const CR_CALLBACK = 7;
|
||||
public const CR_NYI_PH = 999;
|
||||
|
||||
public const V_EQUAL = 8;
|
||||
public const V_RANGE = 9;
|
||||
public const V_LIST = 10;
|
||||
public const V_CALLBACK = 11;
|
||||
public const V_REGEX = 12;
|
||||
|
||||
protected const ENUM_ANY = -2323;
|
||||
protected const ENUM_NONE = -2324;
|
||||
|
||||
protected const PATTERN_NAME = '/[\p{C};%\\\\]/ui';
|
||||
protected const PATTERN_CRV = '/[\p{C};:%\\\\]/ui';
|
||||
protected const PATTERN_INT = '/\D/';
|
||||
public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/i';
|
||||
|
||||
protected const ENUM_FACTION = array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92,
|
||||
54, 946, 67, 1052, 749, 47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809,
|
||||
911, 890, 970, 169, 730, 72, 70, 932, 1156, 933, 510, 1126, 1067, 1073, 509, 941, 1105, 990, 934, 935,
|
||||
1094, 1119, 1124, 1064, 967, 1091, 59, 947, 81, 576, 922, 68, 1050, 1085, 889, 589, 270);
|
||||
protected const ENUM_CURRENCY = array(32572, 32569, 29736, 44128, 20560, 20559, 29434, 37829, 23247, 44990, 24368, 52027, 52030, 43016, 41596, 34052, 45624, 49426, 40752, 47241,
|
||||
40753, 29024, 24245, 26045, 26044, 38425, 29735, 24579, 24581, 32897, 22484, 52026, 52029, 4291, 28558, 43228, 34664, 47242, 52025, 52028,
|
||||
37836, 20558, 34597, 43589);
|
||||
protected const ENUM_EVENT = array( 372, 283, 285, 353, 420, 400, 284, 201, 374, 409, 141, 324, 321, 424, 423, 327, 341, 181, 404, 398,
|
||||
301);
|
||||
protected const ENUM_ZONE = array( 4494, 36, 2597, 3358, 45, 331, 3790, 4277, 16, 3524, 3, 3959, 719, 1584, 25, 1583, 2677, 3702, 3522, 4,
|
||||
3525, 3537, 46, 1941, 2918, 3905, 4024, 2817, 4395, 4378, 148, 393, 1657, 41, 2257, 405, 2557, 65, 4196, 1,
|
||||
14, 10, 15, 139, 12, 3430, 3820, 361, 357, 3433, 721, 394, 3923, 4416, 2917, 4272, 4820, 4264, 3483, 3562,
|
||||
267, 495, 4742, 3606, 210, 4812, 1537, 4710, 4080, 3457, 38, 4131, 3836, 3792, 2100, 2717, 493, 215, 3518, 3698,
|
||||
3456, 3523, 2367, 2159, 1637, 4813, 4298, 2437, 722, 491, 44, 3429, 3968, 796, 2057, 51, 3607, 3791, 3789, 209,
|
||||
3520, 3703, 3711, 1377, 3487, 130, 3679, 406, 1519, 4384, 33, 2017, 1477, 4075, 8, 440, 141, 3428, 3519, 3848,
|
||||
17, 2366, 3840, 3713, 3847, 3775, 4100, 1581, 3557, 3845, 4500, 4809, 47, 3849, 4265, 4493, 4228, 3698, 4406, 3714,
|
||||
3717, 3715, 717, 67, 3716, 457, 4415, 400, 1638, 1216, 85, 4723, 4722, 1337, 4273, 490, 1497, 206, 1196, 4603,
|
||||
718, 3277, 28, 40, 11, 4197, 618, 3521, 3805, 66, 1176, 1977);
|
||||
protected const ENUM_HEROICDUNGEON = array( 4494, 3790, 4277, 4196, 4416, 4272, 4820, 4264, 3562, 4131, 3792, 2367, 4813, 3791, 3789, 3848, 2366, 3713, 3847, 4100,
|
||||
4809, 3849, 4265, 4228, 3714, 3717, 3715, 3716, 4415, 4723, 206, 1196);
|
||||
protected const ENUM_MULTIMODERAID = array( 4812, 3456, 2159, 4500, 4493, 4722, 4273, 4603, 4987);
|
||||
protected const ENUM_HEROICRAID = array( 4987, 4812, 4722);
|
||||
protected const ENUM_CLASSS = array( null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false);
|
||||
protected const ENUM_RACE = array( null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false);
|
||||
protected const ENUM_PROFESSION = array( null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773);
|
||||
|
||||
public bool $error = false; // erroneous search fields
|
||||
|
||||
// item related
|
||||
public array $upgrades = []; // [itemId => slotId]
|
||||
public array $extraOpts = []; // score for statWeights
|
||||
public array $wtCnd = []; // DBType condition for statWeights
|
||||
|
||||
private array $cndSet = []; // db type query storage
|
||||
private array $rawData = [];
|
||||
|
||||
/* genericFilter: [FILTER_TYPE, colOrFnName, param1, param2]
|
||||
[self::CR_BOOLEAN, <string:colName>, <bool:isString>, null]
|
||||
[self::CR_FLAG, <string:colName>, <int:testBit>, <bool:matchAny>] # default param2: matchExact
|
||||
[self::CR_NUMERIC, <string:colName>, <int:NUM_FLAGS>, <bool:addExtraCol>]
|
||||
[self::CR_STRING, <string:colName>, <int:STR_FLAGS>, null]
|
||||
[self::CR_ENUM, <string:colName>, <bool:ANYNONE>, <bool:isEnumVal>] # param3 ? crv is val in enum : key in enum
|
||||
[self::CR_STAFFFLAG, <string:colName>, null, null]
|
||||
[self::CR_CALLBACK, <string:fnName>, <mixed:param1>, <mixed:param2>]
|
||||
[self::CR_NYI_PH, null, <int:returnVal>, param2] # mostly 1: to ignore this criterium; 0: to fail the whole query
|
||||
*/
|
||||
protected array $genericFilter = [];
|
||||
protected array $inputFields = []; // list of input fields defined per page - fieldName => [checkType, checkValue[, fieldIsArray]]
|
||||
protected array $enums = []; // validation for opt lists per page - criteriumID => [validOptionList]
|
||||
protected string $type = ''; // set by child
|
||||
protected array $parentCats = []; // used to validate ty-filter
|
||||
|
||||
// express Filters in template
|
||||
public string $fiInit = ''; // str: filter template (and init html form)
|
||||
public string $fiType = ''; // str: filter template (set without init)
|
||||
public array $fiSetCriteria = []; // fn params (cr, crs, crv)
|
||||
public array $fiSetWeights = []; // fn params (weights, nt, ids, stealth)
|
||||
public array $fiReputationCols = []; // fn params ([[factionId, factionName], ...])
|
||||
public array $fiExtraCols = []; //
|
||||
public string $query = ''; // as in url query params
|
||||
public array $values = []; // old fiData['v']
|
||||
public array $criteria = []; // old fiData['c']
|
||||
|
||||
// parse the provided request into a usable format
|
||||
public function __construct(string|array $data, array $opts = [])
|
||||
{
|
||||
$this->parentCats = $opts['parentCats'] ?? [];
|
||||
|
||||
// use fn fi_init() if we have a criteria selector, else use var fi_type
|
||||
if ($this->genericFilter)
|
||||
$this->fiInit = $this->type;
|
||||
else
|
||||
$this->fiType = $this->type;
|
||||
|
||||
if (is_array($data))
|
||||
$this->rawData = $data; // could set >query for consistency sake, but is not used when converting from POST
|
||||
|
||||
if (is_string($data))
|
||||
{
|
||||
// an error occured, while processing POST
|
||||
if (isset($_SESSION['error']['fi']))
|
||||
{
|
||||
$this->error = $_SESSION['error']['fi'] == get_class($this);
|
||||
unset($_SESSION['error']['fi']);
|
||||
}
|
||||
|
||||
$this->query = $data;
|
||||
$this->rawData = $this->transformGET($data);
|
||||
}
|
||||
|
||||
$this->initFields();
|
||||
}
|
||||
|
||||
public function mergeCat(array &$cats) : void
|
||||
{
|
||||
foreach ($this->parentCats as $idx => $cat)
|
||||
$cats[$idx] = $cat;
|
||||
}
|
||||
|
||||
private function &criteriaIterator() : \Generator
|
||||
{
|
||||
if (!$this->criteria)
|
||||
return;
|
||||
|
||||
for ($i = 0; $i < count($this->criteria['cr']); $i++)
|
||||
{
|
||||
// throws a notice if yielded directly "Only variable references should be yielded by reference"
|
||||
$v = [&$this->criteria['cr'][$i], &$this->criteria['crs'][$i], &$this->criteria['crv'][$i]];
|
||||
yield $i => $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************/
|
||||
/* get prepared values */
|
||||
/***********************/
|
||||
|
||||
public function buildGETParam(array $override = [], array $addCr = []) : string
|
||||
{
|
||||
$get = [];
|
||||
foreach (array_merge($this->criteria, $this->values, $override) as $k => $v)
|
||||
{
|
||||
if (isset($addCr[$k]))
|
||||
{
|
||||
$v = $v ? array_merge((array)$v, (array)$addCr[$k]) : $addCr[$k];
|
||||
unset($addCr[$k]);
|
||||
}
|
||||
|
||||
if ($v === '' || $v === null || $v === [])
|
||||
continue;
|
||||
|
||||
$get[$k] = $k.'='.(is_array($v) ? implode(':', $v) : $v);
|
||||
}
|
||||
|
||||
// no criteria were set, so no merge occured .. append
|
||||
if ($addCr)
|
||||
{
|
||||
$get['cr'] = 'cr='.$addCr['cr'];
|
||||
$get['crs'] = 'crs='.$addCr['crs'];
|
||||
$get['crv'] = 'crv='.$addCr['crv'];
|
||||
}
|
||||
|
||||
return implode(';', $get);
|
||||
}
|
||||
|
||||
public function getConditions() : array
|
||||
{
|
||||
if (!$this->cndSet)
|
||||
{
|
||||
// values
|
||||
$this->cndSet = $this->createSQLForValues();
|
||||
|
||||
// criteria
|
||||
foreach ($this->criteriaIterator() as &$_cr)
|
||||
if ($cnd = $this->createSQLForCriterium(...$_cr))
|
||||
$this->cndSet[] = $cnd;
|
||||
|
||||
if ($this->cndSet) // Note: TYPE_SOUND does not use 'match any'
|
||||
array_unshift($this->cndSet, empty($this->values['ma']) ? 'AND' : 'OR');
|
||||
}
|
||||
|
||||
return $this->cndSet;
|
||||
}
|
||||
|
||||
public function getSetCriteria(int ...$cr) : array
|
||||
{
|
||||
if (!$cr || !$this->fiSetCriteria)
|
||||
return $this->fiSetCriteria;
|
||||
|
||||
return array_intersect($this->fiSetCriteria['cr'], $cr);
|
||||
}
|
||||
|
||||
|
||||
/**********************/
|
||||
/* input sanitization */
|
||||
/**********************/
|
||||
|
||||
private function transformGET(string $get) : array
|
||||
{
|
||||
if (!$get)
|
||||
return [];
|
||||
|
||||
$data = [];
|
||||
foreach (explode(';', $get) as $field)
|
||||
{
|
||||
if (!strstr($field, '='))
|
||||
{
|
||||
trigger_error('Filter::transformGET - malformed GET string', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
[$k, $v] = explode('=', $field);
|
||||
|
||||
if (!isset($this->inputFields[$k]))
|
||||
{
|
||||
trigger_error('Filter::transformGET - GET param not in filter: '.$k, E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$asArray = $this->inputFields[$k][2];
|
||||
|
||||
$data[$k] = $asArray ? explode(':', $v) : $v;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function initFields() : void
|
||||
{
|
||||
foreach ($this->inputFields as $inp => [$type, $valid, $asArray])
|
||||
{
|
||||
$var = in_array($inp, ['cr', 'crs', 'crv']) ? 'criteria' : 'values';
|
||||
|
||||
if (!isset($this->rawData[$inp]) || $this->rawData[$inp] === '')
|
||||
{
|
||||
$this->$var[$inp] = $asArray ? [] : null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$val = $this->rawData[$inp];
|
||||
|
||||
if ($asArray)
|
||||
{
|
||||
// quirk: in the POST step criteria can be [[''], null, null] if not selected.
|
||||
$buff = [];
|
||||
foreach ((array)$val as $v) // can be string|int in POST step if only one value present
|
||||
if ($v !== '' && $this->checkInput($type, $valid, $v))
|
||||
$buff[] = $v;
|
||||
|
||||
$this->$var[$inp] = $buff;
|
||||
}
|
||||
else
|
||||
$this->$var[$inp] = $this->checkInput($type, $valid, $val) ? $val : null;
|
||||
}
|
||||
}
|
||||
|
||||
public function evalCriteria() : void // [cr]iterium, [cr].[s]ign, [cr].[v]alue
|
||||
{
|
||||
if (empty($this->criteria['cr']) && empty($this->criteria['crs']) && empty($this->criteria['crv']))
|
||||
return;
|
||||
else if (empty($this->criteria['cr']) || empty($this->criteria['crs']) || empty($this->criteria['crv']))
|
||||
{
|
||||
unset($this->criteria['cr']);
|
||||
unset($this->criteria['crs']);
|
||||
unset($this->criteria['crv']);
|
||||
|
||||
trigger_error('Filter::setCriteria - one of cr, crs, crv is missing', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$_cr = &$this->criteria['cr'];
|
||||
$_crs = &$this->criteria['crs'];
|
||||
$_crv = &$this->criteria['crv'];
|
||||
|
||||
if (count($_cr) != count($_crv) || count($_cr) != count($_crs) || count($_cr) > 5 || count($_crs) > 5 /*|| count($_crv) > 5*/)
|
||||
{
|
||||
// use min provided criterion as basis; 5 criteria at most
|
||||
$min = max(5, min(count($_cr), count($_crv), count($_crs)));
|
||||
if (count($_cr) > $min)
|
||||
array_splice($_cr, $min);
|
||||
|
||||
if (count($_crv) > $min)
|
||||
array_splice($_crv, $min);
|
||||
|
||||
if (count($_crs) > $min)
|
||||
array_splice($_crs, $min);
|
||||
|
||||
trigger_error('Filter::setCriteria - cr, crs, crv are imbalanced', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($_cr); $i++)
|
||||
{
|
||||
// conduct filter specific checks & casts here
|
||||
$unsetme = false;
|
||||
if (isset($this->genericFilter[$_cr[$i]]))
|
||||
{
|
||||
$gf = $this->genericFilter[$_cr[$i]];
|
||||
switch ($gf[0])
|
||||
{
|
||||
case self::CR_NUMERIC:
|
||||
$_ = $_crs[$i];
|
||||
if (!Util::checkNumeric($_crv[$i], $gf[2]) || !$this->int2Op($_))
|
||||
$unsetme = true;
|
||||
break;
|
||||
case self::CR_BOOLEAN:
|
||||
case self::CR_FLAG:
|
||||
$_ = $_crs[$i];
|
||||
if (!$this->int2Bool($_))
|
||||
$unsetme = true;
|
||||
break;
|
||||
case self::CR_ENUM:
|
||||
case self::CR_STAFFFLAG:
|
||||
if (!Util::checkNumeric($_crs[$i], NUM_CAST_INT))
|
||||
$unsetme = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$unsetme && intval($_cr[$i]) && $_crs[$i] !== '' && $_crv[$i] !== '')
|
||||
continue;
|
||||
|
||||
unset($_cr[$i]);
|
||||
unset($_crs[$i]);
|
||||
unset($_crv[$i]);
|
||||
|
||||
trigger_error('Filter::setCriteria - generic check failed ["'.$_cr[$i].'", "'.$_crs[$i].'", "'.$_crv[$i].'"]', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
}
|
||||
|
||||
$this->fiSetCriteria = array(
|
||||
'cr' => $_cr,
|
||||
'crs' => $_crs,
|
||||
'crv' => $_crv
|
||||
);
|
||||
}
|
||||
|
||||
public function evalWeights() : void
|
||||
{
|
||||
// both empty: not in use; not an error
|
||||
if (!$this->values['wt'] && !$this->values['wtv'])
|
||||
return;
|
||||
|
||||
// one empty: erroneous manual input?
|
||||
if (!$this->values['wt'] || !$this->values['wtv'])
|
||||
{
|
||||
unset($this->values['wt']);
|
||||
unset($this->values['wtv']);
|
||||
|
||||
trigger_error('Filter::setWeights - one of wt, wtv is missing', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$_wt = &$this->values['wt'];
|
||||
$_wtv = &$this->values['wtv'];
|
||||
|
||||
$nwt = count($_wt);
|
||||
$nwtv = count($_wtv);
|
||||
|
||||
if ($nwt != $nwtv)
|
||||
{
|
||||
trigger_error('Filter::setWeights - wt, wtv are imbalanced', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
}
|
||||
|
||||
if ($nwt > $nwtv)
|
||||
array_splice($_wt, $nwtv);
|
||||
else if ($nwtv > $nwt)
|
||||
array_splice($_wtv, $nwt);
|
||||
|
||||
$this->fiSetWeights = [$_wt, $_wtv];
|
||||
}
|
||||
|
||||
protected function checkInput(int $type, mixed $valid, mixed &$val, bool $recursive = false) : bool
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case self::V_EQUAL:
|
||||
if (gettype($valid) == 'integer')
|
||||
$val = intval($val);
|
||||
else if (gettype($valid) == 'double')
|
||||
$val = floatval($val);
|
||||
else /* if (gettype($valid) == 'string') */
|
||||
$val = strval($val);
|
||||
|
||||
if ($valid == $val)
|
||||
return true;
|
||||
|
||||
break;
|
||||
case self::V_LIST:
|
||||
if (!Util::checkNumeric($val, NUM_CAST_INT))
|
||||
return false;
|
||||
|
||||
foreach ($valid as $k => $v)
|
||||
{
|
||||
if (gettype($v) != 'array')
|
||||
continue;
|
||||
|
||||
if ($this->checkInput(self::V_RANGE, $v, $val, true))
|
||||
return true;
|
||||
|
||||
unset($valid[$k]);
|
||||
}
|
||||
|
||||
if (in_array($val, $valid))
|
||||
return true;
|
||||
|
||||
break;
|
||||
case self::V_RANGE:
|
||||
if (Util::checkNumeric($val, NUM_CAST_INT) && $val >= $valid[0] && $val <= $valid[1])
|
||||
return true;
|
||||
|
||||
break;
|
||||
case self::V_CALLBACK:
|
||||
if ($this->$valid($val))
|
||||
return true;
|
||||
|
||||
break;
|
||||
case self::V_REGEX:
|
||||
if (!preg_match($valid, $val))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$recursive)
|
||||
{
|
||||
trigger_error('Filter::checkInput - check failed [type: '.$type.' valid: '.((string)$valid).' val: '.((string)$val).']', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function transformToken(string $string, bool $exact) : string
|
||||
{
|
||||
// escape manually entered _; entering % should be prohibited
|
||||
$string = str_replace('_', '\\_', $string);
|
||||
|
||||
// now replace search wildcards with sql wildcards
|
||||
$string = strtr($string, self::$wCards);
|
||||
|
||||
return sprintf($exact ? '%s' : '%%%s%%', $string);
|
||||
}
|
||||
|
||||
protected function tokenizeString(array $fields, string $string = '', bool $exact = false, bool $shortStr = false) : array
|
||||
{
|
||||
if (!$string && $this->values['na'])
|
||||
$string = $this->values['na'];
|
||||
|
||||
$qry = [];
|
||||
foreach ($fields as $f)
|
||||
{
|
||||
$sub = [];
|
||||
$parts = $exact ? [$string] : array_filter(explode(' ', $string));
|
||||
foreach ($parts as $p)
|
||||
{
|
||||
if ($p[0] == '-' && (mb_strlen($p) > 3 || $shortStr))
|
||||
$sub[] = [$f, $this->transformToken(mb_substr($p, 1), $exact), '!'];
|
||||
else if ($p[0] != '-' && (mb_strlen($p) > 2 || $shortStr))
|
||||
$sub[] = [$f, $this->transformToken($p, $exact)];
|
||||
}
|
||||
|
||||
// single cnd?
|
||||
if (!$sub)
|
||||
continue;
|
||||
else if (count($sub) > 1)
|
||||
array_unshift($sub, 'AND');
|
||||
else
|
||||
$sub = $sub[0];
|
||||
|
||||
$qry[] = $sub;
|
||||
}
|
||||
|
||||
// single cnd?
|
||||
if (!$qry)
|
||||
{
|
||||
trigger_error('Filter::tokenizeString - could not tokenize string: '.$string, E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
}
|
||||
else if (count($qry) > 1)
|
||||
array_unshift($qry, 'OR');
|
||||
else
|
||||
$qry = $qry[0];
|
||||
|
||||
return $qry;
|
||||
}
|
||||
|
||||
protected function int2Op(mixed &$op) : bool
|
||||
{
|
||||
$op = match ($op) {
|
||||
1 => '>',
|
||||
2 => '>=',
|
||||
3 => '=',
|
||||
4 => '<=',
|
||||
5 => '<',
|
||||
6 => '!=',
|
||||
default => null
|
||||
};
|
||||
|
||||
return $op !== null;
|
||||
}
|
||||
|
||||
protected function int2Bool(mixed &$op) : bool
|
||||
{
|
||||
$op = match ($op) {
|
||||
1 => true,
|
||||
2 => false,
|
||||
default => null
|
||||
};
|
||||
|
||||
return $op !== null;
|
||||
}
|
||||
|
||||
protected function list2Mask(array $list, bool $noOffset = false) : int
|
||||
{
|
||||
$mask = 0x0;
|
||||
$o = $noOffset ? 0 : 1; // schoolMask requires this..?
|
||||
|
||||
foreach ($list as $itm)
|
||||
$mask += (1 << (intval($itm) - $o));
|
||||
|
||||
return $mask;
|
||||
}
|
||||
|
||||
|
||||
/**************************/
|
||||
/* create conditions from */
|
||||
/* generic criteria */
|
||||
/**************************/
|
||||
|
||||
private function genericBoolean(string $field, int $op, bool $isString) : ?array
|
||||
{
|
||||
if ($this->int2Bool($op))
|
||||
{
|
||||
$value = $isString ? '' : 0;
|
||||
$operator = $op ? '!' : null;
|
||||
|
||||
return [$field, $value, $operator];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function genericBooleanFlags(string $field, int $value, int $op, ?bool $matchAny = false) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($op))
|
||||
return null;
|
||||
|
||||
if (!$op)
|
||||
return [[$field, $value, '&'], 0];
|
||||
else if ($matchAny)
|
||||
return [[$field, $value, '&'], 0, '!'];
|
||||
else
|
||||
return [[$field, $value, '&'], $value];
|
||||
}
|
||||
|
||||
private function genericString(string $field, string $value, ?int $strFlags) : ?array
|
||||
{
|
||||
$strFlags ??= 0x0;
|
||||
|
||||
if ($strFlags & STR_LOCALIZED)
|
||||
$field .= '_loc'.Lang::getLocale()->value;
|
||||
|
||||
return $this->tokenizeString([$field], $value, $strFlags & STR_MATCH_EXACT, $strFlags & STR_ALLOW_SHORT);
|
||||
}
|
||||
|
||||
private function genericNumeric(string $field, int|float $value, int $op, int $typeCast) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($value, $typeCast))
|
||||
return null;
|
||||
|
||||
if ($this->int2Op($op))
|
||||
return [$field, $value, $op];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function genericEnum(string $field, mixed $value) : ?array
|
||||
{
|
||||
if (is_bool($value))
|
||||
return [$field, 0, ($value ? '>' : '<=')];
|
||||
else if ($value == self::ENUM_ANY)
|
||||
return [$field, 0, '!'];
|
||||
else if ($value == self::ENUM_NONE)
|
||||
return [$field, 0];
|
||||
else if ($value !== null)
|
||||
return [$field, $value];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function genericCriterion(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
[$crType, $colOrFn, $param1, $param2] = array_pad($this->genericFilter[$cr], 4, null);
|
||||
$result = null;
|
||||
|
||||
switch ($crType)
|
||||
{
|
||||
case self::CR_NUMERIC:
|
||||
$result = $this->genericNumeric($colOrFn, $crv, $crs, $param1);
|
||||
break;
|
||||
case self::CR_FLAG:
|
||||
$result = $this->genericBooleanFlags($colOrFn, $param1, $crs, $param2);
|
||||
break;
|
||||
case self::CR_STAFFFLAG:
|
||||
if (User::isInGroup(U_GROUP_EMPLOYEE) && $crs > 0)
|
||||
$result = $this->genericBooleanFlags($colOrFn, (1 << ($crs - 1)), true);
|
||||
break;
|
||||
case self::CR_BOOLEAN:
|
||||
$result = $this->genericBoolean($colOrFn, $crs, !empty($param1));
|
||||
break;
|
||||
case self::CR_STRING:
|
||||
$result = $this->genericString($colOrFn, $crv, $param1);
|
||||
break;
|
||||
case self::CR_ENUM:
|
||||
if (!$param2 && isset($this->enums[$cr][$crs]))
|
||||
$result = $this->genericEnum($colOrFn, $this->enums[$cr][$crs]);
|
||||
if ($param2 && in_array($crs, $this->enums[$cr]))
|
||||
$result = $this->genericEnum($colOrFn, $crs);
|
||||
else if ($param1 && ($crs == self::ENUM_ANY || $crs == self::ENUM_NONE))
|
||||
$result = $this->genericEnum($colOrFn, $crs);
|
||||
break;
|
||||
case self::CR_CALLBACK:
|
||||
$result = $this->{$colOrFn}($cr, $crs, $crv, $param1, $param2);
|
||||
break;
|
||||
case self::CR_NYI_PH: // do not limit with not implemented filters
|
||||
if (is_int($param1))
|
||||
return [$param1];
|
||||
|
||||
// for nonsensical values; compare against 0
|
||||
if ($this->int2Op($crs) && Util::checkNumeric($crv))
|
||||
{
|
||||
if ($crs == '=')
|
||||
$crs = '==';
|
||||
|
||||
return eval('return ('.$crv.' '.$crs.' 0);') ? [1] : [0];
|
||||
}
|
||||
else
|
||||
return [0];
|
||||
}
|
||||
|
||||
if ($result && $crType == self::CR_NUMERIC && !empty($param2))
|
||||
$this->fiExtraCols[] = $cr;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/***********************************/
|
||||
/* create conditions from */
|
||||
/* non-generic values and criteria */
|
||||
/***********************************/
|
||||
|
||||
protected function createSQLForCriterium(int &$cr, int &$crs, string &$crv) : array
|
||||
{
|
||||
if (!$this->genericFilter) // criteria not in use - no error
|
||||
return [];
|
||||
|
||||
if (isset($this->genericFilter[$cr]))
|
||||
if ($genCr = $this->genericCriterion($cr, $crs, $crv))
|
||||
return $genCr;
|
||||
|
||||
trigger_error('Filter::createSQLForCriterium - received unhandled criterium: ["'.$cr.'", "'.$crs.'", "'.$crv.'"]', E_USER_NOTICE);
|
||||
$this->error = true;
|
||||
|
||||
unset($cr, $crs, $crv);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
abstract protected function createSQLForValues() : array;
|
||||
}
|
||||
|
||||
?>
|
||||
69
includes/components/frontend/announcement.class.php
Normal file
69
includes/components/frontend/announcement.class.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Announcement implements \JsonSerializable
|
||||
{
|
||||
public const MODE_PAGE_TOP = 0;
|
||||
public const MODE_CONTENT_TOP = 1;
|
||||
|
||||
public const STATUS_DISABLED = 0;
|
||||
public const STATUS_ENABLED = 1;
|
||||
public const STATUS_DELETED = 2;
|
||||
|
||||
public readonly int $status;
|
||||
private bool $editable = false;
|
||||
|
||||
public function __construct(
|
||||
public readonly int $id,
|
||||
private string $name,
|
||||
private LocString $text,
|
||||
private int $mode = self::MODE_CONTENT_TOP,
|
||||
int $status = self::STATUS_ENABLED,
|
||||
private string $style = '')
|
||||
{
|
||||
// a negative id displays ENABLE/DISABLE and DELETE links for this announcement
|
||||
// TODO - the ugroup check mirrors the js. Add other checks like ownership status? (ownership currently not stored)
|
||||
if (User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU) /* && User::$id == $authorId */)
|
||||
$this->editable = true;
|
||||
|
||||
if ($this->mode != self::MODE_PAGE_TOP && $this->mode != self::MODE_CONTENT_TOP)
|
||||
$this->mode = self::MODE_PAGE_TOP;
|
||||
|
||||
if ($status != self::STATUS_DISABLED && $status != self::STATUS_ENABLED && $status != self::STATUS_DELETED)
|
||||
$this->status = self::STATUS_DELETED;
|
||||
else
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$json = array(
|
||||
'parent' => 'announcement-' . abs($this->id),
|
||||
'id' => $this->editable ? -$this->id : $this->id,
|
||||
'mode' => $this->mode,
|
||||
'status' => $this->status,
|
||||
'name' => $this->name,
|
||||
'text' => (string)$this->text // force LocString to naive string for display
|
||||
);
|
||||
|
||||
if ($this->style)
|
||||
$json['style'] = $this->style;
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->status == self::STATUS_DELETED)
|
||||
return '';
|
||||
|
||||
return "new Announcement(".Util::toJSON($this).");\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
50
includes/components/frontend/book.class.php
Normal file
50
includes/components/frontend/book.class.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Book implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
private array $pages, // js:array of html
|
||||
private string $parent = 'book-generic', // HTMLNode.id
|
||||
private ?int $page = null) // start page; defaults to 1
|
||||
{
|
||||
if (!$this->parent)
|
||||
trigger_error(self::class.'::__construct - initialized without parent element', E_USER_WARNING);
|
||||
|
||||
if (!$this->pages)
|
||||
trigger_error(self::class.'::__construct - initialized without content', E_USER_WARNING);
|
||||
else
|
||||
$this->pages = Util::parseHtmlText($this->pages);
|
||||
}
|
||||
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
reset($this->pages);
|
||||
|
||||
foreach ($this->pages as $idx => &$page)
|
||||
yield $idx => $page;
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this as $prop => $val)
|
||||
if ($val !== null && $prop[0] != '_')
|
||||
$result[$prop] = $val;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return "new Book(".Util::toJSON($this).");\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
159
includes/components/frontend/iconelement.class.php
Normal file
159
includes/components/frontend/iconelement.class.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class IconElement
|
||||
{
|
||||
public const SIZE_SMALL = 0;
|
||||
public const SIZE_MEDIUM = 1;
|
||||
public const SIZE_LARGE = 2;
|
||||
|
||||
private const CREATE_ICON_TPL = "\$WH.ge('%s%d').appendChild(%s.createIcon(%s));\n";
|
||||
|
||||
private int $idx = 0;
|
||||
private string $href = '';
|
||||
private bool $noIcon = false;
|
||||
|
||||
public readonly string $quality;
|
||||
public readonly ?string $align;
|
||||
public readonly int $size;
|
||||
|
||||
public function __construct(
|
||||
public readonly int $type,
|
||||
public readonly int $typeId,
|
||||
public readonly string $text,
|
||||
public readonly int|string $num = '',
|
||||
public readonly int|string $qty = '',
|
||||
?string $quality = null,
|
||||
int $size = self::SIZE_MEDIUM,
|
||||
bool $link = true,
|
||||
string $url = '',
|
||||
?string $align = null,
|
||||
public readonly string $element = 'icontab-icon',
|
||||
public ?string $extraText = null
|
||||
)
|
||||
{
|
||||
if (is_numeric($quality))
|
||||
$this->quality = 'q'.$quality;
|
||||
else if ($quality !== null)
|
||||
$this->quality = 'q';
|
||||
else
|
||||
$this->quality = '';
|
||||
|
||||
if ($size < self::SIZE_SMALL || $size > self::SIZE_LARGE)
|
||||
{
|
||||
trigger_error('IconElement::__construct - invalid icon size '.$size.'. Normalied to 1 [small]', E_USER_WARNING);
|
||||
$this->size = self::SIZE_SMALL;
|
||||
}
|
||||
else
|
||||
$this->size = $size;
|
||||
|
||||
if ($align && !in_array($align, ['left', 'right', 'center', 'justify']))
|
||||
{
|
||||
trigger_error('IconElement::__construct - unset invalid align value "'.$align.'".', E_USER_WARNING);
|
||||
$this->align = null;
|
||||
}
|
||||
else
|
||||
$this->align = $align;
|
||||
|
||||
if ($type && $typeId && !Type::validateIds($type, $typeId))
|
||||
{
|
||||
$link = false;
|
||||
trigger_error('IconElement::__construct - invalid typeId '.$typeId.' for '.Type::getFileString($type).'.', E_USER_WARNING);
|
||||
}
|
||||
else if (!$type || !$typeId)
|
||||
$link = false;
|
||||
|
||||
if ($link || $url)
|
||||
$this->href = $url ?: '?'.Type::getFileString($this->type).'='.$this->typeId;
|
||||
|
||||
// see Spell/Tools having icon container but no actual icon and having to be inline with other IconElements
|
||||
$this->noIcon = !$typeId || !Type::hasIcon($type);
|
||||
}
|
||||
|
||||
public function renderContainer(int $lpad = 0, int &$iconIdxOffset = 0, bool $rowWrap = false) : string
|
||||
{
|
||||
if (!$this->noIcon)
|
||||
$this->idx = ++$iconIdxOffset;
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$td = $dom->createElement('td');
|
||||
$th = $dom->createElement('th');
|
||||
|
||||
if ($this->noIcon) // see Spell/Tools or AchievementCriteria having no actual icon, but placeholder
|
||||
{
|
||||
$ul = $dom->createElement('ul');
|
||||
$li = $dom->createElement('li');
|
||||
$var = $dom->createElement('var', ' ');
|
||||
$li->appendChild($var);
|
||||
$ul->appendChild($li);
|
||||
$th->appendChild($ul);
|
||||
}
|
||||
else
|
||||
{
|
||||
$th->setAttribute('id', $this->element . $this->idx);
|
||||
if ($this->align)
|
||||
$th->setAttribute('align', $this->align);
|
||||
}
|
||||
|
||||
if ($this->href)
|
||||
($a = $dom->createElement('a', $this->text))->setAttribute('href', $this->href);
|
||||
else
|
||||
$a = $dom->createTextNode($this->text);
|
||||
|
||||
if ($this->quality)
|
||||
{
|
||||
($sp = $dom->createElement('span'))->setAttribute('class', $this->quality);
|
||||
$sp->appendChild($a);
|
||||
$td->appendChild($sp);
|
||||
}
|
||||
else
|
||||
$td->appendChild($a);
|
||||
|
||||
// extraText can be HTML, so import it as a fragment
|
||||
if ($this->extraText)
|
||||
{
|
||||
$fragment = $dom->createDocumentFragment();
|
||||
$fragment->appendXML(' '.$this->extraText);
|
||||
$td->appendChild($fragment);
|
||||
}
|
||||
// only for objectives list..?
|
||||
if ($this->num && $this->size == self::SIZE_SMALL)
|
||||
$td->appendChild($dom->createTextNode(' ('.$this->num.')'));
|
||||
|
||||
if ($rowWrap)
|
||||
{
|
||||
$tr = $dom->createElement('tr');
|
||||
$tr->appendChild($th);
|
||||
$tr->appendChild($td);
|
||||
$dom->append($tr);
|
||||
}
|
||||
else
|
||||
$dom->append($th, $td);
|
||||
|
||||
return str_repeat(' ', $lpad) . $dom->saveHTML();
|
||||
}
|
||||
|
||||
// $WH.ge('icontab-icon1').appendChild(g_spells.createIcon(40120, 1, '1-4', 0));
|
||||
|
||||
public function renderJS(int $lpad = 0) : string
|
||||
{
|
||||
if ($this->noIcon)
|
||||
return '';
|
||||
|
||||
$params = [$this->typeId, $this->size];
|
||||
if ($this->num || $this->qty)
|
||||
$params[] = is_numeric($this->num) ? $this->num : "'".$this->num."'";
|
||||
if ($this->qty)
|
||||
$params[] = is_numeric($this->qty) ? $this->qty : "'".$this->qty."'";
|
||||
|
||||
return str_repeat(' ', $lpad) . sprintf(self::CREATE_ICON_TPL, $this->element, $this->idx, Type::getJSGlobalString($this->type), implode(', ', $params));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
49
includes/components/frontend/infoboxmarkup.class.php
Normal file
49
includes/components/frontend/infoboxmarkup.class.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class InfoboxMarkup extends Markup
|
||||
{
|
||||
public function __construct(private array $items = [], array $opts, string $parent = '')
|
||||
{
|
||||
parent::__construct('', $opts, $parent);
|
||||
}
|
||||
|
||||
public function addItem(string $item, ?int $pos = null) : void
|
||||
{
|
||||
if (is_null($pos) || $pos >= count($this->items))
|
||||
$this->items[] = $item;
|
||||
else
|
||||
array_splice($this->items, $pos, 0, $item);
|
||||
}
|
||||
|
||||
public function append(string $text) : self
|
||||
{
|
||||
if ($this->items && !$this->__text)
|
||||
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
|
||||
|
||||
return parent::append($text);
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->items && !$this->__text)
|
||||
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
|
||||
|
||||
return parent::__toString();
|
||||
}
|
||||
|
||||
public function getJsGlobals() : array
|
||||
{
|
||||
if ($this->items && !$this->__text)
|
||||
$this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]');
|
||||
|
||||
return parent::getJsGlobals();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
174
includes/components/frontend/listview.class.php
Normal file
174
includes/components/frontend/listview.class.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Listview implements \JsonSerializable
|
||||
{
|
||||
public const MODE_DEFAULT = 0;
|
||||
public const MODE_CHECKBOX = 1;
|
||||
public const MODE_DIV = 2;
|
||||
public const MODE_TILED = 3;
|
||||
public const MODE_CALENDAR = 4;
|
||||
public const MODE_FLEXGRID = 5;
|
||||
|
||||
private const TEMPLATES = array(
|
||||
'achievement' => ['template' => 'achievement', 'id' => 'achievements', 'name' => '$LANG.tab_achievements' ],
|
||||
'areatrigger' => ['template' => 'areatrigger', 'id' => 'areatrigger', ],
|
||||
'calendar' => ['template' => 'holidaycal', 'id' => 'calendar', 'name' => '$LANG.tab_calendar' ],
|
||||
'class' => ['template' => 'classs', 'id' => 'classes', 'name' => '$LANG.tab_classes' ],
|
||||
'commentpreview' => ['template' => 'commentpreview', 'id' => 'comments', 'name' => '$LANG.tab_comments' ],
|
||||
'npc' => ['template' => 'npc', 'id' => 'npcs', 'name' => '$LANG.tab_npcs' ],
|
||||
'currency' => ['template' => 'currency', 'id' => 'currencies', 'name' => '$LANG.tab_currencies' ],
|
||||
'emote' => ['template' => 'emote', 'id' => 'emotes', ],
|
||||
'enchantment' => ['template' => 'enchantment', 'id' => 'enchantments', ],
|
||||
'event' => ['template' => 'holiday', 'id' => 'holidays', 'name' => '$LANG.tab_holidays' ],
|
||||
'faction' => ['template' => 'faction', 'id' => 'factions', 'name' => '$LANG.tab_factions' ],
|
||||
'genericmodel' => ['template' => 'genericmodel', 'id' => 'same-model-as', 'name' => '$LANG.tab_samemodelas' ],
|
||||
'icongallery' => ['template' => 'icongallery', 'id' => 'icons', ],
|
||||
'item' => ['template' => 'item', 'id' => 'items', 'name' => '$LANG.tab_items' ],
|
||||
'itemset' => ['template' => 'itemset', 'id' => 'itemsets', 'name' => '$LANG.tab_itemsets' ],
|
||||
'mail' => ['template' => 'mail', 'id' => 'mails', ],
|
||||
'model' => ['template' => 'model', 'id' => 'gallery', 'name' => '$LANG.tab_gallery' ],
|
||||
'object' => ['template' => 'object', 'id' => 'objects', 'name' => '$LANG.tab_objects' ],
|
||||
'pet' => ['template' => 'pet', 'id' => 'hunter-pets', 'name' => '$LANG.tab_pets' ],
|
||||
'profile' => ['template' => 'profile', 'id' => 'profiles', 'name' => '$LANG.tab_profiles' ],
|
||||
'quest' => ['template' => 'quest', 'id' => 'quests', 'name' => '$LANG.tab_quests' ],
|
||||
'race' => ['template' => 'race', 'id' => 'races', 'name' => '$LANG.tab_races' ],
|
||||
'replypreview' => ['template' => 'replypreview', 'id' => 'comment-replies', 'name' => '$LANG.tab_commentreplies'],
|
||||
'reputationhistory' => ['template' => 'reputationhistory', 'id' => 'reputation', 'name' => '$LANG.tab_reputation' ],
|
||||
'screenshot' => ['template' => 'screenshot', 'id' => 'screenshots', 'name' => '$LANG.tab_screenshots' ],
|
||||
'skill' => ['template' => 'skill', 'id' => 'skills', 'name' => '$LANG.tab_skills' ],
|
||||
'sound' => ['template' => 'sound', 'id' => 'sounds', 'name' => '$LANG.types[19][2]' ],
|
||||
'spell' => ['template' => 'spell', 'id' => 'spells', 'name' => '$LANG.tab_spells' ],
|
||||
'title' => ['template' => 'title', 'id' => 'titles', 'name' => '$LANG.tab_titles' ],
|
||||
'topusers' => ['template' => 'topusers', 'id' => 'topusers', 'name' => '$LANG.topusers' ],
|
||||
'video' => ['template' => 'video', 'id' => 'videos', 'name' => '$LANG.tab_videos' ],
|
||||
'zone' => ['template' => 'zone', 'id' => 'zones', 'name' => '$LANG.tab_zones' ],
|
||||
'guide' => ['template' => 'guide', 'id' => 'guides', ]
|
||||
);
|
||||
|
||||
private string $id = '';
|
||||
private ?string $name = null;
|
||||
private ?array $data = null; // js:array of object <RowDefinitions>
|
||||
private ?string $tabs = null; // js:Object; instance of "Tabs"
|
||||
private ?string $parent = 'lv-generic'; // HTMLNode.id; can be null but is pretty much always 'lv-generic'
|
||||
private ?string $template = null;
|
||||
private ?int $mode = null; // js:int; defaults to MODE_DEFAULT
|
||||
private ?string $note = null; // text in top band
|
||||
|
||||
private ?int $poundable = null; // 0 (no); 1 (always); 2 (yes, w/o sorting); defaults to 1
|
||||
private ?int $searchable = null; // js:bool; defaults to FALSE
|
||||
private ?int $filtrable = null; // js:bool; defaults to FALSE
|
||||
private ?int $sortable = null; // js:bool; defaults to FALSE
|
||||
private ?int $searchDelay = null; // in ms; defalts to 333
|
||||
private ?int $clickable = null; // js:bool; defaults to TRUE
|
||||
private ?int $hideBands = null; // js:int; 1:top, 2:bottom, 3:both;
|
||||
private ?int $hideNav = null; // js:int; 1:top, 2:bottom, 3:both;
|
||||
private ?int $hideHeader = null; // js:bool
|
||||
private ?int $hideCount = null; // js:bool
|
||||
private ?int $debug = null; // js:bool
|
||||
private ?int $_truncated = null; // js:bool; adds predefined note to top band, because there was too much data to display
|
||||
private ?int $_errors = null; // js:bool; adds predefined note to top band, because there was an error
|
||||
private ?int $_petTalents = null; // js:bool; applies modifier for talent levels
|
||||
|
||||
private ?int $nItemsPerPage = null; // js:int; defaults to 50
|
||||
private ?int $_totalCount = null; // js:int; used by loot and comments
|
||||
private ?array $clip = null; // js:array of int {w:<width>, h:<height>}
|
||||
private ?string $customPound = null;
|
||||
private ?string $genericlinktype = null; // sometimes set when expecting to display model
|
||||
private ?array $_upgradeIds = null; // js:array of int (itemIds)
|
||||
|
||||
private null|array|string $extraCols = null; // js:callable or js:array of object <ColumnDefinition>
|
||||
private null|array|string $visibleCols = null; // js:callable or js:array of string <colIds>
|
||||
private null|array|string $hiddenCols = null; // js:callable or js:array of string <colIds>
|
||||
private null|array|string $sort = null; // js:callable or js:array of colIndizes
|
||||
|
||||
private ?string $onBeforeCreate = null; // js:callable
|
||||
private ?string $onAfterCreate = null; // js:callable
|
||||
private ?string $onNoData = null; // js:callable
|
||||
private ?string $computeDataFunc = null; // js:callable
|
||||
private ?string $onSearchSubmit = null; // js:callable
|
||||
private ?string $createNote = null; // js:callable
|
||||
private ?string $createCbControls = null; // js:callable
|
||||
private ?string $customFilter = null; // js:callable
|
||||
private ?string $getItemLink = null; // js:callable
|
||||
private ?array $sortOptions = null; // js:array of object {id:<colId>, name:<name>, hidden:<bool>, type:"text", sortFunc:<callable>}
|
||||
|
||||
private string $__addIn = '';
|
||||
|
||||
public function __construct(array $opts, string $template = '', string $addIn = '')
|
||||
{
|
||||
if ($template && isset(self::TEMPLATES[$template]))
|
||||
foreach (self::TEMPLATES[$template] as $k => $v)
|
||||
$this->$k = $v;
|
||||
|
||||
foreach ($opts as $k => $v)
|
||||
{
|
||||
if (property_exists($this, $k))
|
||||
{
|
||||
// reindex arrays to force json_encode to treat them as arrays
|
||||
if (is_array($v)) // in_array($k, ['data', 'extraCols', 'visibleCols', 'hiddenCols', 'sort', 'sortOptions']))
|
||||
$v = array_values($v);
|
||||
$this->$k = $v;
|
||||
}
|
||||
else
|
||||
trigger_error(self::class.'::__construct - unrecognized option: ' . $k);
|
||||
}
|
||||
|
||||
if ($addIn && !Template\PageTemplate::test('listviews/', $addIn.'.tpl'))
|
||||
trigger_error('Nonexistent Listview addin requested: template/listviews/'.$addIn.'.tpl', E_USER_ERROR);
|
||||
else if ($addIn)
|
||||
$this->__addIn = 'template/listviews/'.$addIn.'.tpl';
|
||||
}
|
||||
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
reset($this->data);
|
||||
|
||||
foreach ($this->data as $idx => &$row)
|
||||
yield $idx => $row;
|
||||
}
|
||||
|
||||
public function getTemplate() : string
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
public function setTabs(string $tabVar) : void
|
||||
{
|
||||
if ($tabVar[0] !== '$') // expects a jsVar, which we denote with a prefixed $
|
||||
$tabVar = '$' . $tabVar;
|
||||
|
||||
$this->tabs = $tabVar;
|
||||
}
|
||||
|
||||
public function setError() : void
|
||||
{
|
||||
$this->_errors = 1;
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this as $prop => $val)
|
||||
if ($val !== null && substr($prop, 0, 2) != '__')
|
||||
$result[$prop] = $val;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->__addIn)
|
||||
include($this->__addIn);
|
||||
|
||||
return "new Listview(".Util::toJSON($this).");\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
291
includes/components/frontend/markup.class.php
Normal file
291
includes/components/frontend/markup.class.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Markup implements \JsonSerializable
|
||||
{
|
||||
private const DB_TAG_PATTERN = '/(?<!\\\\)\[(npc|object|item|itemset|quest|spell|zone|faction|pet|achievement|statistic|title|event|class|race|skill|currency|emote|enchantment|money|sound|icondb)=(-?\d+)[^\]]*\]/i';
|
||||
|
||||
// const val
|
||||
public const MARKUP_MODE_COMMENT = 1;
|
||||
public const MARKUP_MODE_ARTICLE = 2;
|
||||
public const MARKUP_MODE_QUICKFACTS = 3;
|
||||
public const MARKUP_MODE_SIGNATURE = 4;
|
||||
public const MARKUP_MODE_REPLY = 5;
|
||||
|
||||
// js var
|
||||
public const MODE_COMMENT = '$Markup.MODE_COMMENT';
|
||||
public const MODE_ARTICLE = '$Markup.MODE_ARTICLE';
|
||||
public const MODE_QUICKFACTS = '$Markup.MODE_QUICKFACTS';
|
||||
public const MODE_SIGNATURE = '$Markup.MODE_SIGNATURE';
|
||||
public const MODE_REPLY = '$Markup.MODE_REPLY';
|
||||
|
||||
// const val
|
||||
public const MARKUP_CLASS_ADMIN = 40;
|
||||
public const MARKUP_CLASS_STAFF = 30;
|
||||
public const MARKUP_CLASS_PREMIUM = 20;
|
||||
public const MARKUP_CLASS_USER = 10;
|
||||
public const MARKUP_CLASS_PENDING = 1;
|
||||
|
||||
// js var
|
||||
public const CLASS_ADMIN = '$Markup.CLASS_ADMIN';
|
||||
public const CLASS_STAFF = '$Markup.CLASS_STAFF';
|
||||
public const CLASS_PREMIUM = '$Markup.CLASS_PREMIUM';
|
||||
public const CLASS_USER = '$Markup.CLASS_USER';
|
||||
public const CLASS_PENDING = '$Markup.CLASS_PENDING';
|
||||
|
||||
// options
|
||||
private ?string $prepend = null; // html in front of article
|
||||
private ?string $append = null; // html trailing the article
|
||||
private ?int $locale = null; // forces tooltips in the article to adhere to another locale
|
||||
private ?int $inBlog = null; // js:bool; unused by aowow
|
||||
private ?string $mode = null; // defaults to Markup.MODE_ARTICLE, which is what we want.
|
||||
private ?string $allow = null; // defaults to Markup.CLASS_STAFF
|
||||
private ?int $roles = null; // if allow is null, get allow from roles (user group); also mode will be set to MODE_ARTICLE for staff groups
|
||||
private ?int $stopAtBreak = null; // js:bool; only parses text until substring "[break]" is encountered; some debug option...?
|
||||
private ?string $highlight = null; // HTMLNode selector
|
||||
|
||||
private ?int $skipReset = null; // js:bool; unsure, if TRUE the next settings in this block get skipped
|
||||
private ?string $uid = null; // defaults to 'abc'; unsure, key under which media is stored and referenced in g_screenshots and g_videos
|
||||
private ?string $root = null; // unsure, something to with Markup Tags that need to be subordinate to other tags (e.g.: [li] to [ol])
|
||||
private ?int $preview = null; // unsure, appends '-preview' to the div created by the [tabs] tag and prevents scrolling. Forum feature?
|
||||
private ?int $dbpage = null; // js:bool; set on db type detail pages; adds article edit links to admin menu
|
||||
|
||||
protected string $__text;
|
||||
|
||||
private string $__parent = 'article-generic';
|
||||
|
||||
public function __construct(string $text, array $opts, string $parent = '')
|
||||
{
|
||||
foreach ($opts as $k => $v)
|
||||
{
|
||||
if (property_exists($this, $k))
|
||||
$this->$k = $v;
|
||||
else
|
||||
trigger_error(self::class.'::__construct - unrecognized option: ' . $k);
|
||||
}
|
||||
|
||||
$this->__text = $text;
|
||||
|
||||
if ($parent)
|
||||
$this->__parent = $parent;
|
||||
}
|
||||
|
||||
public function getJsGlobals() : array
|
||||
{
|
||||
return $this->_parseTags();
|
||||
}
|
||||
|
||||
public function getParent() : string
|
||||
{
|
||||
return $this->__parent;
|
||||
}
|
||||
|
||||
|
||||
/***********************/
|
||||
/* Markup tag handling */
|
||||
/***********************/
|
||||
|
||||
private function _parseTags(array &$jsg = []) : array
|
||||
{
|
||||
return self::parseTags($this->__text, $jsg);
|
||||
}
|
||||
|
||||
public static function parseTags(string $text, array &$jsg = []) : array
|
||||
{
|
||||
$jsGlobals = [];
|
||||
|
||||
if (preg_match_all(self::DB_TAG_PATTERN, $text, $matches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach ($matches as $match)
|
||||
{
|
||||
if ($match[1] == 'statistic')
|
||||
$match[1] = 'achievement';
|
||||
else if ($match[1] == 'icondb')
|
||||
$match[1] = 'icon';
|
||||
|
||||
if ($match[1] == 'money')
|
||||
{
|
||||
if (stripos($match[0], 'items'))
|
||||
{
|
||||
if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i+=2)
|
||||
$jsGlobals[Type::ITEM][$sm[$i]] = $sm[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (stripos($match[0], 'currency'))
|
||||
{
|
||||
if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i+=2)
|
||||
$jsGlobals[Type::CURRENCY][$sm[$i]] = $sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
|
||||
$jsGlobals[$type][$match[2]] = $match[2];
|
||||
}
|
||||
}
|
||||
|
||||
Util::mergeJsGlobals($jsg, $jsGlobals);
|
||||
|
||||
return $jsGlobals;
|
||||
}
|
||||
|
||||
private function _stripTags(array $jsgData = []) : string
|
||||
{
|
||||
return self::stripTags($this->__text, $jsgData);
|
||||
}
|
||||
|
||||
public static function stripTags(string $text, array $jsgData = []) : string
|
||||
{
|
||||
// replace DB Tags
|
||||
$text = preg_replace_callback(self::DB_TAG_PATTERN, function ($match) use ($jsgData) {
|
||||
if ($match[1] == 'statistic')
|
||||
$match[1] = 'achievement';
|
||||
else if ($match[1] == 'icondb')
|
||||
$match[1] = 'icon';
|
||||
else if ($match[1] == 'money')
|
||||
{
|
||||
$moneys = [];
|
||||
if (stripos($match[0], 'items'))
|
||||
{
|
||||
if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i += 2)
|
||||
{
|
||||
if (!empty($jsgData[Type::ITEM][1][$sm[$i]]))
|
||||
$moneys[] = $jsgData[Type::ITEM][1][$sm[$i]]['name'] ?? $jsgData[Type::ITEM][1][$match[2]]['name_' . Lang::getLocale()->json()];
|
||||
else
|
||||
$moneys[] = Util::ucFirst(Lang::game('item')).' #'.$sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stripos($match[0], 'currency'))
|
||||
{
|
||||
if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i += 2)
|
||||
{
|
||||
if (!empty($jsgData[Type::CURRENCY][1][$sm[$i]]))
|
||||
$moneys[] = $jsgData[Type::CURRENCY][1][$sm[$i]]['name'] ?? $jsgData[Type::CURRENCY][1][$match[2]]['name_' . Lang::getLocale()->json()];
|
||||
else
|
||||
$moneys[] = Util::ucFirst(Lang::game('curency')).' #'.$sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Lang::concat($moneys);
|
||||
}
|
||||
if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
|
||||
{
|
||||
if (!empty($jsgData[$type][1][$match[2]]))
|
||||
return $jsgData[$type][1][$match[2]]['name'] ?? $jsgData[$type][1][$match[2]]['name_' . Lang::getLocale()->json()];
|
||||
else
|
||||
return Util::ucFirst(Lang::game($match[1])).' #'.$match[2];
|
||||
}
|
||||
|
||||
trigger_error('Markup::stripTags() - encountered unhandled db-tag: '.var_export($match));
|
||||
return '';
|
||||
}, $text);
|
||||
|
||||
// replace line endings
|
||||
$text = str_replace('[br]', "\n", $text);
|
||||
|
||||
// strip other Tags
|
||||
$stripped = '';
|
||||
$inTag = false;
|
||||
for ($i = 0; $i < strlen($text); $i++)
|
||||
{
|
||||
if ($text[$i] == '[' && (!$i || $text[$i - 1] != '\\'))
|
||||
$inTag = true;
|
||||
if (!$inTag)
|
||||
$stripped .= $text[$i];
|
||||
if ($inTag && $text[$i] == ']' && (!$i || $text[$i - 1] != '\\'))
|
||||
$inTag = false;
|
||||
}
|
||||
|
||||
return $stripped;
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* String Operations */
|
||||
/*********************/
|
||||
|
||||
public function append(string $text) : self
|
||||
{
|
||||
$this->__text .= $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function prepend(string $text) : self
|
||||
{
|
||||
$this->__text = $text . $this->__text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function apply(\Closure $fn) : void
|
||||
{
|
||||
$this->__text = $fn($this->__text);
|
||||
}
|
||||
|
||||
public function replace(string $middle, int $offset = 0, ?int $len = null) : self
|
||||
{
|
||||
// y no mb_substr_replace >:(
|
||||
$start = $end = '';
|
||||
|
||||
if ($offset < 0)
|
||||
$offset = mb_strlen($this->__text) + $offset;
|
||||
|
||||
$start = mb_substr($this->__text, 0, $offset);
|
||||
|
||||
if (!is_null($len) && $len >= 0)
|
||||
$end = mb_substr($this->__text, $offset + $len);
|
||||
else if (!is_null($len) && $len < 0)
|
||||
$end = mb_substr($this->__text, $offset + mb_strlen($this->__text) + $len);
|
||||
|
||||
$this->__text = $start . $middle . $end;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function cleanText() : string
|
||||
{
|
||||
// break script-tags, unify newlines
|
||||
$val = preg_replace(['/script\s*\>/i', "/\r\n/", "/\r/"], ['script>', "\n", "\n"], $this->__text);
|
||||
|
||||
return strtr(Util::jsEscape($val), ['script>' => 'scr"+"ipt>']);
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this as $prop => $val)
|
||||
if ($val !== null && $prop[0] != '_')
|
||||
$result[$prop] = $val;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->jsonSerialize())
|
||||
return 'Markup.printHtml("'.$this->cleanText().'", "'.$this->__parent.'", '.Util::toJSON($this).");\n";
|
||||
|
||||
return 'Markup.printHtml("'.$this->cleanText().'", "'.$this->__parent."\");\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
70
includes/components/frontend/summary.class.php
Normal file
70
includes/components/frontend/summary.class.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Summary implements \JsonSerializable
|
||||
{
|
||||
private string $id = ''; // HTMLNode.id
|
||||
private ?string $parent = ''; // HTMLNode.id; if set $id is created and attached here instead of searched for
|
||||
private string $template = ''; //
|
||||
private ?int $editable = null; // js:bool; defaults to TRUE
|
||||
private ?int $draggable = null; // js:bool; defaults to $editable
|
||||
private ?int $searchable = null; // js:bool; defaults to $editable && $draggable
|
||||
private ?int $weightable = null; // js:bool; defaults to $editable
|
||||
private ?int $textable = null; // js:bool; defaults to FALSE
|
||||
private ?int $enhanceable = null; // js:bool; defaults to $editable
|
||||
private ?int $level = null; // js:int; defaults to 80
|
||||
private array $groups = []; // js:array; defaults to GET-params
|
||||
private ?array $weights = null; // js:array; defaults to GET-params
|
||||
|
||||
public function __construct(array $opts)
|
||||
{
|
||||
foreach ($opts as $k => $v)
|
||||
{
|
||||
if (property_exists($this, $k))
|
||||
$this->$k = $v;
|
||||
else
|
||||
trigger_error(self::class.'::__construct - unrecognized option: ' . $k);
|
||||
}
|
||||
|
||||
if (!$this->template)
|
||||
trigger_error(self::class.'::__construct - initialized without template', E_USER_WARNING);
|
||||
if (!$this->id)
|
||||
trigger_error(self::class.'::__construct - initialized without HTMLNode#id to reference', E_USER_WARNING);
|
||||
}
|
||||
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
reset($this->groups);
|
||||
|
||||
foreach ($this->groups as $idx => &$group)
|
||||
yield $idx => $group;
|
||||
}
|
||||
|
||||
public function addGroup(array $group) : void
|
||||
{
|
||||
$this->groups[] = $group;
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this as $prop => $val)
|
||||
if ($val !== null && $prop[0] != '_')
|
||||
$result[$prop] = $val;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return "new Summary(".Util::toJSON($this).");\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
142
includes/components/frontend/tabs.class.php
Normal file
142
includes/components/frontend/tabs.class.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Tabs implements \JsonSerializable, \Countable
|
||||
{
|
||||
private array $__tabs = [];
|
||||
|
||||
private string $parent = ''; // HTMLNode
|
||||
private ?int $poundable = null; // js:bool
|
||||
private ?int $forceScroll = null; // js:bool
|
||||
private ?int $noScroll = null; // js:bool
|
||||
private ?string $trackable = null; // String to track in Google Analytics .. often a DB Type
|
||||
|
||||
private ?string $onLoad = null; // js::callable
|
||||
private ?string $onShow = null; // js::callable
|
||||
private ?string $onHide = null; // js::callable
|
||||
|
||||
public function __construct(array $opts, public readonly string $__tabVar = 'myTabs', private bool $__forceTabs = false)
|
||||
{
|
||||
foreach ($opts as $k => $v)
|
||||
{
|
||||
if (property_exists($this, $k))
|
||||
$this->$k = $v;
|
||||
else
|
||||
trigger_error(self::class.'::__construct - unrecognized option: ' . $k);
|
||||
}
|
||||
}
|
||||
|
||||
public function &iterate() : \Generator
|
||||
{
|
||||
reset($this->__tabs);
|
||||
|
||||
foreach ($this->__tabs as $idx => &$tab)
|
||||
yield $idx => $tab;
|
||||
}
|
||||
|
||||
public function addListviewTab(Listview $lv) : void
|
||||
{
|
||||
$this->__tabs[] = $lv;
|
||||
}
|
||||
|
||||
public function addDataTab(string $id, string $name, string $data) : void
|
||||
{
|
||||
$this->__tabs[] = ['id' => $id, 'name' => $name, 'data' => $data];
|
||||
$this->__forceTabs = true; // otherwise a single DataTab could not be accessed
|
||||
}
|
||||
|
||||
public function getDataContainer() : \Generator
|
||||
{
|
||||
foreach ($this->__tabs as $tab)
|
||||
if (is_array($tab))
|
||||
yield '<div class="text tabbed-contents" id="tab-'.$tab['id'].'" style="display:none;">'.$tab['data'].'</div>';
|
||||
}
|
||||
|
||||
public function getFlush() : string
|
||||
{
|
||||
if ($this->isTabbed())
|
||||
return $this->__tabVar.".flush();";
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function isTabbed() : bool
|
||||
{
|
||||
return count($this->__tabs) > 1 || $this->__forceTabs;
|
||||
}
|
||||
|
||||
|
||||
/***********************/
|
||||
/* enable deep cloning */
|
||||
/***********************/
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->__tabs as $idx => $tab)
|
||||
{
|
||||
if (is_array($tab))
|
||||
continue;
|
||||
|
||||
$this->__tabs[$idx] = clone $tab;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************/
|
||||
/* make countable */
|
||||
/******************/
|
||||
|
||||
public function count() : int
|
||||
{
|
||||
return count($this->__tabs);
|
||||
}
|
||||
|
||||
|
||||
/************************/
|
||||
/* make Tabs stringable */
|
||||
/************************/
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this as $prop => $val)
|
||||
if ($val !== null && $prop[0] != '_')
|
||||
$result[$prop] = $val;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
if ($this->isTabbed())
|
||||
$result .= "var ".$this->__tabVar." = new Tabs(".Util::toJSON($this).");\n";
|
||||
|
||||
foreach ($this->__tabs as $tab)
|
||||
{
|
||||
if (is_array($tab))
|
||||
{
|
||||
$n = $tab['name'][0] == '$' ? substr($tab['name'], 1) : "'".$tab['name']."'";
|
||||
$result .= $this->__tabVar.".add(".$n.", { id: '".$tab['id']."' });\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->isTabbed())
|
||||
$tab->setTabs($this->__tabVar);
|
||||
|
||||
$result .= $tab; // Listview::__toString here
|
||||
}
|
||||
}
|
||||
|
||||
return $result . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
60
includes/components/frontend/tooltip.class.php
Normal file
60
includes/components/frontend/tooltip.class.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Tooltip implements \JsonSerializable
|
||||
{
|
||||
private ?string $name = null;
|
||||
private ?string $tooltip = null;
|
||||
private ?\StdClass $map = null; // secondary tooltip
|
||||
private ?string $icon = null;
|
||||
private ?int $quality = null; // icon border color coded
|
||||
private ?bool $daily = null;
|
||||
private ?array $spells = null;
|
||||
private ?string $buff = null;
|
||||
private ?array $buffspells = null;
|
||||
|
||||
public function __construct(private string $__powerTpl, private string $__subject, array $opts = [])
|
||||
{
|
||||
foreach ($opts as $k => $v)
|
||||
{
|
||||
if (property_exists($this, $k))
|
||||
$this->$k = $v;
|
||||
else
|
||||
trigger_error(self::class.'::__construct - unrecognized option: ' . $k);
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
$out = [];
|
||||
|
||||
$locString = Lang::getLocale()->json();
|
||||
|
||||
foreach ($this as $k => $v)
|
||||
{
|
||||
if ($v === null || $k[0] == '_')
|
||||
continue;
|
||||
|
||||
if ($k == 'icon')
|
||||
$out[$k] = rawurldecode($v);
|
||||
else if ($k == 'quality' || $k == 'map' || $k == 'daily')
|
||||
$out[$k] = $v;
|
||||
else
|
||||
$out[$k . '_' . $locString] = $v;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return sprintf($this->__powerTpl, Util::toJSON($this->__subject, JSON_AOWOW_POWER), Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
61
includes/components/locstring.class.php
Normal file
61
includes/components/locstring.class.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class LocString
|
||||
{
|
||||
private \WeakMap $store;
|
||||
|
||||
public function __construct(array $data, string $key = 'name', ?callable $callback = null)
|
||||
{
|
||||
$this->store = new \WeakMap();
|
||||
|
||||
$callback ??= fn($x) => $x;
|
||||
|
||||
if (!array_filter($data, fn($v, $k) => $v && strstr($k, $key.'_loc'), ARRAY_FILTER_USE_BOTH))
|
||||
trigger_error('LocString - is entrirely empty', E_USER_WARNING);
|
||||
|
||||
foreach (Locale::cases() as $l)
|
||||
$this->store[$l] = (string)$callback($data[$key.'_loc'.$l->value] ?? '');
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($str = $this->store[Lang::getLocale()])
|
||||
return $str;
|
||||
|
||||
foreach (Locale::cases() as $l) // desired loc not set, use any other
|
||||
if (isset($this->store[$l]))
|
||||
return Cfg::get('DEBUG') ? '['.$this->store[$l].']' : $this->store[$l];
|
||||
|
||||
return Cfg::get('DEBUG') ? '[LOCSTRING]' : '';
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
$data = [];
|
||||
foreach (Locale::cases() as $l)
|
||||
if (isset($this->store[$l]))
|
||||
$data[$l->value] = $this->store[$l];
|
||||
|
||||
return ['store' => $data];
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->store = new \WeakMap();
|
||||
|
||||
if (empty($data['store']))
|
||||
return;
|
||||
|
||||
foreach ($data['store'] as $locId => $str)
|
||||
if (($l = Locale::tryFrom($locId))?->validate())
|
||||
$this->store[$l] = (string)$str;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,13 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Profiler
|
||||
{
|
||||
const PID_FILE = 'config/pr-queue-pid';
|
||||
const CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT
|
||||
public const PID_FILE = 'config/pr-queue-pid';
|
||||
public const CHAR_GMFLAGS = 0x1 | 0x8 | 0x10 | 0x20; // PLAYER_EXTRA_ :: GM_ON | TAXICHEAT | GM_INVISIBLE | GM_CHAT
|
||||
|
||||
public const REGIONS = array( // see cfg_categories.dbc
|
||||
'us' => [2, 3, 4, 5], // US (us, oceanic, latin america, americas - tournament)
|
||||
'kr' => [6, 7], // KR (kr, tournament)
|
||||
'eu' => [8, 9, 10, 11, 12, 13], // EU (english, german, french, spanish, russian, eu - tournament)
|
||||
'tw' => [14, 15], // TW (tw, tournament)
|
||||
'cn' => [16, 17, 18, 19, 20, 21, 22, 23, 24, 25], // CN (cn, CN1-8, tournament)
|
||||
'dev' => [1, 26, 27, 28, 30] // Development, Test Server, Test Server - tournament, QA Server, Test Server 2
|
||||
);
|
||||
|
||||
private static $realms = [];
|
||||
|
||||
@@ -178,35 +189,58 @@ class Profiler
|
||||
return $str;
|
||||
}
|
||||
|
||||
public static function getRealms()
|
||||
public static function getRealms() : array
|
||||
{
|
||||
if (DB::isConnectable(DB_AUTH) && !self::$realms)
|
||||
{
|
||||
self::$realms = DB::Auth()->select('SELECT
|
||||
id AS ARRAY_KEY,
|
||||
if (!DB::isConnectable(DB_AUTH) || self::$realms)
|
||||
return self::$realms;
|
||||
|
||||
self::$realms = DB::Auth()->select(
|
||||
'SELECT `id` AS ARRAY_KEY,
|
||||
`name`,
|
||||
CASE
|
||||
WHEN timezone IN (2, 3, 4) THEN "us"
|
||||
WHEN timezone IN (8, 9, 10, 11, 12) THEN "eu"
|
||||
WHEN timezone = 6 THEN "kr"
|
||||
WHEN timezone = 14 THEN "tw"
|
||||
WHEN timezone = 16 THEN "cn"
|
||||
END AS region
|
||||
FROM
|
||||
realmlist
|
||||
WHERE
|
||||
allowedSecurityLevel = 0 AND
|
||||
gamebuild = ?d',
|
||||
CASE WHEN `timezone` BETWEEN 2 AND 5 THEN "us" # US, Oceanic, Latin America, Americas-Tournament
|
||||
WHEN `timezone` BETWEEN 6 AND 7 THEN "kr" # KR, KR-Tournament
|
||||
WHEN `timezone` BETWEEN 8 AND 13 THEN "eu" # GB, DE, FR, ES, RU, EU-Tournament
|
||||
WHEN `timezone` BETWEEN 14 AND 15 THEN "tw" # TW, TW-Tournament
|
||||
WHEN `timezone` BETWEEN 16 AND 25 THEN "cn" # CN, CN1-8, CN-Tournament
|
||||
ELSE "dev" END AS "region", # 1: Dev, 26: Test, 27: Test Tournament, 28: QA, 30: Test2, 31+: misc
|
||||
`allowedSecurityLevel` AS "access"
|
||||
FROM `realmlist`
|
||||
WHERE `gamebuild` = ?d',
|
||||
WOW_BUILD
|
||||
);
|
||||
|
||||
foreach (self::$realms as $rId => $rData)
|
||||
foreach (self::$realms as $rId => &$rData)
|
||||
{
|
||||
if (DB::isConnectable(DB_CHARACTERS . $rId))
|
||||
continue;
|
||||
|
||||
// realm in db but no connection info set
|
||||
if (!DB::isConnectable(DB_CHARACTERS . $rId))
|
||||
{
|
||||
unset(self::$realms[$rId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter by access level
|
||||
if ($rData['access'] == SEC_ADMINISTRATOR && (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)))
|
||||
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN;
|
||||
else if ($rData['access'] == SEC_GAMEMASTER && (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD)))
|
||||
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD;
|
||||
else if ($rData['access'] == SEC_MODERATOR && (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD | U_GROUP_BUREAU)))
|
||||
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_MOD | U_GROUP_BUREAU;
|
||||
else if ($rData['access'] > SEC_PLAYER && !CLI)
|
||||
{
|
||||
unset(self::$realms[$rId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter dev realms
|
||||
if ($rData['region'] === 'dev')
|
||||
{
|
||||
if (CLI || User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
|
||||
$rData['access'] = U_GROUP_DEV | U_GROUP_ADMIN;
|
||||
else
|
||||
{
|
||||
unset(self::$realms[$rId]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,17 +249,17 @@ class Profiler
|
||||
|
||||
private static function queueInsert($realmId, $guid, $type, $localId)
|
||||
{
|
||||
if ($rData = DB::Aowow()->selectRow('SELECT requestTime AS time, status FROM ?_profiler_sync WHERE realm = ?d AND realmGUID = ?d AND `type` = ?d AND typeId = ?d AND status <> ?d', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WORKING))
|
||||
if ($rData = DB::Aowow()->selectRow('SELECT `requestTime` AS "time", `status` FROM ?_profiler_sync WHERE `realm` = ?d AND `realmGUID` = ?d AND `type` = ?d AND `typeId` = ?d AND `status` <> ?d', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WORKING))
|
||||
{
|
||||
// not on already scheduled - recalc time and set status to PR_QUEUE_STATUS_WAITING
|
||||
if ($rData['status'] != PR_QUEUE_STATUS_WAITING)
|
||||
{
|
||||
$newTime = CFG_DEBUG ? time() : max($rData['time'] + CFG_PROFILER_RESYNC_DELAY, time());
|
||||
DB::Aowow()->query('UPDATE ?_profiler_sync SET requestTime = ?d, status = ?d, errorCode = 0 WHERE realm = ?d AND realmGUID = ?d AND `type` = ?d AND typeId = ?d', $newTime, PR_QUEUE_STATUS_WAITING, $realmId, $guid, $type, $localId);
|
||||
$newTime = Cfg::get('DEBUG') ? time() : max($rData['time'] + Cfg::get('PROFILER_RESYNC_DELAY'), time());
|
||||
DB::Aowow()->query('UPDATE ?_profiler_sync SET `requestTime` = ?d, `status` = ?d, `errorCode` = 0 WHERE `realm` = ?d AND `realmGUID` = ?d AND `type` = ?d AND `typeId` = ?d', $newTime, PR_QUEUE_STATUS_WAITING, $realmId, $guid, $type, $localId);
|
||||
}
|
||||
}
|
||||
else
|
||||
DB::Aowow()->query('REPLACE INTO ?_profiler_sync (realm, realmGUID, `type`, typeId, requestTime, status, errorCode) VALUES (?d, ?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, 0)', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WAITING);
|
||||
DB::Aowow()->query('REPLACE INTO ?_profiler_sync (`realm`, `realmGUID`, `type`, `typeId`, `requestTime`, `status`, `errorCode`) VALUES (?d, ?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, 0)', $realmId, $guid, $type, $localId, PR_QUEUE_STATUS_WAITING);
|
||||
}
|
||||
|
||||
public static function scheduleResync($type, $realmId, $guid)
|
||||
@@ -235,17 +269,17 @@ class Profiler
|
||||
switch ($type)
|
||||
{
|
||||
case Type::PROFILE:
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $guid))
|
||||
self::queueInsert($realmId, $guid, Type::PROFILE, $newId);
|
||||
|
||||
break;
|
||||
case Type::GUILD:
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_guild WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $guid))
|
||||
self::queueInsert($realmId, $guid, Type::GUILD, $newId);
|
||||
|
||||
break;
|
||||
case Type::ARENA_TEAM:
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $guid))
|
||||
if ($newId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $guid))
|
||||
self::queueInsert($realmId, $guid, Type::ARENA_TEAM, $newId);
|
||||
|
||||
break;
|
||||
@@ -262,18 +296,43 @@ class Profiler
|
||||
return $newId;
|
||||
}
|
||||
|
||||
public static function resyncStatus($type, array $subjectGUIDs)
|
||||
/* return
|
||||
<status object> [
|
||||
nQueueProcesses,
|
||||
[statusCode, timeToRefresh, curQueuePos, errorCode, nResyncs],
|
||||
[<anotherStatus>]
|
||||
...
|
||||
]
|
||||
|
||||
statusCode:
|
||||
0: end the request
|
||||
1: waiting
|
||||
2: working...
|
||||
3: ready; click to view
|
||||
4: error / retry
|
||||
timeToRefresh:
|
||||
msec till the client may ask for another update
|
||||
curQueuePos:
|
||||
position in the queue
|
||||
errorCode:
|
||||
0: unk error
|
||||
1: char does not exist
|
||||
2: armory gone
|
||||
nResyncs:
|
||||
??? .. if !nResyncs && !timeToRefresh prints "Adding to queue..." but will not ping the server for updates...?
|
||||
*/
|
||||
public static function resyncStatus(int $type, array $subjectGUIDs) : string
|
||||
{
|
||||
$response = [CFG_PROFILER_ENABLE ? 2 : 0]; // in theory you could have multiple queues; used as divisor for: (15 / x) + 2
|
||||
$response = [Cfg::get('PROFILER_ENABLE') ? 2 : 0]; // in theory you could have multiple queues; used as divisor in wait time estimation: (15 / x) + 2
|
||||
if (!$subjectGUIDs)
|
||||
$response[] = [PR_QUEUE_STATUS_ENDED, 0, 0, PR_QUEUE_ERROR_CHAR];
|
||||
else
|
||||
{
|
||||
// error out all profiles with status WORKING, that are older than 60sec
|
||||
DB::Aowow()->query('UPDATE ?_profiler_sync SET status = ?d, errorCode = ?d WHERE status = ?d AND requestTime < ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_UNK, PR_QUEUE_STATUS_WORKING, time() - MINUTE);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_sync SET `status` = ?d, `errorCode` = ?d WHERE `status` = ?d AND `requestTime` < ?d', PR_QUEUE_STATUS_ERROR, PR_QUEUE_ERROR_UNK, PR_QUEUE_STATUS_WORKING, time() - MINUTE);
|
||||
|
||||
$subjectStatus = DB::Aowow()->select('SELECT typeId AS ARRAY_KEY, status, realm, errorCode FROM ?_profiler_sync WHERE `type` = ?d AND typeId IN (?a)', $type, $subjectGUIDs);
|
||||
$queue = DB::Aowow()->selectCol('SELECT CONCAT(type, ":", typeId) FROM ?_profiler_sync WHERE status = ?d AND requestTime < UNIX_TIMESTAMP() ORDER BY requestTime ASC', PR_QUEUE_STATUS_WAITING);
|
||||
$subjectStatus = DB::Aowow()->select('SELECT `typeId` AS ARRAY_KEY, `status`, `realm`, `errorCode` FROM ?_profiler_sync WHERE `type` = ?d AND `typeId` IN (?a)', $type, $subjectGUIDs);
|
||||
$queue = DB::Aowow()->selectCol('SELECT CONCAT(`type`, ":", `typeId`) FROM ?_profiler_sync WHERE `status` = ?d AND `requestTime` < UNIX_TIMESTAMP() ORDER BY `requestTime` ASC', PR_QUEUE_STATUS_WAITING);
|
||||
foreach ($subjectGUIDs as $guid)
|
||||
{
|
||||
if (empty($subjectStatus[$guid])) // whelp, thats some error..
|
||||
@@ -283,25 +342,31 @@ class Profiler
|
||||
else
|
||||
$response[] = array(
|
||||
$subjectStatus[$guid]['status'],
|
||||
$subjectStatus[$guid]['status'] != PR_QUEUE_STATUS_READY ? CFG_PROFILER_RESYNC_PING : 0,
|
||||
$subjectStatus[$guid]['status'] != PR_QUEUE_STATUS_READY ? Cfg::get('PROFILER_RESYNC_PING') : 0,
|
||||
array_search($type.':'.$guid, $queue) + 1,
|
||||
0,
|
||||
1 // nResycTries - unsure about this one
|
||||
1 // nResyncs - unsure about this one
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
return Util::toJSON($response);
|
||||
}
|
||||
|
||||
public static function getCharFromRealm($realmId, $charGuid)
|
||||
{
|
||||
$char = DB::Characters($realmId)->selectRow('SELECT c.* FROM characters c WHERE c.guid = ?d', $charGuid);
|
||||
$char = DB::Characters($realmId)->selectRow('SELECT c.* FROM characters c WHERE c.`guid` = ?d', $charGuid);
|
||||
if (!$char)
|
||||
return false;
|
||||
|
||||
if (!$char['name'])
|
||||
{
|
||||
trigger_error('char #'.$charGuid.' on realm #'.$realmId.' has empty name. skipping...', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// reminder: this query should not fail: a placeholder entry is created as soon as a char listview is created or profile detail page is called
|
||||
$profile = DB::Aowow()->selectRow('SELECT id, lastupdated FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d', $realmId, $char['guid']);
|
||||
$profile = DB::Aowow()->selectRow('SELECT `id`, `lastupdated` FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $char['guid']);
|
||||
if (!$profile)
|
||||
return false; // well ... it failed
|
||||
|
||||
@@ -311,15 +376,16 @@ class Profiler
|
||||
|
||||
if (!$char['online'] && $char['logout_time'] <= $profile['lastupdated'])
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET lastupdated = ?d WHERE id = ?d', time(), $profileId);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `lastupdated` = ?d WHERE `id` = ?d', time(), $profileId);
|
||||
CLI::write('char did not log in since last update. skipping...');
|
||||
return true;
|
||||
}
|
||||
|
||||
CLI::write('writing...');
|
||||
|
||||
$ra = (1 << ($char['race'] - 1));
|
||||
$cl = (1 << ($char['class'] - 1));
|
||||
$ra = ChrRace::from($char['race']);
|
||||
$cl = ChrClass::from($char['class']);
|
||||
|
||||
|
||||
/*************/
|
||||
/* equipment */
|
||||
@@ -337,8 +403,8 @@ class Profiler
|
||||
*/
|
||||
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE id = ?d', $profileId);
|
||||
$items = DB::Characters($realmId)->select('SELECT ci.slot AS ARRAY_KEY, ii.itemEntry, ii.enchantments, ii.randomPropertyId FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ?d AND bag = 0 AND slot BETWEEN 0 AND 18', $char['guid']);
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_items WHERE `id` = ?d', $profileId);
|
||||
$items = DB::Characters($realmId)->select('SELECT ci.`slot` AS ARRAY_KEY, ii.`itemEntry`, ii.`enchantments`, ii.`randomPropertyId` FROM character_inventory ci JOIN item_instance ii ON ci.`item` = ii.`guid` WHERE ci.`guid` = ?d AND `bag` = 0 AND `slot` BETWEEN 0 AND 18', $char['guid']);
|
||||
|
||||
$gemItems = [];
|
||||
$permEnch = [];
|
||||
@@ -355,7 +421,7 @@ class Profiler
|
||||
|
||||
if ($gEnch)
|
||||
{
|
||||
$gi = DB::Aowow()->selectCol('SELECT gemEnchantmentId AS ARRAY_KEY, id FROM ?_items WHERE class = 3 AND gemEnchantmentId IN (?a)', $gEnch);
|
||||
$gi = DB::Aowow()->selectCol('SELECT `gemEnchantmentId` AS ARRAY_KEY, `id` FROM ?_items WHERE `class` = ?d AND `gemEnchantmentId` IN (?a)', ITEM_CLASS_GEM, $gEnch);
|
||||
foreach ($gEnch as $eId)
|
||||
{
|
||||
if (isset($gemItems[$eId]))
|
||||
@@ -411,7 +477,7 @@ class Profiler
|
||||
'hairstyle' => $char['hairStyle'],
|
||||
'haircolor' => $char['hairColor'],
|
||||
'features' => $char['facialStyle'], // maybe facetype
|
||||
'title' => $char['chosenTitle'] ? DB::Aowow()->selectCell('SELECT id FROM ?_titles WHERE bitIdx = ?d', $char['chosenTitle']) : 0,
|
||||
'title' => $char['chosenTitle'] ? DB::Aowow()->selectCell('SELECT `id` FROM ?_titles WHERE `bitIdx` = ?d', $char['chosenTitle']) : 0,
|
||||
'playedtime' => $char['totaltime'],
|
||||
'nomodelMask' => ($char['playerFlags'] & 0x400 ? (1 << SLOT_HEAD) : 0) | ($char['playerFlags'] & 0x800 ? (1 << SLOT_BACK) : 0),
|
||||
'talenttree1' => 0,
|
||||
@@ -431,22 +497,23 @@ class Profiler
|
||||
// char is flagged for rename
|
||||
if ($char['at_login'] & 0x1)
|
||||
{
|
||||
$ri = DB::Aowow()->selectCell('SELECT MAX(renameItr) FROM ?_profiler_profiles WHERE realm = ?d AND realmGUID = ?d AND name = ?', $realmId, $charGuid, $char['name']);
|
||||
$ri = DB::Aowow()->selectCell('SELECT MAX(`renameItr`) FROM ?_profiler_profiles WHERE `realm` = ?d AND `realmGUID` IS NOT NULL AND `name` = ?', $realmId, $char['name']);
|
||||
$data['renameItr'] = $ri ? ++$ri : 1;
|
||||
}
|
||||
|
||||
|
||||
/********************/
|
||||
/* talents + glyphs */
|
||||
/********************/
|
||||
|
||||
$t = DB::Characters($realmId)->selectCol('SELECT talentGroup AS ARRAY_KEY, spell AS ARRAY_KEY2, spell FROM character_talent WHERE guid = ?d', $char['guid']);
|
||||
$g = DB::Characters($realmId)->select('SELECT talentGroup AS ARRAY_KEY, glyph1 AS g1, glyph2 AS g4, glyph3 AS g5, glyph4 AS g2, glyph5 AS g3, glyph6 AS g6 FROM character_glyphs WHERE guid = ?d', $char['guid']);
|
||||
$t = DB::Characters($realmId)->selectCol('SELECT `talentGroup` AS ARRAY_KEY, `spell` AS ARRAY_KEY2, `spell` FROM character_talent WHERE `guid` = ?d', $char['guid']);
|
||||
$g = DB::Characters($realmId)->select('SELECT `talentGroup` AS ARRAY_KEY, `glyph1` AS "g1", `glyph2` AS "g4", `glyph3` AS "g5", `glyph4` AS "g2", `glyph5` AS "g3", `glyph6` AS "g6" FROM character_glyphs WHERE `guid` = ?d', $char['guid']);
|
||||
for ($i = 0; $i < 2; $i++)
|
||||
{
|
||||
// talents
|
||||
for ($j = 0; $j < 3; $j++)
|
||||
{
|
||||
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell IN (?a), `rank`, 0)) FROM ?_talents WHERE class = ?d AND tab = ?d GROUP BY id ORDER BY `row`, `col` ASC', !empty($t[$i]) ? $t[$i] : [0], $char['class'], $j);
|
||||
$_ = DB::Aowow()->selectCol('SELECT `spell` AS ARRAY_KEY, MAX(IF(`spell` IN (?a), `rank`, 0)) FROM ?_talents WHERE `class` = ?d AND `tab` = ?d GROUP BY `id` ORDER BY `row`, `col` ASC', $t[$i] ?? [0], $cl->value, $j);
|
||||
$data['talentbuild'.($i + 1)] .= implode('', $_);
|
||||
if ($data['activespec'] == $i)
|
||||
$data['talenttree'.($j + 1)] = array_sum($_);
|
||||
@@ -461,10 +528,21 @@ class Profiler
|
||||
$gProps[$j] = $g[$i]['g'.$j];
|
||||
|
||||
if ($gProps)
|
||||
if ($gItems = DB::Aowow()->selectCol('SELECT i.id FROM ?_glyphproperties gp JOIN ?_spell s ON s.effect1MiscValue = gp.id AND s.effect1Id = 74 JOIN ?_items i ON i.class = 16 AND i.spellId1 = s.id WHERE gp.id IN (?a)', $gProps))
|
||||
{
|
||||
$gItems = DB::Aowow()->selectCol(
|
||||
'SELECT i.`id`
|
||||
FROM ?_glyphproperties gp
|
||||
JOIN ?_spell s ON s.`effect1MiscValue` = gp.`id` AND s.`effect1Id` = ?d
|
||||
JOIN ?_items i ON i.`class` = ?d AND i.`spellId1` = s.`id` AND (i.`cuFlags` & ?d) = 0
|
||||
WHERE gp.`id` IN (?a)',
|
||||
SPELL_EFFECT_APPLY_GLYPH, ITEM_CLASS_GLYPH, CUSTOM_DISABLED | CUSTOM_UNAVAILABLE | CUSTOM_EXCLUDE_FOR_LISTVIEW, $gProps
|
||||
);
|
||||
|
||||
if ($gItems)
|
||||
$data['glyphs'.($i + 1)] = implode(':', $gItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$t = array(
|
||||
'spent' => [$data['talenttree1'], $data['talenttree2'], $data['talenttree3']],
|
||||
@@ -494,9 +572,15 @@ class Profiler
|
||||
// enchantId => multiple spells => multiple items with varying itemlevels, quality, whatevs
|
||||
// cant reasonably get to the castItem from enchantId and slot
|
||||
|
||||
$profSpec = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, skillLevel AS "1", skillLine AS "0" FROM ?_itemenchantment WHERE id IN (?a)', $permEnch);
|
||||
foreach ($permEnch as $eId)
|
||||
$profSpec = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `skillLevel` AS "1", `skillLine` AS "0" FROM ?_itemenchantment WHERE `id` IN (?a)', $permEnch);
|
||||
foreach ($permEnch as $slot => $eId)
|
||||
{
|
||||
if (!isset($profSpec[$eId]))
|
||||
{
|
||||
trigger_error('char #'.$charGuid.' on realm #'.$realmId.' has item in slot #'.$slot.' with invalid perm enchantment #'.CLI::bold($eId), E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($x = Util::getEnchantmentScore(0, 0, !!$profSpec[$eId][1], $eId))
|
||||
$data['gearscore'] += $x;
|
||||
else if ($profSpec[$eId][0] != 776) // not runeforging
|
||||
@@ -513,22 +597,31 @@ class Profiler
|
||||
/* hunter pets */
|
||||
/***************/
|
||||
|
||||
if ($cl == CLASS_HUNTER)
|
||||
if ($cl == ChrClass::HUNTER)
|
||||
{
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_pets WHERE owner = ?d', $profileId);
|
||||
$pets = DB::Characters($realmId)->select('SELECT id AS ARRAY_KEY, id, entry, modelId, name FROM character_pet WHERE owner = ?d', $charGuid);
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_pets WHERE `owner` = ?d', $profileId);
|
||||
$pets = DB::Characters($realmId)->select('SELECT `id` AS ARRAY_KEY, `entry`, `modelId`, `name` FROM character_pet WHERE `owner` = ?d', $charGuid);
|
||||
foreach ($pets as $petGuid => $petData)
|
||||
{
|
||||
$morePet = DB::Aowow()->selectRow('SELECT p.`type`, c.family FROM ?_pet p JOIN ?_creature c ON c.family = p.id WHERE c.id = ?d', $petData['entry']);
|
||||
$petSpells = DB::Characters($realmId)->selectCol('SELECT spell FROM pet_spell WHERE guid = ?d', $petGuid);
|
||||
$petSpells = DB::Characters($realmId)->selectCol('SELECT `spell` FROM pet_spell WHERE `guid` = ?d', $petGuid);
|
||||
$morePet = DB::Aowow()->selectRow(
|
||||
'SELECT IFNULL(c3.`id`, IFNULL(c2.`id`, IFNULL(c1.`id`, c.`id`))) AS "entry", p.`type`, c.`family`
|
||||
FROM ?_pet p
|
||||
JOIN ?_creature c ON c.`family` = p.`id`
|
||||
LEFT JOIN ?_creature c1 ON c1.`difficultyEntry1` = c.`id`
|
||||
LEFT JOIN ?_creature c2 ON c2.`difficultyEntry2` = c.`id`
|
||||
LEFT JOIN ?_creature c3 ON c3.`difficultyEntry3` = c.`id`
|
||||
WHERE c.`id` = ?d',
|
||||
$petData['entry']
|
||||
);
|
||||
|
||||
$_ = DB::Aowow()->selectCol('SELECT spell AS ARRAY_KEY, MAX(IF(spell IN (?a), `rank`, 0)) FROM ?_talents WHERE class = 0 AND petTypeMask = ?d GROUP BY id ORDER BY row, col ASC', $petSpells ?: [0], 1 << $morePet['type']);
|
||||
$_ = DB::Aowow()->selectCol('SELECT `spell` AS ARRAY_KEY, MAX(IF(`spell` IN (?a), `rank`, 0)) FROM ?_talents WHERE `class` = 0 AND `petTypeMask` = ?d GROUP BY `row`, `col` ORDER BY `row`, `col` ASC', $petSpells ?: [0], 1 << $morePet['type']);
|
||||
$pet = array(
|
||||
'id' => $petGuid,
|
||||
'owner' => $profileId,
|
||||
'name' => $petData['name'],
|
||||
'family' => $morePet['family'],
|
||||
'npc' => $petData['entry'],
|
||||
'npc' => $morePet['entry'],
|
||||
'displayId' => $petData['modelId'],
|
||||
'talents' => implode('', $_)
|
||||
);
|
||||
@@ -544,95 +637,70 @@ class Profiler
|
||||
/* completion data */
|
||||
/*******************/
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion WHERE id = ?d', $profileId);
|
||||
// done quests //
|
||||
|
||||
// done quests
|
||||
if ($quests = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, quest AS typeId FROM character_queststatus_rewarded WHERE guid = ?d', $profileId, Type::QUEST, $char['guid']))
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_quests WHERE `id` = ?d', $profileId);
|
||||
|
||||
if ($quests = DB::Characters($realmId)->select('SELECT ?d AS `id`, `quest` AS `questId` FROM character_queststatus_rewarded WHERE `guid` = ?d', $profileId, $char['guid']))
|
||||
foreach (Util::createSqlBatchInsert($quests) as $q)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$q, array_keys($quests[0]));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_quests (?#) VALUES '.$q, array_keys($quests[0]));
|
||||
|
||||
CLI::write(' ..quests');
|
||||
|
||||
|
||||
// known skills (professions only)
|
||||
$skAllowed = DB::Aowow()->selectCol('SELECT id FROM ?_skillline WHERE typeCat IN (9, 11) AND (cuFlags & ?d) = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW);
|
||||
$skills = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, skill AS typeId, `value` AS cur, max FROM character_skills WHERE guid = ?d AND skill IN (?a)', $profileId, Type::SKILL, $char['guid'], $skAllowed);
|
||||
// known skills (professions only) //
|
||||
|
||||
// manually apply racial profession bonuses
|
||||
foreach ($skills as &$sk)
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_skills WHERE `id` = ?d', $profileId);
|
||||
|
||||
$skAllowed = DB::Aowow()->selectCol('SELECT `id` FROM ?_skillline WHERE `typeCat` IN (9, 11) AND (`cuFlags` & ?d) = 0', CUSTOM_EXCLUDE_FOR_LISTVIEW);
|
||||
$skills = DB::Characters($realmId)->select('SELECT ?d AS `id`, `skill` AS `skillId`, `value`, `max` FROM character_skills WHERE `guid` = ?d AND `skill` IN (?a)', $profileId, $char['guid'], $skAllowed);
|
||||
$racials = DB::Aowow()->select('SELECT `effect1MiscValue` AS ARRAY_KEY, `effect1DieSides` + `effect1BasePoints` AS qty, `reqRaceMask`, `reqClassMask` FROM ?_spell WHERE `typeCat` = -4 AND `effect1Id` = ?d AND `effect1AuraId` = ?d', SPELL_EFFECT_APPLY_AURA, SPELL_AURA_MOD_SKILL_TALENT);
|
||||
|
||||
foreach ($skills as &$sk) // apply racial profession bonuses
|
||||
{
|
||||
// Blood Elves - Arcane Affinity
|
||||
if ($sk['typeId'] == 333 && $char['race'] == 10)
|
||||
if (!isset($racials[$sk['skillId']]))
|
||||
continue;
|
||||
|
||||
$r = $racials[$sk['skillId']];
|
||||
if ($ra->matches($r['reqRaceMask']) && $cl->matches($r['reqClassMask']))
|
||||
{
|
||||
$sk['cur'] += 10;
|
||||
$sk['max'] += 10;
|
||||
}
|
||||
// Draenei - Gemcutting
|
||||
if ($sk['typeId'] == 755 && $char['race'] == 11)
|
||||
{
|
||||
$sk['cur'] += 5;
|
||||
$sk['max'] += 5;
|
||||
}
|
||||
// Tauren - Cultivation
|
||||
// Gnomes - Engineering Specialization
|
||||
if (($sk['typeId'] == 182 && $char['race'] == 6) ||
|
||||
($sk['typeId'] == 202 && $char['race'] == 7))
|
||||
{
|
||||
$sk['cur'] += 15;
|
||||
$sk['max'] += 15;
|
||||
$sk['value'] += $r['qty'];
|
||||
$sk['max'] += $r['qty'];
|
||||
}
|
||||
}
|
||||
unset($sk);
|
||||
|
||||
if ($skills)
|
||||
{
|
||||
// apply auto-learned trade skills
|
||||
DB::Aowow()->query('
|
||||
INSERT INTO ?_profiler_completion
|
||||
SELECT ?d, ?d, spellId, NULL, NULL
|
||||
FROM dbc_skilllineability
|
||||
WHERE skillLineId IN (?a) AND
|
||||
acquireMethod = 1 AND
|
||||
(reqRaceMask = 0 OR reqRaceMask & ?d) AND
|
||||
(reqClassMask = 0 OR reqClassMask & ?d)',
|
||||
$profileId, Type::SPELL,
|
||||
array_column($skills, 'typeId'),
|
||||
1 << ($char['race'] - 1),
|
||||
1 << ($char['class'] - 1)
|
||||
);
|
||||
|
||||
foreach (Util::createSqlBatchInsert($skills) as $sk)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$sk, array_keys($skills[0]));
|
||||
}
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_skills (?#) VALUES '.$sk, array_keys($skills[0]));
|
||||
|
||||
CLI::write(' ..professions');
|
||||
|
||||
|
||||
// reputation
|
||||
// reputation //
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_reputation WHERE `id` = ?d', $profileId);
|
||||
|
||||
// get base values for this race/class
|
||||
$reputation = [];
|
||||
$baseRep = DB::Aowow()->selectCol('
|
||||
SELECT id AS ARRAY_KEY, baseRepValue1 FROM aowow_factions WHERE baseRepValue1 && (baseRepRaceMask1 & ?d || (!baseRepRaceMask1 AND baseRepClassMask1)) &&
|
||||
((baseRepClassMask1 & ?d) || !baseRepClassMask1) UNION
|
||||
SELECT id AS ARRAY_KEY, baseRepValue2 FROM aowow_factions WHERE baseRepValue2 && (baseRepRaceMask2 & ?d || (!baseRepRaceMask2 AND baseRepClassMask2)) &&
|
||||
((baseRepClassMask2 & ?d) || !baseRepClassMask2) UNION
|
||||
SELECT id AS ARRAY_KEY, baseRepValue3 FROM aowow_factions WHERE baseRepValue3 && (baseRepRaceMask3 & ?d || (!baseRepRaceMask3 AND baseRepClassMask3)) &&
|
||||
((baseRepClassMask3 & ?d) || !baseRepClassMask3) UNION
|
||||
SELECT id AS ARRAY_KEY, baseRepValue4 FROM aowow_factions WHERE baseRepValue4 && (baseRepRaceMask4 & ?d || (!baseRepRaceMask4 AND baseRepClassMask4)) &&
|
||||
((baseRepClassMask4 & ?d) || !baseRepClassMask4)
|
||||
', $ra, $cl, $ra, $cl, $ra, $cl, $ra, $cl);
|
||||
$baseRep = DB::Aowow()->selectCol(
|
||||
'SELECT `id` AS ARRAY_KEY, `baseRepValue1` FROM aowow_factions WHERE `baseRepValue1` AND (`baseRepRaceMask1` & ?d OR (`baseRepClassMask1` AND NOT `baseRepRaceMask1`)) AND ((`baseRepClassMask1` & ?d) OR NOT `baseRepClassMask1`) UNION
|
||||
SELECT `id` AS ARRAY_KEY, `baseRepValue2` FROM aowow_factions WHERE `baseRepValue2` AND (`baseRepRaceMask2` & ?d OR (`baseRepClassMask2` AND NOT `baseRepRaceMask2`)) AND ((`baseRepClassMask2` & ?d) OR NOT `baseRepClassMask2`) UNION
|
||||
SELECT `id` AS ARRAY_KEY, `baseRepValue3` FROM aowow_factions WHERE `baseRepValue3` AND (`baseRepRaceMask3` & ?d OR (`baseRepClassMask3` AND NOT `baseRepRaceMask3`)) AND ((`baseRepClassMask3` & ?d) OR NOT `baseRepClassMask3`) UNION
|
||||
SELECT `id` AS ARRAY_KEY, `baseRepValue4` FROM aowow_factions WHERE `baseRepValue4` AND (`baseRepRaceMask4` & ?d OR (`baseRepClassMask4` AND NOT `baseRepRaceMask4`)) AND ((`baseRepClassMask4` & ?d) OR NOT `baseRepClassMask4`)',
|
||||
$ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask(), $ra->toMask(), $cl->toMask()
|
||||
);
|
||||
|
||||
if ($reputation = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, faction AS typeId, standing AS cur FROM character_reputation WHERE guid = ?d AND (flags & 0x4) = 0', $profileId, Type::FACTION, $char['guid']))
|
||||
if ($reputation = DB::Characters($realmId)->select('SELECT ?d AS `id`, `faction` AS `factionId`, `standing` FROM character_reputation WHERE `guid` = ?d AND (`flags` & 0x4) = 0', $profileId, $char['guid']))
|
||||
{
|
||||
// merge back base values for encountered factions
|
||||
foreach ($reputation as &$set)
|
||||
{
|
||||
if (empty($baseRep[$set['typeId']]))
|
||||
if (empty($baseRep[$set['factionId']]))
|
||||
continue;
|
||||
|
||||
$set['cur'] += $baseRep[$set['typeId']];
|
||||
unset($baseRep[$set['typeId']]);
|
||||
$set['standing'] += $baseRep[$set['factionId']];
|
||||
unset($baseRep[$set['factionId']]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,18 +708,20 @@ class Profiler
|
||||
foreach ($baseRep as $id => $val)
|
||||
$reputation[] = array(
|
||||
'id' => $profileId,
|
||||
'type' => Type::FACTION,
|
||||
'typeId' => $id,
|
||||
'cur' => $val
|
||||
'factionId' => $id,
|
||||
'standing' => $val
|
||||
);
|
||||
|
||||
foreach (Util::createSqlBatchInsert($reputation) as $rep)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$rep, array_keys($reputation[0]));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_reputation (?#) VALUES '.$rep, array_keys($reputation[0]));
|
||||
|
||||
CLI::write(' ..reputation');
|
||||
|
||||
|
||||
// known titles
|
||||
// known titles //
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_titles WHERE `id` = ?d', $profileId);
|
||||
|
||||
$tBlocks = explode(' ', $char['knownTitles']);
|
||||
$indizes = [];
|
||||
for ($i = 0; $i < 6; $i++)
|
||||
@@ -660,38 +730,63 @@ class Profiler
|
||||
$indizes[] = $j + ($i * 32);
|
||||
|
||||
if ($indizes)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion SELECT ?d, ?d, id, NULL, NULL FROM ?_titles WHERE bitIdx IN (?a)', $profileId, Type::TITLE, $indizes);
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_titles SELECT ?d, `id` FROM ?_titles WHERE `bitIdx` IN (?a)', $profileId, $indizes);
|
||||
|
||||
CLI::write(' ..titles');
|
||||
|
||||
|
||||
// achievements
|
||||
if ($achievements = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, achievement AS typeId, date AS cur FROM character_achievement WHERE guid = ?d', $profileId, Type::ACHIEVEMENT, $char['guid']))
|
||||
// achievements //
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_achievements WHERE `id` = ?d', $profileId);
|
||||
|
||||
if ($achievements = DB::Characters($realmId)->select('SELECT ?d AS id, `achievement` AS `achievementId`, `date` FROM character_achievement WHERE `guid` = ?d', $profileId, $char['guid']))
|
||||
{
|
||||
foreach (Util::createSqlBatchInsert($achievements) as $a)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$a, array_keys($achievements[0]));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_achievements (?#) VALUES '.$a, array_keys($achievements[0]));
|
||||
|
||||
$data['achievementpoints'] = DB::Aowow()->selectCell('SELECT SUM(points) FROM ?_achievement WHERE id IN (?a)', array_column($achievements, 'typeId'));
|
||||
$data['achievementpoints'] = DB::Aowow()->selectCell('SELECT SUM(`points`) FROM ?_achievement WHERE `id` IN (?a) AND (`flags` & ?d) = 0', array_column($achievements, 'achievementId'), ACHIEVEMENT_FLAG_COUNTER);
|
||||
}
|
||||
|
||||
CLI::write(' ..achievements');
|
||||
|
||||
|
||||
// raid progression
|
||||
if ($progress = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, criteria AS typeId, date AS cur, counter AS `max` FROM character_achievement_progress WHERE guid = ?d AND criteria IN (?a)', $profileId, Type::ACHIEVEMENT, $char['guid'], self::$raidProgression))
|
||||
// raid progression //
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_statistics WHERE `id` = ?d', $profileId);
|
||||
|
||||
if ($progress = DB::Characters($realmId)->select('SELECT ?d AS `id`, `criteria` AS `achievementId`, `date`, `counter` FROM character_achievement_progress WHERE `guid` = ?d AND `criteria` IN (?a)', $profileId, $char['guid'], self::$raidProgression))
|
||||
{
|
||||
array_walk($progress, function (&$val) { $val['typeId'] = array_search($val['typeId'], self::$raidProgression); });
|
||||
array_walk($progress, function (&$val) { $val['achievementId'] = array_search($val['achievementId'], self::$raidProgression); });
|
||||
foreach (Util::createSqlBatchInsert($progress) as $p)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$p, array_keys($progress[0]));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_statistics (?#) VALUES '.$p, array_keys($progress[0]));
|
||||
}
|
||||
|
||||
CLI::write(' ..raid progression');
|
||||
|
||||
|
||||
// known spells
|
||||
if ($spells = DB::Characters($realmId)->select('SELECT ?d AS id, ?d AS `type`, spell AS typeId FROM character_spell WHERE guid = ?d AND disabled = 0', $profileId, Type::SPELL, $char['guid']))
|
||||
// known spells //
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_completion_spells WHERE `id` = ?d', $profileId);
|
||||
|
||||
if ($spells = DB::Characters($realmId)->select('SELECT ?d AS `id`, `spell` AS `spellId` FROM character_spell WHERE `guid` = ?d AND `disabled` = 0', $profileId, $char['guid']))
|
||||
foreach (Util::createSqlBatchInsert($spells) as $s)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion (?#) VALUES '.$s, array_keys($spells[0]));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_completion_spells (?#) VALUES '.$s, array_keys($spells[0]));
|
||||
|
||||
// apply auto-learned spells from trade skills
|
||||
if ($skills)
|
||||
DB::Aowow()->query(
|
||||
'INSERT INTO ?_profiler_completion_spells
|
||||
SELECT ?d, `spellId`
|
||||
FROM ?_skilllineability
|
||||
WHERE `skillLineId` IN (?a) AND
|
||||
`acquireMethod` = 1 AND
|
||||
(`reqRaceMask` = 0 OR `reqRaceMask` & ?d) AND
|
||||
(`reqClassMask` = 0 OR `reqClassMask` & ?d)',
|
||||
$profileId,
|
||||
array_column($skills, 'skillId'),
|
||||
$ra->toMask(),
|
||||
$cl->toMask()
|
||||
);
|
||||
|
||||
CLI::write(' ..known spells (vanity pets & mounts)');
|
||||
|
||||
@@ -701,7 +796,7 @@ class Profiler
|
||||
/****************/
|
||||
|
||||
// guilds
|
||||
if ($guild = DB::Characters($realmId)->selectRow('SELECT g.name AS name, g.guildid AS id, gm.rank FROM guild_member gm JOIN guild g ON g.guildid = gm.guildid WHERE gm.guid = ?d', $char['guid']))
|
||||
if ($guild = DB::Characters($realmId)->selectRow('SELECT g.`name` AS `name`, g.`guildid` AS `id`, gm.`rank` FROM guild_member gm JOIN guild g ON g.`guildid` = gm.`guildid` WHERE gm.`guid` = ?d', $char['guid']))
|
||||
{
|
||||
$guildId = 0;
|
||||
if (!($guildId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guild['id'])))
|
||||
@@ -721,13 +816,15 @@ class Profiler
|
||||
$data['guildRank'] = $guild['rank'];
|
||||
}
|
||||
|
||||
CLI::write(' ..basic guild data');
|
||||
|
||||
|
||||
// arena teams
|
||||
$teams = DB::Characters($realmId)->select('SELECT at.arenaTeamId AS ARRAY_KEY, at.name, at.type, IF(at.captainGuid = atm.guid, 1, 0) AS captain, atm.* FROM arena_team at JOIN arena_team_member atm ON atm.arenaTeamId = at.arenaTeamId WHERE atm.guid = ?d', $char['guid']);
|
||||
$teams = DB::Characters($realmId)->select('SELECT at.`arenaTeamId` AS ARRAY_KEY, at.`name`, at.`type`, IF(at.`captainGuid` = atm.`guid`, 1, 0) AS `captain`, atm.* FROM arena_team at JOIN arena_team_member atm ON atm.`arenaTeamId` = at.`arenaTeamId` WHERE atm.`guid` = ?d', $char['guid']);
|
||||
foreach ($teams as $rGuid => $t)
|
||||
{
|
||||
$teamId = 0;
|
||||
if (!($teamId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $rGuid)))
|
||||
if (!($teamId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $rGuid)))
|
||||
{
|
||||
$team = array( // only most basic data
|
||||
'realm' => $realmId,
|
||||
@@ -752,6 +849,15 @@ class Profiler
|
||||
'personalRating' => $t['personalRating']
|
||||
);
|
||||
|
||||
// Delete members from other teams of the same type
|
||||
DB::Aowow()->query(
|
||||
'DELETE atm
|
||||
FROM ?_profiler_arena_team_member atm
|
||||
JOIN ?_profiler_arena_team at ON atm.`arenaTeamId` = at.`id` AND at.`type` = ?d
|
||||
WHERE atm.`profileId` = ?d AND atm.`arenaTeamId` <> ?d',
|
||||
$t['type'], $profileId, $teamId
|
||||
);
|
||||
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES (?a) ON DUPLICATE KEY UPDATE ?a', array_keys($member), array_values($member), array_slice($member, 2));
|
||||
}
|
||||
|
||||
@@ -761,20 +867,26 @@ class Profiler
|
||||
/* mark char as done */
|
||||
/*********************/
|
||||
|
||||
if (DB::Aowow()->query('UPDATE ?_profiler_profiles SET ?a WHERE realm = ?d AND realmGUID = ?d', $data, $realmId, $charGuid) !== null)
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $profileId);
|
||||
if (DB::Aowow()->query('UPDATE ?_profiler_profiles SET ?a WHERE `realm` = ?d AND `realmGUID` = ?d', $data, $realmId, $charGuid) !== null)
|
||||
DB::Aowow()->query('UPDATE ?_profiler_profiles SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $profileId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getGuildFromRealm($realmId, $guildGuid)
|
||||
{
|
||||
$guild = DB::Characters($realmId)->selectRow('SELECT guildId, name, createDate, info, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM guild WHERE guildId = ?d', $guildGuid);
|
||||
$guild = DB::Characters($realmId)->selectRow('SELECT `guildId`, `name`, `createDate`, `info`, `backgroundColor`, `emblemStyle`, `emblemColor`, `borderStyle`, `borderColor` FROM guild WHERE `guildId` = ?d', $guildGuid);
|
||||
if (!$guild)
|
||||
return false;
|
||||
|
||||
if (!$guild['name'])
|
||||
{
|
||||
trigger_error('guild #'.$guildGuid.' on realm #'.$realmId.' has empty name. skipping...', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// reminder: this query should not fail: a placeholder entry is created as soon as a team listview is created or team detail page is called
|
||||
$guildId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_guild WHERE realm = ?d AND realmGUID = ?d', $realmId, $guild['guildId']);
|
||||
$guildId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_guild WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $guild['guildId']);
|
||||
|
||||
CLI::write('fetching guild #'.$guildGuid.' from realm #'.$realmId);
|
||||
CLI::write('writing...');
|
||||
@@ -787,11 +899,11 @@ class Profiler
|
||||
unset($guild['guildId']);
|
||||
$guild['nameUrl'] = self::urlize($guild['name']);
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_profiler_guild SET ?a WHERE realm = ?d AND realmGUID = ?d', $guild, $realmId, $guildGuid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_guild SET ?a WHERE `realm` = ?d AND `realmGUID` = ?d', $guild, $realmId, $guildGuid);
|
||||
|
||||
// ranks
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_guild_rank WHERE guildId = ?d', $guildId);
|
||||
if ($ranks = DB::Characters($realmId)->select('SELECT ?d AS guildId, rid AS `rank`, rname AS name FROM guild_rank WHERE guildid = ?d', $guildId, $guildGuid))
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_guild_rank WHERE `guildId` = ?d', $guildId);
|
||||
if ($ranks = DB::Characters($realmId)->select('SELECT ?d AS `guildId`, `rid` AS "rank", `rname` AS "name" FROM guild_rank WHERE `guildid` = ?d', $guildId, $guildGuid))
|
||||
foreach (Util::createSqlBatchInsert($ranks) as $r)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_guild_rank (?#) VALUES '.$r, array_keys(reset($ranks)));
|
||||
|
||||
@@ -823,19 +935,25 @@ class Profiler
|
||||
/* mark guild as done */
|
||||
/*********************/
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_profiler_guild SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $guildId);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_guild SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $guildId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getArenaTeamFromRealm($realmId, $teamGuid)
|
||||
{
|
||||
$team = DB::Characters($realmId)->selectRow('SELECT arenaTeamId, name, type, captainGuid, rating, seasonGames, seasonWins, weekGames, weekWins, `rank`, backgroundColor, emblemStyle, emblemColor, borderStyle, borderColor FROM arena_team WHERE arenaTeamId = ?d', $teamGuid);
|
||||
$team = DB::Characters($realmId)->selectRow('SELECT `arenaTeamId`, `name`, `type`, `captainGuid`, `rating`, `seasonGames`, `seasonWins`, `weekGames`, `weekWins`, `rank`, `backgroundColor`, `emblemStyle`, `emblemColor`, `borderStyle`, `borderColor` FROM arena_team WHERE `arenaTeamId` = ?d', $teamGuid);
|
||||
if (!$team)
|
||||
return false;
|
||||
|
||||
if (!$team['name'])
|
||||
{
|
||||
trigger_error('arena team #'.$teamGuid.' on realm #'.$realmId.' has empty name. skipping...', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
// reminder: this query should not fail: a placeholder entry is created as soon as a team listview is created or team detail page is called
|
||||
$teamId = DB::Aowow()->selectCell('SELECT id FROM ?_profiler_arena_team WHERE realm = ?d AND realmGUID = ?d', $realmId, $team['arenaTeamId']);
|
||||
$teamId = DB::Aowow()->selectCell('SELECT `id` FROM ?_profiler_arena_team WHERE `realm` = ?d AND `realmGUID` = ?d', $realmId, $team['arenaTeamId']);
|
||||
|
||||
CLI::write('fetching arena team #'.$teamGuid.' from realm #'.$realmId);
|
||||
CLI::write('writing...');
|
||||
@@ -850,7 +968,7 @@ class Profiler
|
||||
unset($team['arenaTeamId']);
|
||||
$team['nameUrl'] = self::urlize($team['name']);
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET ?a WHERE realm = ?d AND realmGUID = ?d', $team, $realmId, $teamGuid);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET ?a WHERE `realm` = ?d AND `realmGUID` = ?d', $team, $realmId, $teamGuid);
|
||||
|
||||
CLI::write(' ..team data');
|
||||
|
||||
@@ -859,18 +977,14 @@ class Profiler
|
||||
/* Member Data */
|
||||
/***************/
|
||||
|
||||
$members = DB::Characters($realmId)->select('
|
||||
SELECT
|
||||
atm.guid AS ARRAY_KEY, atm.arenaTeamId, atm.weekGames, atm.weekWins, atm.seasonGames, atm.seasonWins, atm.personalrating
|
||||
FROM
|
||||
arena_team_member atm
|
||||
JOIN
|
||||
characters c ON c.guid = atm.guid AND
|
||||
c.deleteInfos_Account IS NULL AND
|
||||
c.level <= ?d AND
|
||||
(c.extra_flags & ?d) = 0
|
||||
WHERE
|
||||
arenaTeamId = ?d',
|
||||
$members = DB::Characters($realmId)->select(
|
||||
'SELECT atm.`guid` AS ARRAY_KEY, atm.`arenaTeamId`, atm.`weekGames`, atm.`weekWins`, atm.`seasonGames`, atm.`seasonWins`, atm.`personalrating`
|
||||
FROM arena_team_member atm
|
||||
JOIN characters c ON c.`guid` = atm.`guid` AND
|
||||
c.`deleteInfos_Account` IS NULL AND
|
||||
c.`level` <= ?d AND
|
||||
(c.`extra_flags` & ?d) = 0
|
||||
WHERE `arenaTeamId` = ?d',
|
||||
MAX_LEVEL,
|
||||
self::CHAR_GMFLAGS,
|
||||
$teamGuid
|
||||
@@ -884,8 +998,9 @@ class Profiler
|
||||
);
|
||||
|
||||
$mProfiles = new RemoteProfileList($conditions, ['sv' => $realmId]);
|
||||
if (!$mProfiles->error)
|
||||
{
|
||||
if ($mProfiles->error)
|
||||
return false;
|
||||
|
||||
$mProfiles->initializeLocalEntries();
|
||||
foreach ($mProfiles->iterate() as $__)
|
||||
{
|
||||
@@ -897,22 +1012,30 @@ class Profiler
|
||||
$members[$mGuid]['profileId'] = $mProfiles->getField('id');
|
||||
}
|
||||
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_arena_team_member WHERE arenaTeamId = ?d', $teamId);
|
||||
// Delete members from other teams of the same type...
|
||||
DB::Aowow()->query(
|
||||
'DELETE atm
|
||||
FROM ?_profiler_arena_team_member atm
|
||||
JOIN ?_profiler_arena_team at ON atm.`arenaTeamId` = at.`id` AND at.`type` = ?d
|
||||
WHERE atm.`profileId` IN (?a)',
|
||||
$team['type'],
|
||||
array_column($members, 'profileId')
|
||||
);
|
||||
|
||||
// ...and purge this teams member
|
||||
DB::Aowow()->query('DELETE FROM ?_profiler_arena_team_member WHERE `arenaTeamId` = ?d', $teamId);
|
||||
|
||||
foreach (Util::createSqlBatchInsert($members) as $m)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES '.$m, array_keys(reset($members)));
|
||||
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
CLI::write(' ..team members');
|
||||
|
||||
|
||||
/*********************/
|
||||
/* mark team as done */
|
||||
/*********************/
|
||||
|
||||
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET cuFlags = cuFlags & ?d WHERE id = ?d', ~PROFILER_CU_NEEDS_RESYNC, $teamId);
|
||||
DB::Aowow()->query('UPDATE ?_profiler_arena_team SET `cuFlags` = `cuFlags` & ?d WHERE `id` = ?d', ~PROFILER_CU_NEEDS_RESYNC, $teamId);
|
||||
|
||||
return true;
|
||||
}
|
||||
274
includes/components/report.class.php
Normal file
274
includes/components/report.class.php
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Report
|
||||
{
|
||||
public const MODE_GENERAL = 0;
|
||||
public const MODE_COMMENT = 1;
|
||||
public const MODE_FORUM_POST = 2;
|
||||
public const MODE_SCREENSHOT = 3;
|
||||
public const MODE_CHARACTER = 4;
|
||||
public const MODE_VIDEO = 5;
|
||||
public const MODE_GUIDE = 6;
|
||||
|
||||
public const GEN_FEEDBACK = 1;
|
||||
public const GEN_BUG_REPORT = 2;
|
||||
public const GEN_TYPO_TRANSLATION = 3;
|
||||
public const GEN_OP_ADVERTISING = 4;
|
||||
public const GEN_OP_PARTNERSHIP = 5;
|
||||
public const GEN_PRESS_INQUIRY = 6;
|
||||
public const GEN_MISCELLANEOUS = 7;
|
||||
public const GEN_MISINFORMATION = 8;
|
||||
public const CO_ADVERTISING = 15;
|
||||
public const CO_INACCURATE = 16;
|
||||
public const CO_OUT_OF_DATE = 17;
|
||||
public const CO_SPAM = 18;
|
||||
public const CO_INAPPROPRIATE = 19;
|
||||
public const CO_MISCELLANEOUS = 20;
|
||||
public const FO_ADVERTISING = 30;
|
||||
public const FO_AVATAR = 31;
|
||||
public const FO_INACCURATE = 32;
|
||||
public const FO_OUT_OF_DATE = 33;
|
||||
public const FO_SPAM = 34;
|
||||
public const FO_STICKY_REQUEST = 35;
|
||||
public const FO_INAPPROPRIATE = 36;
|
||||
public const FO_MISCELLANEOUS = 37;
|
||||
public const SS_INACCURATE = 45;
|
||||
public const SS_OUT_OF_DATE = 46;
|
||||
public const SS_INAPPROPRIATE = 47;
|
||||
public const SS_MISCELLANEOUS = 48;
|
||||
public const PR_INACCURATE_DATA = 60;
|
||||
public const PR_MISCELLANEOUS = 61;
|
||||
public const VI_INACCURATE = 45;
|
||||
public const VI_OUT_OF_DATE = 46;
|
||||
public const VI_INAPPROPRIATE = 47;
|
||||
public const VI_MISCELLANEOUS = 48;
|
||||
public const AR_INACCURATE = 45;
|
||||
public const AR_OUT_OF_DATE = 46;
|
||||
public const AR_MISCELLANEOUS = 48;
|
||||
|
||||
private array $context = array(
|
||||
self::MODE_GENERAL => array(
|
||||
self::GEN_FEEDBACK => true,
|
||||
self::GEN_BUG_REPORT => true,
|
||||
self::GEN_TYPO_TRANSLATION => true,
|
||||
self::GEN_OP_ADVERTISING => true,
|
||||
self::GEN_OP_PARTNERSHIP => true,
|
||||
self::GEN_PRESS_INQUIRY => true,
|
||||
self::GEN_MISCELLANEOUS => true,
|
||||
self::GEN_MISINFORMATION => true
|
||||
),
|
||||
self::MODE_COMMENT => array(
|
||||
self::CO_ADVERTISING => U_GROUP_MODERATOR,
|
||||
self::CO_INACCURATE => true,
|
||||
self::CO_OUT_OF_DATE => true,
|
||||
self::CO_SPAM => U_GROUP_MODERATOR,
|
||||
self::CO_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::CO_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_FORUM_POST => array(
|
||||
self::FO_ADVERTISING => U_GROUP_MODERATOR,
|
||||
self::FO_AVATAR => true,
|
||||
self::FO_INACCURATE => true,
|
||||
self::FO_OUT_OF_DATE => U_GROUP_MODERATOR,
|
||||
self::FO_SPAM => U_GROUP_MODERATOR,
|
||||
self::FO_STICKY_REQUEST => U_GROUP_MODERATOR,
|
||||
self::FO_INAPPROPRIATE => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_SCREENSHOT => array(
|
||||
self::SS_INACCURATE => true,
|
||||
self::SS_OUT_OF_DATE => true,
|
||||
self::SS_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::SS_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_CHARACTER => array(
|
||||
self::PR_INACCURATE_DATA => true,
|
||||
self::PR_MISCELLANEOUS => true
|
||||
),
|
||||
self::MODE_VIDEO => array(
|
||||
self::VI_INACCURATE => true,
|
||||
self::VI_OUT_OF_DATE => true,
|
||||
self::VI_INAPPROPRIATE => U_GROUP_MODERATOR,
|
||||
self::VI_MISCELLANEOUS => U_GROUP_MODERATOR
|
||||
),
|
||||
self::MODE_GUIDE => array(
|
||||
self::AR_INACCURATE => true,
|
||||
self::AR_OUT_OF_DATE => true,
|
||||
self::AR_MISCELLANEOUS => true
|
||||
)
|
||||
);
|
||||
|
||||
private const ERR_NONE = 0; // aka: success
|
||||
private const ERR_INVALID_CAPTCHA = 1; // captcha not in use
|
||||
private const ERR_DESC_TOO_LONG = 2;
|
||||
private const ERR_NO_DESC = 3;
|
||||
private const ERR_ALREADY_REPORTED = 7;
|
||||
private const ERR_MISCELLANEOUS = -1;
|
||||
|
||||
public const STATUS_OPEN = 0;
|
||||
public const STATUS_ASSIGNED = 1;
|
||||
public const STATUS_CLOSED_WONTFIX = 2;
|
||||
public const STATUS_CLOSED_SOLVED = 3;
|
||||
|
||||
private int $errorCode = self::ERR_NONE;
|
||||
|
||||
|
||||
public function __construct(private int $mode, private int $reason, private ?int $subject = 0)
|
||||
{
|
||||
if ($mode < 0 || $reason <= 0)
|
||||
{
|
||||
trigger_error('Report - malformed contact request received', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->context[$mode][$reason]))
|
||||
{
|
||||
trigger_error('Report - report has invalid context (mode:'.$mode.' / reason:'.$reason.')', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!User::isLoggedIn() && !User::$ip)
|
||||
{
|
||||
trigger_error('Report - could not determine IP for anonymous user', E_USER_ERROR);
|
||||
$this->errorCode = self::ERR_MISCELLANEOUS;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->subject ??= 0; // 0 for utility, tools and misc pages?
|
||||
}
|
||||
|
||||
private function checkTargetContext() : int
|
||||
{
|
||||
// check already reported
|
||||
$field = User::isLoggedIn() ? 'userId' : 'ip';
|
||||
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_reports WHERE `mode` = ?d AND `reason`= ?d AND `subject` = ?d AND ?# = ?', $this->mode, $this->reason, $this->subject, $field, User::$id ?: User::$ip))
|
||||
return self::ERR_ALREADY_REPORTED;
|
||||
|
||||
// check targeted post/postOwner staff status
|
||||
$ctxCheck = $this->context[$this->mode][$this->reason];
|
||||
if (is_int($ctxCheck))
|
||||
{
|
||||
$roles = User::$groups;
|
||||
if ($this->mode == self::MODE_COMMENT)
|
||||
$roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_comments WHERE `id` = ?d', $this->subject);
|
||||
// else if if ($this->mode == self::MODE_FORUM_POST)
|
||||
// $roles = DB::Aowow()->selectCell('SELECT `roles` FROM ?_forum_posts WHERE `id` = ?d', $this->subject);
|
||||
|
||||
return $roles & $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS;
|
||||
}
|
||||
else
|
||||
return $ctxCheck ? self::ERR_NONE : self::ERR_MISCELLANEOUS;
|
||||
|
||||
// Forum not in use, else:
|
||||
// check post owner
|
||||
// User::$id == post.op && !post.sticky;
|
||||
// check user custom avatar
|
||||
// g_users[post.user].avatar == 2 && (post.roles & U_GROUP_MODERATOR) == 0
|
||||
}
|
||||
|
||||
public function create(string $desc, ?string $userAgent = null, ?string $appName = null, ?string $pageUrl = null, ?string $relUrl = null, ?string $email = null) : bool
|
||||
{
|
||||
if ($this->errorCode)
|
||||
return false;
|
||||
|
||||
if (!$desc)
|
||||
{
|
||||
$this->errorCode = self::ERR_NO_DESC;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mb_strlen($desc) > 500)
|
||||
{
|
||||
$this->errorCode = self::ERR_DESC_TOO_LONG;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($err = $this->checkTargetContext())
|
||||
{
|
||||
$this->errorCode = $err;
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = array(
|
||||
'userId' => User::$id,
|
||||
'createDate' => time(),
|
||||
'mode' => $this->mode,
|
||||
'reason' => $this->reason,
|
||||
'subject' => $this->subject,
|
||||
'ip' => User::$ip,
|
||||
'description' => $desc,
|
||||
'userAgent' => $userAgent ?: User::$agent,
|
||||
'appName' => $appName ?: (get_browser(null, true)['browser'] ?: '')
|
||||
);
|
||||
|
||||
if ($pageUrl)
|
||||
$update['url'] = $pageUrl;
|
||||
|
||||
if ($relUrl)
|
||||
$update['relatedurl'] = $relUrl;
|
||||
|
||||
if ($email)
|
||||
$update['email'] = $email;
|
||||
|
||||
return DB::Aowow()->query('INSERT INTO ?_reports (?#) VALUES (?a)', array_keys($update), array_values($update));
|
||||
}
|
||||
|
||||
public function getSimilar(int ...$status) : array
|
||||
{
|
||||
if ($this->errorCode)
|
||||
return [];
|
||||
|
||||
foreach ($status as &$s)
|
||||
if ($s < self::STATUS_OPEN || $s > self::STATUS_CLOSED_SOLVED)
|
||||
unset($s);
|
||||
|
||||
return DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, r.* FROM ?_reports r WHERE {`status` IN (?a) AND }`mode` = ?d AND `reason` = ?d AND `subject` = ?d',
|
||||
$status ?: DBSIMPLE_SKIP, $this->mode, $this->reason, $this->subject);
|
||||
}
|
||||
|
||||
public function close(int $closeStatus, bool $inclAssigned = false) : bool
|
||||
{
|
||||
if ($closeStatus != self::STATUS_CLOSED_SOLVED && $closeStatus != self::STATUS_CLOSED_WONTFIX)
|
||||
return false;
|
||||
|
||||
if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD))
|
||||
return false;
|
||||
|
||||
$fromStatus = [self::STATUS_OPEN];
|
||||
if ($inclAssigned)
|
||||
$fromStatus[] = self::STATUS_ASSIGNED;
|
||||
|
||||
if ($reports = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `userId` FROM ?_reports WHERE `status` IN (?a) AND `mode` = ?d AND `reason` = ?d AND `subject` = ?d',
|
||||
$fromStatus, $this->mode, $this->reason, $this->subject))
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_reports SET `status` = ?d, `assigned` = 0 WHERE `id` IN (?a)', $closeStatus, array_keys($reports));
|
||||
|
||||
foreach ($reports as $rId => $uId)
|
||||
Util::gainSiteReputation($uId, $closeStatus == self::STATUS_CLOSED_SOLVED ? SITEREP_ACTION_GOOD_REPORT : SITEREP_ACTION_BAD_REPORT, ['id' => $rId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reopen(int $assignedTo = 0) : bool
|
||||
{
|
||||
// assignedTo = 0 ? status = STATUS_OPEN : status = STATUS_ASSIGNED, userId = assignedTo
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getError() : int
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -11,42 +13,46 @@ if (!defined('AOWOW_REVISION'))
|
||||
|
||||
class DB
|
||||
{
|
||||
private static $interfaceCache = [];
|
||||
private static $optionsCache = [];
|
||||
private static $connectionCache = [];
|
||||
private static array $interfaceCache = [];
|
||||
private static array $optionsCache = [];
|
||||
private static array $logs = [];
|
||||
|
||||
private static $logs = [];
|
||||
|
||||
private static function createConnectSyntax(&$options)
|
||||
private static function createConnectSyntax(array &$options) : string
|
||||
{
|
||||
return 'mysqli://'.$options['user'].':'.$options['pass'].'@'.$options['host'].'/'.$options['db'];
|
||||
}
|
||||
|
||||
public static function connect($idx)
|
||||
public static function connect(int $idx) : void
|
||||
{
|
||||
if (self::isConnected($idx))
|
||||
return;
|
||||
{
|
||||
self::$interfaceCache[$idx]->link->close();
|
||||
self::$interfaceCache[$idx] = null;
|
||||
}
|
||||
|
||||
$options = &self::$optionsCache[$idx];
|
||||
$interface = DbSimple_Generic::connect(self::createConnectSyntax($options));
|
||||
$interface = \DbSimple_Generic::connect(self::createConnectSyntax($options));
|
||||
|
||||
if (!$interface || $interface->error)
|
||||
die('Failed to connect to database on index #'.$idx.".\n");
|
||||
|
||||
$interface->setErrorHandler(['DB', 'errorHandler']);
|
||||
$interface->query('SET NAMES ?', 'utf8mb4');
|
||||
$interface->setErrorHandler(self::errorHandler(...));
|
||||
if ($options['prefix'])
|
||||
$interface->setIdentPrefix($options['prefix']);
|
||||
|
||||
// disable STRICT_TRANS_TABLES and STRICT_ALL_TABLES off. It prevents usage of implicit default values.
|
||||
if ($idx == DB_AOWOW)
|
||||
$interface->query("SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION'");
|
||||
// disable ONLY_FULL_GROUP_BY (Allows for non-aggregated selects in a group-by query)
|
||||
else
|
||||
$interface->query("SET SESSION sql_mode = ''");
|
||||
|
||||
self::$interfaceCache[$idx] = &$interface;
|
||||
self::$connectionCache[$idx] = true;
|
||||
|
||||
// should be caught by registered error handler
|
||||
if (!$interface || !$interface->link)
|
||||
return;
|
||||
|
||||
$interface->query('SET NAMES ?', 'utf8mb4');
|
||||
|
||||
// disable STRICT_TRANS_TABLES and STRICT_ALL_TABLES off. It prevents usage of implicit default values.
|
||||
// disable ONLY_FULL_GROUP_BY (Allows for non-aggregated selects in a group-by query)
|
||||
$extraModes = ['STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES', 'ONLY_FULL_GROUP_BY', 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE', 'ERROR_FOR_DIVISION_BY_ZERO'];
|
||||
$oldModes = explode(',', $interface->selectCell('SELECT @@sql_mode'));
|
||||
$newModes = array_diff($oldModes, $extraModes);
|
||||
|
||||
if ($oldModes != $newModes)
|
||||
$interface->query("SET SESSION sql_mode = ?", implode(',', $newModes));
|
||||
}
|
||||
|
||||
public static function test(array $options, ?string &$err = '') : bool
|
||||
@@ -56,33 +62,41 @@ class DB
|
||||
if (strstr($options['host'], ':'))
|
||||
[$options['host'], $port] = explode(':', $options['host']);
|
||||
|
||||
try {
|
||||
$link = @mysqli_connect($options['host'], $options['user'], $options['pass'], $options['db'], $port ?: $defPort);
|
||||
mysqli_close($link);
|
||||
}
|
||||
catch (Exception $e)
|
||||
if ($link = mysqli_connect($options['host'], $options['user'], $options['pass'], $options['db'], $port ?: $defPort))
|
||||
{
|
||||
$err = '['.mysqli_connect_errno().'] '.mysqli_connect_error();
|
||||
return false;
|
||||
}
|
||||
mysqli_close($link);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function errorHandler($message, $data)
|
||||
$err = '['.mysqli_connect_errno().'] '.mysqli_connect_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function errorHandler(string $message, array $data) : void
|
||||
{
|
||||
if (!error_reporting())
|
||||
return;
|
||||
|
||||
$error = "DB ERROR:<br /><br />\n\n<pre>".print_r($data, true)."</pre>";
|
||||
// continue on warning, end on error
|
||||
$isError = $data['code'] > 0;
|
||||
|
||||
echo CLI ? strip_tags($error) : $error;
|
||||
exit;
|
||||
// make number sensible again
|
||||
$data['code'] = abs($data['code']);
|
||||
|
||||
if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO)
|
||||
{
|
||||
echo "\nDB ERROR\n";
|
||||
foreach ($data as $k => $v)
|
||||
echo ' '.str_pad($k.':', 10).$v."\n";
|
||||
}
|
||||
|
||||
public static function logger($self, $query, $trace)
|
||||
trigger_error($message, $isError ? E_USER_ERROR : E_USER_WARNING);
|
||||
}
|
||||
|
||||
public static function profiler(mixed $self, string $query, mixed $trace) : void
|
||||
{
|
||||
if ($trace) // actual query
|
||||
self::$logs[] = [substr(str_replace("\n", ' ', $query), 0, 200)];
|
||||
self::$logs[] = [str_replace("\n", ' ', $query)];
|
||||
else // the statistics
|
||||
{
|
||||
end(self::$logs);
|
||||
@@ -90,7 +104,7 @@ class DB
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLogs()
|
||||
public static function getProfiles() : string
|
||||
{
|
||||
$out = '<pre><table style="font-size:12;"><tr><th></th><th>Time</th><th>Query</th></tr>';
|
||||
foreach (self::$logs as $i => [$l, $t])
|
||||
@@ -108,71 +122,64 @@ class DB
|
||||
return Util::jsEscape($out).'</table></pre>';
|
||||
}
|
||||
|
||||
public static function getDB($idx)
|
||||
public static function getDB(int $idx) : ?\DbSimple_Mysqli
|
||||
{
|
||||
return self::$interfaceCache[$idx];
|
||||
}
|
||||
|
||||
public static function isConnected($idx)
|
||||
public static function isConnected(int $idx) : bool
|
||||
{
|
||||
return isset(self::$connectionCache[$idx]);
|
||||
return isset(self::$interfaceCache[$idx]) && self::$interfaceCache[$idx]->link;
|
||||
}
|
||||
|
||||
public static function isConnectable($idx)
|
||||
public static function isConnectable(int $idx) : bool
|
||||
{
|
||||
return isset(self::$optionsCache[$idx]);
|
||||
}
|
||||
|
||||
private static function safeGetDB($idx)
|
||||
/**
|
||||
* @static
|
||||
* @return DbSimple_Mysqli
|
||||
*/
|
||||
public static function Characters(int $realmId) : ?\DbSimple_Mysqli
|
||||
{
|
||||
if (!self::isConnected($idx))
|
||||
self::connect($idx);
|
||||
if (!isset(self::$optionsCache[DB_CHARACTERS.$realmId]))
|
||||
die('Connection info not found for live database of realm #'.$realmId.'. Aborted.');
|
||||
|
||||
return self::getDB($idx);
|
||||
return self::getDB(DB_CHARACTERS.$realmId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @return DbSimple_Mysql
|
||||
* @return DbSimple_Mysqli
|
||||
*/
|
||||
public static function Characters($realm)
|
||||
public static function Auth() : ?\DbSimple_Mysqli
|
||||
{
|
||||
if (!isset(self::$optionsCache[DB_CHARACTERS.$realm]))
|
||||
die('Connection info not found for live database of realm #'.$realm.'. Aborted.');
|
||||
|
||||
return self::safeGetDB(DB_CHARACTERS.$realm);
|
||||
return self::getDB(DB_AUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @return DbSimple_Mysql
|
||||
* @return DbSimple_Mysqli
|
||||
*/
|
||||
public static function Auth()
|
||||
public static function World() : ?\DbSimple_Mysqli
|
||||
{
|
||||
return self::safeGetDB(DB_AUTH);
|
||||
return self::getDB(DB_WORLD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @return DbSimple_Mysql
|
||||
* @return DbSimple_Mysqli
|
||||
*/
|
||||
public static function World()
|
||||
public static function Aowow() : ?\DbSimple_Mysqli
|
||||
{
|
||||
return self::safeGetDB(DB_WORLD);
|
||||
return self::getDB(DB_AOWOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @return DbSimple_Mysql
|
||||
*/
|
||||
public static function Aowow()
|
||||
{
|
||||
return self::safeGetDB(DB_AOWOW);
|
||||
}
|
||||
|
||||
public static function load($idx, $config)
|
||||
public static function load(int $idx, array $config) : void
|
||||
{
|
||||
self::$optionsCache[$idx] = $config;
|
||||
self::connect($idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1695
includes/defines.php
1695
includes/defines.php
File diff suppressed because it is too large
Load Diff
@@ -1,490 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Game
|
||||
{
|
||||
public static $resistanceFields = array(
|
||||
null, 'resHoly', 'resFire', 'resNature', 'resFrost', 'resShadow', 'resArcane'
|
||||
);
|
||||
|
||||
public static $rarityColorStings = array( // zero-indexed
|
||||
'9d9d9d', 'ffffff', '1eff00', '0070dd', 'a335ee', 'ff8000', 'e5cc80', 'e6cc80'
|
||||
);
|
||||
|
||||
public static $specIconStrings = array(
|
||||
-1 => 'inv_misc_questionmark',
|
||||
0 => 'spell_nature_elementalabsorption',
|
||||
6 => ['spell_deathknight_bloodpresence', 'spell_deathknight_frostpresence', 'spell_deathknight_unholypresence' ],
|
||||
11 => ['spell_nature_starfall', 'ability_racial_bearform', 'spell_nature_healingtouch' ],
|
||||
3 => ['ability_hunter_beasttaming', 'ability_marksmanship', 'ability_hunter_swiftstrike' ],
|
||||
8 => ['spell_holy_magicalsentry', 'spell_fire_firebolt02', 'spell_frost_frostbolt02' ],
|
||||
2 => ['spell_holy_holybolt', 'spell_holy_devotionaura', 'spell_holy_auraoflight' ],
|
||||
5 => ['spell_holy_wordfortitude', 'spell_holy_holybolt', 'spell_shadow_shadowwordpain' ],
|
||||
4 => ['ability_rogue_eviscerate', 'ability_backstab', 'ability_stealth' ],
|
||||
7 => ['spell_nature_lightning', 'spell_nature_lightningshield', 'spell_nature_magicimmunity' ],
|
||||
9 => ['spell_shadow_deathcoil', 'spell_shadow_metamorphosis', 'spell_shadow_rainoffire' ],
|
||||
1 => ['ability_rogue_eviscerate', 'ability_warrior_innerrage', 'ability_warrior_defensivestance' ]
|
||||
);
|
||||
|
||||
public static $classFileStrings = array(
|
||||
null, 'warrior', 'paladin', 'hunter', 'rogue', 'priest', 'deathknight', 'shaman', 'mage', 'warlock', null, 'druid'
|
||||
);
|
||||
|
||||
private static $combatRatingToItemMod = array( // zero-indexed idx:CR; val:Mod
|
||||
null, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28,
|
||||
29, 30, null, null, null, 37, 44
|
||||
);
|
||||
|
||||
public static $lvlIndepRating = array( // rating doesn't scale with level
|
||||
ITEM_MOD_MANA, ITEM_MOD_HEALTH, ITEM_MOD_ATTACK_POWER, ITEM_MOD_MANA_REGENERATION, ITEM_MOD_SPELL_POWER,
|
||||
ITEM_MOD_HEALTH_REGEN, ITEM_MOD_SPELL_PENETRATION, ITEM_MOD_BLOCK_VALUE
|
||||
);
|
||||
|
||||
public static $questClasses = array(
|
||||
-2 => [ 0],
|
||||
0 => [ 1, 3, 4, 8, 9, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 132, 139, 154, 267, 1497, 1519, 1537, 2257, 3430, 3431, 3433, 3487, 4080, 4298],
|
||||
1 => [ 14, 15, 16, 17, 141, 148, 188, 215, 220, 331, 357, 361, 363, 400, 405, 406, 440, 490, 493, 618, 1377, 1637, 1638, 1657, 1769, 3524, 3525, 3526, 3557],
|
||||
2 => [ 206, 209, 491, 717, 718, 719, 721, 722, 796, 1176, 1196, 1337, 1417, 1581, 1583, 1584, 1941, 2017, 2057, 2100, 2366, 2367, 2437, 2557, 3535, 3562, 3688, 3713, 3714, 3715, 3716, 3717, 3789, 3790, 3791, 3792, 3842, 3847, 3848, 3849, 3905, 4100, 4131, 4196, 4228, 4264, 4265, 4272, 4277, 4415, 4416, 4494, 4522, 4723, 4809, 4813, 4820],
|
||||
3 => [ 1977, 2159, 2677, 2717, 3428, 3429, 3456, 3457, 3606, 3607, 3805, 3836, 3845, 3923, 3959, 4075, 4273, 4493, 4500, 4603, 4722, 4812, 4987],
|
||||
4 => [ -372, -263, -262, -261, -162, -161, -141, -82, -81, -61],
|
||||
5 => [ -373, -371, -324, -304, -264, -201, -182, -181, -121, -101, -24],
|
||||
6 => [ -25, 2597, 3277, 3358, 3820, 4384, 4710],
|
||||
7 => [-1010, -368, -367, -365, -344, -241, -1],
|
||||
8 => [ 3483, 3518, 3519, 3520, 3521, 3522, 3523, 3679, 3703],
|
||||
9 => [-1005, -1003, -1002, -1001, -376, -375, -374, -370, -369, -366, -364, -41, -22], // 22: seasonal
|
||||
10 => [ 65, 66, 67, 210, 394, 495, 2817, 3537, 3711, 4024, 4197, 4395, 4742]
|
||||
);
|
||||
|
||||
/* why:
|
||||
Because petSkills (and ranged weapon skills) are the only ones with more than two skillLines attached. Because Left Joining ?_spell with ?_skillLineability causes more trouble than it has uses.
|
||||
Because this is more or less the only reaonable way to fit all that information into one database field, so..
|
||||
.. the indizes of this array are bits of skillLine2OrMask in ?_spell if skillLineId1 is negative
|
||||
*/
|
||||
public static $skillLineMask = array( // idx => [familyId, skillLineId]
|
||||
-1 => array( // Pets (Hunter)
|
||||
[ 1, 208], [ 2, 209], [ 3, 203], [ 4, 210], [ 5, 211], [ 6, 212], [ 7, 213], // Wolf, Cat, Spider, Bear, Boar, Crocolisk, Carrion Bird
|
||||
[ 8, 214], [ 9, 215], [11, 217], [12, 218], [20, 236], [21, 251], [24, 653], // Crab, Gorilla, Raptor, Tallstrider, Scorpid, Turtle, Bat
|
||||
[25, 654], [26, 655], [27, 656], [30, 763], [31, 767], [32, 766], [33, 765], // Hyena, Bird of Prey, Wind Serpent, Dragonhawk, Ravager, Warp Stalker, Sporebat
|
||||
[34, 764], [35, 768], [37, 775], [38, 780], [39, 781], [41, 783], [42, 784], // Nether Ray, Serpent, Moth, Chimaera, Devilsaur, Silithid, Worm
|
||||
[43, 786], [44, 785], [45, 787], [46, 788] // Rhino, Wasp, Core Hound, Spirit Beast
|
||||
),
|
||||
-2 => array( // Pets (Warlock)
|
||||
[15, 189], [16, 204], [17, 205], [19, 207], [23, 188], [29, 761] // Felhunter, Voidwalker, Succubus, Doomguard, Imp, Felguard
|
||||
),
|
||||
-3 => array( // Ranged Weapons
|
||||
[null, 45], [null, 46], [null, 226] // Bow, Gun, Crossbow
|
||||
)
|
||||
);
|
||||
|
||||
public static $sockets = array( // jsStyle Strings
|
||||
'meta', 'red', 'yellow', 'blue'
|
||||
);
|
||||
|
||||
// 'replicates' $WH.g_statToJson
|
||||
public static $itemMods = array( // zero-indexed; "mastrtng": unused mastery; _[a-z] => taken mods..
|
||||
'dmg', 'mana', 'health', 'agi', 'str', 'int', 'spi',
|
||||
'sta', 'energy', 'rage', 'focus', 'runicpwr', 'defrtng', 'dodgertng',
|
||||
'parryrtng', 'blockrtng', 'mlehitrtng', 'rgdhitrtng', 'splhitrtng', 'mlecritstrkrtng', 'rgdcritstrkrtng',
|
||||
'splcritstrkrtng', '_mlehitrtng', '_rgdhitrtng', '_splhitrtng', '_mlecritstrkrtng', '_rgdcritstrkrtng', '_splcritstrkrtng',
|
||||
'mlehastertng', 'rgdhastertng', 'splhastertng', 'hitrtng', 'critstrkrtng', '_hitrtng', '_critstrkrtng',
|
||||
'resirtng', 'hastertng', 'exprtng', 'atkpwr', 'rgdatkpwr', 'feratkpwr', 'splheal',
|
||||
'spldmg', 'manargn', 'armorpenrtng', 'splpwr', 'healthrgn', 'splpen', 'block', // ITEM_MOD_BLOCK_VALUE
|
||||
'mastrtng', 'armor', 'firres', 'frores', 'holres', 'shares', 'natres',
|
||||
'arcres', 'firsplpwr', 'frosplpwr', 'holsplpwr', 'shasplpwr', 'natsplpwr', 'arcsplpwr'
|
||||
);
|
||||
|
||||
public static $class2SpellFamily = array(
|
||||
// null Warrior Paladin Hunter Rogue Priest DK Shaman Mage Warlock null Druid
|
||||
null, 4, 10, 9, 8, 6, 15, 11, 3, 5, null, 7
|
||||
);
|
||||
|
||||
public static $areaFloors = array(
|
||||
206 => 3, 209 => 7, 719 => 3, 721 => 4, 796 => 4, 1196 => 2, 1337 => 2, 1581 => 2, 1583 => 7, 1584 => 2,
|
||||
2017 => 2, 2057 => 4, 2100 => 2, 2557 => 6, 2677 => 4, 3428 => 3, 3457 => 17, 3790 => 2, 3791 => 2, 3959 => 8,
|
||||
3456 => 6, 3715 => 2, 3848 => 3, 3849 => 2, 4075 => 2, 4100 => 2, 4131 => 2, 4196 => 2, 4228 => 4, 4272 => 2,
|
||||
4273 => 6, 4277 => 3, 4395 => 2, 4494 => 2, 4722 => 2, 4812 => 8
|
||||
);
|
||||
|
||||
public static function itemModByRatingMask($mask)
|
||||
{
|
||||
if (($mask & 0x1C000) == 0x1C000) // special case resilience
|
||||
return ITEM_MOD_RESILIENCE_RATING;
|
||||
|
||||
if (($mask & 0x00E0) == 0x00E0) // hit rating - all subcats (mle, rgd, spl)
|
||||
return ITEM_MOD_HIT_RATING;
|
||||
|
||||
if (($mask & 0x0700) == 0x0700) // crit rating - all subcats (mle, rgd, spl)
|
||||
return ITEM_MOD_CRIT_RATING;
|
||||
|
||||
for ($j = 0; $j < count(self::$combatRatingToItemMod); $j++)
|
||||
{
|
||||
if (!self::$combatRatingToItemMod[$j])
|
||||
continue;
|
||||
|
||||
if (!($mask & (1 << $j)))
|
||||
continue;
|
||||
|
||||
return self::$combatRatingToItemMod[$j];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function sideByRaceMask($race)
|
||||
{
|
||||
// Any
|
||||
if (!$race || ($race & RACE_MASK_ALL) == RACE_MASK_ALL)
|
||||
return SIDE_BOTH;
|
||||
|
||||
// Horde
|
||||
if ($race & RACE_MASK_HORDE && !($race & RACE_MASK_ALLIANCE))
|
||||
return SIDE_HORDE;
|
||||
|
||||
// Alliance
|
||||
if ($race & RACE_MASK_ALLIANCE && !($race & RACE_MASK_HORDE))
|
||||
return SIDE_ALLIANCE;
|
||||
|
||||
return SIDE_BOTH;
|
||||
}
|
||||
|
||||
public static function getReputationLevelForPoints($pts)
|
||||
{
|
||||
if ($pts >= 41999)
|
||||
return REP_EXALTED;
|
||||
else if ($pts >= 20999)
|
||||
return REP_REVERED;
|
||||
else if ($pts >= 8999)
|
||||
return REP_HONORED;
|
||||
else if ($pts >= 2999)
|
||||
return REP_FRIENDLY;
|
||||
else if ($pts >= 0)
|
||||
return REP_NEUTRAL;
|
||||
else if ($pts >= -3000)
|
||||
return REP_UNFRIENDLY;
|
||||
else if ($pts >= -6000)
|
||||
return REP_HOSTILE;
|
||||
else
|
||||
return REP_HATED;
|
||||
}
|
||||
|
||||
public static function getTaughtSpells(&$spell)
|
||||
{
|
||||
$extraIds = [-1]; // init with -1 to prevent empty-array errors
|
||||
$lookup = [-1];
|
||||
switch (gettype($spell))
|
||||
{
|
||||
case 'object':
|
||||
if (get_class($spell) != 'SpellList')
|
||||
return [];
|
||||
|
||||
$lookup[] = $spell->id;
|
||||
foreach ($spell->canTeachSpell() as $idx)
|
||||
$extraIds[] = $spell->getField('effect'.$idx.'TriggerSpell');
|
||||
|
||||
break;
|
||||
case 'integer':
|
||||
$lookup[] = $spell;
|
||||
break;
|
||||
case 'array':
|
||||
$lookup = $spell;
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
// note: omits required spell and chance in skill_discovery_template
|
||||
$data = array_merge(
|
||||
DB::World()->selectCol('SELECT spellId FROM spell_learn_spell WHERE entry IN (?a)', $lookup),
|
||||
DB::World()->selectCol('SELECT spellId FROM skill_discovery_template WHERE reqSpell IN (?a)', $lookup),
|
||||
$extraIds
|
||||
);
|
||||
|
||||
// return list of integers, not strings
|
||||
$data = array_map('intVal', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function getPageText($ptId)
|
||||
{
|
||||
$pages = [];
|
||||
while ($ptId)
|
||||
{
|
||||
if ($row = DB::World()->selectRow('SELECT ptl.Text AS Text_loc?d, pt.* FROM page_text pt LEFT JOIN page_text_locale ptl ON pt.ID = ptl.ID AND locale = ? WHERE pt.ID = ?d', User::$localeId, User::$localeString, $ptId))
|
||||
{
|
||||
$ptId = $row['NextPageID'];
|
||||
$pages[] = Util::parseHtmlText(Util::localizedString($row, 'Text'));
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('Referenced PageTextId #'.$ptId.' is not in DB', E_USER_WARNING);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* World Pos. Checks */
|
||||
/*********************/
|
||||
|
||||
private static $alphaMapCache = [];
|
||||
|
||||
private static function alphaMapCheck(int $areaId, array &$set) : bool
|
||||
{
|
||||
$file = 'setup/generated/alphaMaps/'.$areaId.'.png';
|
||||
if (!file_exists($file)) // file does not exist (probably instanced area)
|
||||
return false;
|
||||
|
||||
// invalid and corner cases (literally)
|
||||
if (!is_array($set) || empty($set['posX']) || empty($set['posY']) || $set['posX'] >= 100 || $set['posY'] >= 100)
|
||||
{
|
||||
$set = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty(self::$alphaMapCache[$areaId]))
|
||||
self::$alphaMapCache[$areaId] = imagecreatefrompng($file);
|
||||
|
||||
// alphaMaps are 1000 x 1000, adapt points [black => valid point]
|
||||
if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10))
|
||||
$set = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function checkCoords(array $points) : array
|
||||
{
|
||||
$result = [];
|
||||
$capitals = array( // capitals take precedence over their surroundings
|
||||
1497, 1637, 1638, 3487, // Undercity, Ogrimmar, Thunder Bluff, Silvermoon City
|
||||
1519, 1537, 1657, 3557, // Stormwind City, Ironforge, Darnassus, The Exodar
|
||||
3703, 4395 // Shattrath City, Dalaran
|
||||
);
|
||||
|
||||
foreach ($points as $res)
|
||||
{
|
||||
if (self::alphaMapCheck($res['areaId'], $res))
|
||||
{
|
||||
if (!$res)
|
||||
continue;
|
||||
|
||||
// some rough measure how central the spawn is on the map (the lower the number, the better)
|
||||
// 0: perfect center; 1: touches a border
|
||||
$q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) );
|
||||
|
||||
if (empty($result) || $result[0] > $q)
|
||||
$result = [$q, $res];
|
||||
}
|
||||
else if (in_array($res['areaId'], $capitals)) // capitals (auto-discovered) and no hand-made alphaMap available
|
||||
return $res;
|
||||
else if (empty($result)) // add with lowest quality if alpha map is missing
|
||||
$result = [1.0, $res];
|
||||
}
|
||||
|
||||
// spawn does not really match on a map, but we need at least one result
|
||||
if (!$result)
|
||||
{
|
||||
usort($points, function ($a, $b) { return ($a['dist'] < $b['dist']) ? -1 : 1; });
|
||||
$result = [1.0, $points[0]];
|
||||
}
|
||||
|
||||
return $result[1];
|
||||
}
|
||||
|
||||
public static function getWorldPosForGUID(int $type, int ...$guids) : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
switch ($type)
|
||||
{
|
||||
case Type::NPC:
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::OBJECT:
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_y` AS `posX`, `position_x` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::SOUND:
|
||||
$result = DB::AoWoW()->select('SELECT `soundId` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM dbc_soundemitters WHERE `soundId` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::AREATRIGGER:
|
||||
$result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM dbc_areatrigger WHERE `id` IN (?a)', $guids);
|
||||
break;
|
||||
default:
|
||||
trigger_error('Game::getWorldPosForGUID - instanced with unsupported TYPE '.$type, E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function worldPosToZonePos(int $mapId, float $posX, float $posY, int $areaId = 0, int $floor = -1) : array
|
||||
{
|
||||
if (!$mapId < 0)
|
||||
return [];
|
||||
|
||||
$query = 'SELECT
|
||||
dm.id,
|
||||
wma.areaId,
|
||||
IFNULL(dm.floor, 0) AS floor,
|
||||
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)), 1) AS `posX`,
|
||||
100 - ROUND(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)), 1) AS `posY`,
|
||||
SQRT(POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minY) * 100 / (dm.maxY - dm.minY), (?f - wma.right) * 100 / (wma.left - wma.right)) - 50), 2) +
|
||||
POWER(abs(IF(dm.id IS NOT NULL, (?f - dm.minX) * 100 / (dm.maxX - dm.minX), (?f - wma.bottom) * 100 / (wma.top - wma.bottom)) - 50), 2)) AS `dist`
|
||||
FROM
|
||||
dbc_worldmaparea wma
|
||||
LEFT JOIN
|
||||
dbc_dungeonmap dm ON dm.mapId = IF(?d AND (wma.mapId NOT IN (0, 1, 530, 571) OR wma.areaId = 4395), wma.mapId, -1)
|
||||
WHERE
|
||||
wma.mapId = ?d AND IF(?d, wma.areaId = ?d, wma.areaId <> 0){ AND IF(dm.floor IS NULL, 1, dm.floor = ?d)}
|
||||
HAVING
|
||||
(`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9)
|
||||
ORDER BY
|
||||
`dist` ASC';
|
||||
|
||||
// dist BETWEEN 0 (center) AND 70.7 (corner)
|
||||
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 1, $mapId, $areaId, $areaId, $floor < 0 ? DBSIMPLE_SKIP : $floor);
|
||||
if (!$points) // retry: TC counts pre-instance subareas as instance-maps .. which have no map file
|
||||
$points = DB::Aowow()->select($query, $posX, $posX, $posY, $posY, $posX, $posX, $posY, $posY, 0, $mapId, 0, 0, DBSIMPLE_SKIP);
|
||||
|
||||
if (!is_array($points))
|
||||
{
|
||||
trigger_error('Game::worldPosToZonePos - dbc query failed', E_USER_ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
public static function getQuotesForCreature(int $creatureId, bool $asHTML = false, string $talkSource = '') : array
|
||||
{
|
||||
$nQuotes = 0;
|
||||
$quotes = [];
|
||||
$soundIds = [];
|
||||
|
||||
$quoteSrc = DB::World()->select('
|
||||
SELECT
|
||||
ct.GroupID AS ARRAY_KEY, ct.ID as ARRAY_KEY2,
|
||||
ct.`Type` AS `talkType`,
|
||||
ct.TextRange AS `range`,
|
||||
IFNULL(bct.`LanguageID`, ct.`Language`) AS lang,
|
||||
IFNULL(NULLIF(bct.Text, ""), IFNULL(NULLIF(bct.Text1, ""), IFNULL(ct.`Text`, ""))) AS text_loc0,
|
||||
{IFNULL(NULLIF(bctl.Text, ""), IFNULL(NULLIF(bctl.Text1, ""), IFNULL(ctl.Text, ""))) AS text_loc?d,}
|
||||
IF(bct.SoundEntriesID > 0, bct.SoundEntriesID, ct.Sound) AS soundId
|
||||
FROM
|
||||
creature_text ct
|
||||
{LEFT JOIN
|
||||
creature_text_locale ctl ON ct.CreatureID = ctl.CreatureID AND ct.GroupID = ctl.GroupID AND ct.ID = ctl.ID AND ctl.Locale = ?}
|
||||
LEFT JOIN
|
||||
broadcast_text bct ON ct.BroadcastTextId = bct.ID
|
||||
{LEFT JOIN
|
||||
broadcast_text_locale bctl ON ct.BroadcastTextId = bctl.ID AND bctl.locale = ?}
|
||||
WHERE
|
||||
ct.CreatureID = ?d',
|
||||
User::$localeId ?: DBSIMPLE_SKIP,
|
||||
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
|
||||
User::$localeId ? Util::$localeStrings[User::$localeId] : DBSIMPLE_SKIP,
|
||||
$creatureId
|
||||
);
|
||||
|
||||
foreach ($quoteSrc as $grp => $text)
|
||||
{
|
||||
$group = [];
|
||||
foreach ($text as $t)
|
||||
{
|
||||
if ($t['soundId'])
|
||||
$soundIds[] = $t['soundId'];
|
||||
|
||||
$msg = Util::localizedString($t, 'text');
|
||||
if (!$msg)
|
||||
continue;
|
||||
|
||||
// fixup .. either set %s for emotes or dont >.<
|
||||
if (in_array($t['talkType'], [2, 16]) && strpos($msg, '%s') === false)
|
||||
$msg = '%s '.$msg;
|
||||
|
||||
// fixup: bad case-insensivity
|
||||
$msg = Util::parseHtmlText(str_replace('%S', '%s', htmlentities($msg)), !$asHTML);
|
||||
|
||||
if ($talkSource)
|
||||
$msg = sprintf($msg, $talkSource);
|
||||
|
||||
// make type css compatible
|
||||
switch ($t['talkType'])
|
||||
{
|
||||
case 1: // yell:
|
||||
case 14: $t['talkType'] = 1; break; // - dark red
|
||||
case 2: // emote:
|
||||
case 16: // "
|
||||
case 3: // boss emote:
|
||||
case 41: $t['talkType'] = 4; break; // - orange
|
||||
case 4: // whisper:
|
||||
case 15: // "
|
||||
case 5: // boss whisper:
|
||||
case 42: $t['talkType'] = 3; break; // - pink-ish
|
||||
default: $t['talkType'] = 2; // [type: 0, 12] say: yellow-ish
|
||||
|
||||
}
|
||||
|
||||
// prefix
|
||||
$pre = '';
|
||||
if ($t['talkType'] != 4)
|
||||
$pre = ($talkSource ?: '%s').' '.Lang::npc('textTypes', $t['talkType']).Lang::main('colon').($t['lang'] ? '['.Lang::game('languages', $t['lang']).'] ' : null);
|
||||
|
||||
if ($asHTML)
|
||||
$msg = '<div><span class="s'.$t['talkType'].'">%s'.($t['range'] ? sprintf(Util::$dfnString, Lang::npc('textRanges', $t['range']), $msg) : $msg).'</span></div>';
|
||||
else
|
||||
$msg = '[div][span class=s'.$t['talkType'].']%s'.html_entity_decode($msg).'[/span][/div]';
|
||||
|
||||
$line = array(
|
||||
'range' => $t['range'],
|
||||
'text' => $msg,
|
||||
'prefix' => $pre
|
||||
);
|
||||
|
||||
|
||||
$nQuotes++;
|
||||
$group[] = $line;
|
||||
}
|
||||
|
||||
if ($group)
|
||||
$quotes[$grp] = $group;
|
||||
}
|
||||
|
||||
return [$quotes, $nQuotes, $soundIds];
|
||||
}
|
||||
|
||||
public static function getBreakpointsForSkill(int $skillId, int $reqLevel) : array
|
||||
{
|
||||
switch ($skillId)
|
||||
{
|
||||
case SKILL_HERBALISM:
|
||||
case SKILL_LOCKPICKING:
|
||||
case SKILL_JEWELCRAFTING:
|
||||
case SKILL_INSCRIPTION:
|
||||
case SKILL_SKINNING:
|
||||
case SKILL_MINING:
|
||||
$points = [$reqLevel]; // red/orange
|
||||
|
||||
if ($reqLevel + 25 <= MAX_SKILL) // orange/yellow
|
||||
$points[] = $reqLevel + 25;
|
||||
|
||||
if ($reqLevel + 50 <= MAX_SKILL) // yellow/green
|
||||
$points[] = $reqLevel + 50;
|
||||
|
||||
if ($reqLevel + 100 <= MAX_SKILL) // green/grey
|
||||
$points[] = $reqLevel + 100;
|
||||
|
||||
return $points;
|
||||
default:
|
||||
return [$reqLevel];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
79
includes/game/chrclass.class.php
Normal file
79
includes/game/chrclass.class.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
enum ChrClass : int
|
||||
{
|
||||
case WARRIOR = 1;
|
||||
case PALADIN = 2;
|
||||
case HUNTER = 3;
|
||||
case ROGUE = 4;
|
||||
case PRIEST = 5;
|
||||
case DEATHKNIGHT = 6;
|
||||
case SHAMAN = 7;
|
||||
case MAGE = 8;
|
||||
case WARLOCK = 9;
|
||||
case DRUID = 11;
|
||||
|
||||
public const MASK_ALL = 0x5FF;
|
||||
|
||||
public function matches(int $classMask) : bool
|
||||
{
|
||||
return !$classMask || $this->value & $classMask;
|
||||
}
|
||||
|
||||
public function toMask() : int
|
||||
{
|
||||
return 1 << ($this->value - 1);
|
||||
}
|
||||
|
||||
public static function fromMask(int $classMask = self::MASK_ALL) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::cases() as $cl)
|
||||
if ($cl->toMask() & $classMask)
|
||||
$x[] = $cl->value;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public function json() : string
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::WARRIOR => 'warrior',
|
||||
self::PALADIN => 'paladin',
|
||||
self::HUNTER => 'hunter',
|
||||
self::ROGUE => 'rogue',
|
||||
self::PRIEST => 'priest',
|
||||
self::DEATHKNIGHT => 'deathknight',
|
||||
self::SHAMAN => 'shaman',
|
||||
self::MAGE => 'mage',
|
||||
self::WARLOCK => 'warlock',
|
||||
self::DRUID => 'druid'
|
||||
};
|
||||
}
|
||||
|
||||
public function spellFamily() : int
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::WARRIOR => SPELLFAMILY_WARRIOR,
|
||||
self::PALADIN => SPELLFAMILY_PALADIN,
|
||||
self::HUNTER => SPELLFAMILY_HUNTER,
|
||||
self::ROGUE => SPELLFAMILY_ROGUE,
|
||||
self::PRIEST => SPELLFAMILY_PRIEST,
|
||||
self::DEATHKNIGHT => SPELLFAMILY_DEATHKNIGHT,
|
||||
self::SHAMAN => SPELLFAMILY_SHAMAN,
|
||||
self::MAGE => SPELLFAMILY_MAGE,
|
||||
self::WARLOCK => SPELLFAMILY_WARLOCK,
|
||||
self::DRUID => SPELLFAMILY_DRUID
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
132
includes/game/chrrace.class.php
Normal file
132
includes/game/chrrace.class.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
enum ChrRace : int
|
||||
{
|
||||
case HUMAN = 1;
|
||||
case ORC = 2;
|
||||
case DWARF = 3;
|
||||
case NIGHTELF = 4;
|
||||
case UNDEAD = 5;
|
||||
case TAUREN = 6;
|
||||
case GNOME = 7;
|
||||
case TROLL = 8;
|
||||
case BLOODELF = 10;
|
||||
case DRAENEI = 11;
|
||||
|
||||
public const MASK_ALLIANCE = 0x44D;
|
||||
public const MASK_HORDE = 0x2B2;
|
||||
public const MASK_ALL = 0x6FF;
|
||||
|
||||
public function matches(int $raceMask) : bool
|
||||
{
|
||||
return !$raceMask || $this->value & $raceMask;
|
||||
}
|
||||
|
||||
public function toMask() : int
|
||||
{
|
||||
return 1 << ($this->value - 1);
|
||||
}
|
||||
|
||||
public function isAlliance() : bool
|
||||
{
|
||||
return $this->toMask() & self::MASK_ALLIANCE;
|
||||
}
|
||||
|
||||
public function isHorde() : bool
|
||||
{
|
||||
return $this->toMask() & self::MASK_HORDE;
|
||||
}
|
||||
|
||||
public function getSide() : int
|
||||
{
|
||||
if ($this->isHorde() && $this->isAlliance())
|
||||
return SIDE_BOTH;
|
||||
else if ($this->isHorde())
|
||||
return SIDE_HORDE;
|
||||
else if ($this->isAlliance())
|
||||
return SIDE_ALLIANCE;
|
||||
else
|
||||
return SIDE_NONE;
|
||||
}
|
||||
|
||||
public function getTeam() : int
|
||||
{
|
||||
if ($this->isHorde() && $this->isAlliance())
|
||||
return TEAM_NEUTRAL;
|
||||
else if ($this->isHorde())
|
||||
return TEAM_HORDE;
|
||||
else if ($this->isAlliance())
|
||||
return TEAM_ALLIANCE;
|
||||
else
|
||||
return TEAM_NEUTRAL;
|
||||
}
|
||||
|
||||
public function json() : string
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::HUMAN => 'human',
|
||||
self::ORC => 'orc',
|
||||
self::DWARF => 'dwarf',
|
||||
self::NIGHTELF => 'nightelf',
|
||||
self::UNDEAD => 'undead',
|
||||
self::TAUREN => 'tauren',
|
||||
self::GNOME => 'gnome',
|
||||
self::TROLL => 'troll',
|
||||
self::BLOODELF => 'bloodelf',
|
||||
self::DRAENEI => 'draenei'
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromMask(int $raceMask = self::MASK_ALL) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::cases() as $cl)
|
||||
if ($cl->toMask() & $raceMask)
|
||||
$x[] = $cl->value;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function sideFromMask(int $raceMask) : int
|
||||
{
|
||||
// Any
|
||||
if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL)
|
||||
return SIDE_BOTH;
|
||||
|
||||
// Horde
|
||||
if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE))
|
||||
return SIDE_HORDE;
|
||||
|
||||
// Alliance
|
||||
if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE))
|
||||
return SIDE_ALLIANCE;
|
||||
|
||||
return SIDE_BOTH;
|
||||
}
|
||||
|
||||
public static function teamFromMask(int $raceMask) : int
|
||||
{
|
||||
// Any
|
||||
if (!$raceMask || ($raceMask & self::MASK_ALL) == self::MASK_ALL)
|
||||
return TEAM_NEUTRAL;
|
||||
|
||||
// Horde
|
||||
if ($raceMask & self::MASK_HORDE && !($raceMask & self::MASK_ALLIANCE))
|
||||
return TEAM_HORDE;
|
||||
|
||||
// Alliance
|
||||
if ($raceMask & self::MASK_ALLIANCE && !($raceMask & self::MASK_HORDE))
|
||||
return TEAM_ALLIANCE;
|
||||
|
||||
return TEAM_NEUTRAL;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
723
includes/game/chrstatistics.php
Normal file
723
includes/game/chrstatistics.php
Normal file
@@ -0,0 +1,723 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
abstract class Stat // based on g_statToJson
|
||||
{
|
||||
public const HEALTH = 1;
|
||||
public const MANA = 2; // note: mana is on idx 0 from 5.0 onwards; idx 2 is empty
|
||||
public const AGILITY = 3;
|
||||
public const STRENGTH = 4;
|
||||
public const INTELLECT = 5;
|
||||
public const SPIRIT = 6;
|
||||
public const STAMINA = 7;
|
||||
public const ENERGY = 8;
|
||||
public const RAGE = 9;
|
||||
public const FOCUS = 10;
|
||||
public const RUNIC_POWER = 11;
|
||||
public const DEFENSE_RTG = 12;
|
||||
public const DODGE_RTG = 13;
|
||||
public const PARRY_RTG = 14;
|
||||
public const BLOCK_RTG = 15;
|
||||
public const MELEE_HIT_RTG = 16;
|
||||
public const RANGED_HIT_RTG = 17;
|
||||
public const SPELL_HIT_RTG = 18;
|
||||
public const MELEE_CRIT_RTG = 19;
|
||||
public const RANGED_CRIT_RTG = 20;
|
||||
public const SPELL_CRIT_RTG = 21;
|
||||
public const MELEE_HIT_TAKEN_RTG = 22;
|
||||
public const RANGED_HIT_TAKEN_RTG = 23;
|
||||
public const SPELL_HIT_TAKEN_RTG = 24;
|
||||
public const MELEE_CRIT_TAKEN_RTG = 25;
|
||||
public const RANGED_CRIT_TAKEN_RTG = 26;
|
||||
public const SPELL_CRIT_TAKEN_RTG = 27;
|
||||
public const MELEE_HASTE_RTG = 28;
|
||||
public const RANGED_HASTE_RTG = 29;
|
||||
public const SPELL_HASTE_RTG = 30;
|
||||
public const HIT_RTG = 31;
|
||||
public const CRIT_RTG = 32;
|
||||
public const HIT_TAKEN_RTG = 33;
|
||||
public const CRIT_TAKEN_RTG = 34;
|
||||
public const RESILIENCE_RTG = 35;
|
||||
public const HASTE_RTG = 36;
|
||||
public const EXPERTISE_RTG = 37;
|
||||
public const ATTACK_POWER = 38;
|
||||
public const RANGED_ATTACK_POWER = 39;
|
||||
public const FERAL_ATTACK_POWER = 40; // unused in wow-3.3.5
|
||||
public const HEALING_SPELL_POWER = 41; // deprecated
|
||||
public const DAMAGE_SPELL_POWER = 42; // deprecated
|
||||
public const MANA_REGENERATION = 43;
|
||||
public const ARMOR_PENETRATION_RTG = 44;
|
||||
public const SPELL_POWER = 45;
|
||||
public const HEALTH_REGENERATION = 46; // no differentiation between IC (item mods / spells) and OOC (from spirit) for Profiler
|
||||
public const SPELL_PENETRATION = 47;
|
||||
public const BLOCK = 48;
|
||||
// public const MASTERY_RTG = 49; // not in wow-3.3.5
|
||||
public const ARMOR = 50;
|
||||
public const FIRE_RESISTANCE = 51;
|
||||
public const FROST_RESISTANCE = 52;
|
||||
public const HOLY_RESISTANCE = 53;
|
||||
public const SHADOW_RESISTANCE = 54;
|
||||
public const NATURE_RESISTANCE = 55;
|
||||
public const ARCANE_RESISTANCE = 56;
|
||||
public const FIRE_SPELL_POWER = 57;
|
||||
public const FROST_SPELL_POWER = 58;
|
||||
public const HOLY_SPELL_POWER = 59;
|
||||
public const SHADOW_SPELL_POWER = 60;
|
||||
public const NATURE_SPELL_POWER = 61;
|
||||
public const ARCANE_SPELL_POWER = 62;
|
||||
// v for stats lookups v
|
||||
public const WEAPON_DAMAGE = 200; // +weapon dmg from enchantments
|
||||
public const WEAPON_DAMAGE_TYPE = 201;
|
||||
public const WEAPON_DAMAGE_MIN = 202;
|
||||
public const WEAPON_DAMAGE_MAX = 203;
|
||||
public const WEAPON_SPEED = 204;
|
||||
public const WEAPON_DPS = 205; // also +weapon dps from enchantments (rockbiter)
|
||||
public const MELEE_DAMAGE_MIN = 206;
|
||||
public const MELEE_DAMAGE_MAX = 207;
|
||||
public const MELEE_SPEED = 208;
|
||||
public const MELEE_DPS = 209;
|
||||
public const RANGED_DAMAGE_MIN = 210;
|
||||
public const RANGED_DAMAGE_MAX = 211;
|
||||
public const RANGED_SPEED = 212;
|
||||
public const RANGED_DPS = 213;
|
||||
public const EXTRA_SOCKETS = 214;
|
||||
public const ARMOR_BONUS = 215;
|
||||
public const MELEE_ATTACK_POWER = 216;
|
||||
// v only seen in profiler v
|
||||
public const EXPERTISE = 500;
|
||||
public const ARMOR_PENETRATION_PCT = 501;
|
||||
public const MELEE_HIT_PCT = 502;
|
||||
public const MELEE_CRIT_PCT = 503;
|
||||
public const MELEE_HASTE_PCT = 504;
|
||||
public const RANGED_HIT_PCT = 505;
|
||||
public const RANGED_CRIT_PCT = 506;
|
||||
public const RANGED_HASTE_PCT = 507;
|
||||
public const SPELL_HIT_PCT = 508;
|
||||
public const SPELL_CRIT_PCT = 509;
|
||||
public const SPELL_HASTE_PCT = 510;
|
||||
public const MANA_REGENERATION_SPI = 511; // mp5 from spirit, excluding other sources
|
||||
public const MANA_REGENERATION_OC = 512; // mp5 out of combat, excluding other sources
|
||||
public const MANA_REGENERATION_IC = 513; // mp5 in combat, excluding other sources
|
||||
public const ARMOR_TOTAL = 514; // ARMOR + ARMOR_BONUS meta category .. can be skipped here like pet* stats?
|
||||
public const DEFENSE = 515;
|
||||
public const DODGE_PCT = 516;
|
||||
public const PARRY_PCT = 517;
|
||||
public const BLOCK_PCT = 518;
|
||||
public const RESILIENCE_PCT = 519;
|
||||
|
||||
public const FLAG_NONE = 0x00;
|
||||
public const FLAG_ITEM = 0x01; // found on items
|
||||
public const FLAG_SERVERSIDE = 0x02; // not included in g_statToJson
|
||||
public const FLAG_PROFILER = 0x04; // stat used in profiler only
|
||||
public const FLAG_LVL_SCALING = 0x08; // rating effectivenes scales with level
|
||||
public const FLAG_FLOAT_VALUE = 0x10; // not an int
|
||||
|
||||
public const IDX_JSON_STR = 0;
|
||||
public const IDX_ITEM_MOD = 1; // granted by items
|
||||
public const IDX_COMBAT_RATING = 2; // granted by spells + enchantments
|
||||
public const IDX_FILTER_CR_ID = 3; // also references listview cols
|
||||
public const IDX_FLAGS = 4;
|
||||
|
||||
private static /* array */ $data = array(
|
||||
self::HEALTH => ['health', ITEM_MOD_HEALTH, null, 115, self::FLAG_ITEM],
|
||||
self::MANA => ['mana', ITEM_MOD_MANA, null, 116, self::FLAG_ITEM],
|
||||
self::AGILITY => ['agi', ITEM_MOD_AGILITY, null, 21, self::FLAG_ITEM],
|
||||
self::STRENGTH => ['str', ITEM_MOD_STRENGTH, null, 20, self::FLAG_ITEM],
|
||||
self::INTELLECT => ['int', ITEM_MOD_INTELLECT, null, 23, self::FLAG_ITEM],
|
||||
self::SPIRIT => ['spi', ITEM_MOD_SPIRIT, null, 24, self::FLAG_ITEM],
|
||||
self::STAMINA => ['sta', ITEM_MOD_STAMINA, null, 22, self::FLAG_ITEM],
|
||||
self::ENERGY => ['energy', null, null, null, self::FLAG_ITEM],
|
||||
self::RAGE => ['rage', null, null, null, self::FLAG_ITEM],
|
||||
self::FOCUS => ['focus', null, null, null, self::FLAG_ITEM],
|
||||
self::RUNIC_POWER => ['runic', null, null, null, self::FLAG_ITEM | self::FLAG_SERVERSIDE],
|
||||
self::DEFENSE_RTG => ['defrtng', ITEM_MOD_DEFENSE_SKILL_RATING, CR_DEFENSE_SKILL, 42, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::DODGE_RTG => ['dodgertng', ITEM_MOD_DODGE_RATING, CR_DODGE, 45, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::PARRY_RTG => ['parryrtng', ITEM_MOD_PARRY_RATING, CR_PARRY, 46, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::BLOCK_RTG => ['blockrtng', ITEM_MOD_BLOCK_RATING, CR_BLOCK, 44, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::MELEE_HIT_RTG => ['mlehitrtng', ITEM_MOD_HIT_MELEE_RATING, CR_HIT_MELEE, 95, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RANGED_HIT_RTG => ['rgdhitrtng', ITEM_MOD_HIT_RANGED_RATING, CR_HIT_RANGED, 39, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_HIT_RTG => ['splhitrtng', ITEM_MOD_HIT_SPELL_RATING, CR_HIT_SPELL, 48, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::MELEE_CRIT_RTG => ['mlecritstrkrtng', ITEM_MOD_CRIT_MELEE_RATING, CR_CRIT_MELEE, 84, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RANGED_CRIT_RTG => ['rgdcritstrkrtng', ITEM_MOD_CRIT_RANGED_RATING, CR_CRIT_RANGED, 40, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_CRIT_RTG => ['splcritstrkrtng', ITEM_MOD_CRIT_SPELL_RATING, CR_CRIT_SPELL, 49, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::MELEE_HIT_TAKEN_RTG => ['_mlehitrtng', ITEM_MOD_HIT_TAKEN_MELEE_RATING, CR_HIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RANGED_HIT_TAKEN_RTG => ['_rgdhitrtng', ITEM_MOD_HIT_TAKEN_RANGED_RATING, CR_HIT_TAKEN_RANGED, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_HIT_TAKEN_RTG => ['_splhitrtng', ITEM_MOD_HIT_TAKEN_SPELL_RATING, CR_HIT_TAKEN_SPELL, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::MELEE_CRIT_TAKEN_RTG => ['_mlecritstrkrtng', ITEM_MOD_CRIT_TAKEN_MELEE_RATING, CR_CRIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RANGED_CRIT_TAKEN_RTG => ['_rgdcritstrkrtng', ITEM_MOD_CRIT_TAKEN_RANGED_RATING, CR_CRIT_TAKEN_RANGED, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_CRIT_TAKEN_RTG => ['_splcritstrkrtng', ITEM_MOD_CRIT_TAKEN_SPELL_RATING, CR_CRIT_TAKEN_SPELL, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::MELEE_HASTE_RTG => ['mlehastertng', ITEM_MOD_HASTE_MELEE_RATING, CR_HASTE_MELEE, 78, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RANGED_HASTE_RTG => ['rgdhastertng', ITEM_MOD_HASTE_RANGED_RATING, CR_HASTE_RANGED, 101, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_HASTE_RTG => ['splhastertng', ITEM_MOD_HASTE_SPELL_RATING, CR_HASTE_SPELL, 102, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::HIT_RTG => ['hitrtng', ITEM_MOD_HIT_RATING, -CR_HIT_MELEE, 119, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::CRIT_RTG => ['critstrkrtng', ITEM_MOD_CRIT_RATING, -CR_CRIT_MELEE, 96, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::HIT_TAKEN_RTG => ['_hitrtng', ITEM_MOD_HIT_TAKEN_RATING, -CR_HIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::CRIT_TAKEN_RTG => ['_critstrkrtng', ITEM_MOD_CRIT_TAKEN_RATING, -CR_CRIT_TAKEN_MELEE, null, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::RESILIENCE_RTG => ['resirtng', ITEM_MOD_RESILIENCE_RATING, -CR_CRIT_TAKEN_MELEE, 79, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::HASTE_RTG => ['hastertng', ITEM_MOD_HASTE_RATING, -CR_HASTE_MELEE, 103, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::EXPERTISE_RTG => ['exprtng', ITEM_MOD_EXPERTISE_RATING, CR_EXPERTISE, 117, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::ATTACK_POWER => ['atkpwr', ITEM_MOD_ATTACK_POWER, null, 77, self::FLAG_ITEM],
|
||||
self::RANGED_ATTACK_POWER => ['rgdatkpwr', ITEM_MOD_RANGED_ATTACK_POWER, null, 38, self::FLAG_ITEM],
|
||||
self::FERAL_ATTACK_POWER => ['feratkpwr', ITEM_MOD_FERAL_ATTACK_POWER, null, 97, self::FLAG_ITEM],
|
||||
self::HEALING_SPELL_POWER => ['splheal', ITEM_MOD_SPELL_HEALING_DONE, null, 50, self::FLAG_ITEM],
|
||||
self::DAMAGE_SPELL_POWER => ['spldmg', ITEM_MOD_SPELL_DAMAGE_DONE, null, 51, self::FLAG_ITEM],
|
||||
self::MANA_REGENERATION => ['manargn', ITEM_MOD_MANA_REGENERATION, null, 61, self::FLAG_ITEM],
|
||||
self::ARMOR_PENETRATION_RTG => ['armorpenrtng', ITEM_MOD_ARMOR_PENETRATION_RATING, CR_ARMOR_PENETRATION, 114, self::FLAG_ITEM | self::FLAG_LVL_SCALING],
|
||||
self::SPELL_POWER => ['splpwr', ITEM_MOD_SPELL_POWER, null, 123, self::FLAG_ITEM],
|
||||
self::HEALTH_REGENERATION => ['healthrgn', ITEM_MOD_HEALTH_REGEN, null, 60, self::FLAG_ITEM],
|
||||
self::SPELL_PENETRATION => ['splpen', ITEM_MOD_SPELL_PENETRATION, null, 94, self::FLAG_ITEM],
|
||||
self::BLOCK => ['block', ITEM_MOD_BLOCK_VALUE, null, 43, self::FLAG_ITEM],
|
||||
// self::MASTERY_RTG => ['mastrtng', null, CR_MASTERY, null, self::FLAG_NONE],
|
||||
self::ARMOR => ['armor', null, null, 41, self::FLAG_ITEM],
|
||||
self::FIRE_RESISTANCE => ['firres', null, null, 26, self::FLAG_ITEM],
|
||||
self::FROST_RESISTANCE => ['frores', null, null, 28, self::FLAG_ITEM],
|
||||
self::HOLY_RESISTANCE => ['holres', null, null, 30, self::FLAG_ITEM],
|
||||
self::SHADOW_RESISTANCE => ['shares', null, null, 29, self::FLAG_ITEM],
|
||||
self::NATURE_RESISTANCE => ['natres', null, null, 27, self::FLAG_ITEM],
|
||||
self::ARCANE_RESISTANCE => ['arcres', null, null, 25, self::FLAG_ITEM],
|
||||
self::FIRE_SPELL_POWER => ['firsplpwr', null, null, 53, self::FLAG_ITEM],
|
||||
self::FROST_SPELL_POWER => ['frosplpwr', null, null, 54, self::FLAG_ITEM],
|
||||
self::HOLY_SPELL_POWER => ['holsplpwr', null, null, 55, self::FLAG_ITEM],
|
||||
self::SHADOW_SPELL_POWER => ['shasplpwr', null, null, 57, self::FLAG_ITEM],
|
||||
self::NATURE_SPELL_POWER => ['natsplpwr', null, null, 56, self::FLAG_ITEM],
|
||||
self::ARCANE_SPELL_POWER => ['arcsplpwr', null, null, 52, self::FLAG_ITEM],
|
||||
// v not part of g_statToJson v
|
||||
self::WEAPON_DAMAGE => ['dmg', null, null, null, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
|
||||
self::WEAPON_DAMAGE_TYPE => ['damagetype', null, null, 35, self::FLAG_SERVERSIDE],
|
||||
self::WEAPON_DAMAGE_MIN => ['dmgmin1', null, null, 33, self::FLAG_SERVERSIDE],
|
||||
self::WEAPON_DAMAGE_MAX => ['dmgmax1', null, null, 34, self::FLAG_SERVERSIDE],
|
||||
self::WEAPON_SPEED => ['speed', null, null, 36, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
|
||||
self::WEAPON_DPS => ['dps', null, null, 32, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
|
||||
self::MELEE_DAMAGE_MIN => ['mledmgmin', null, null, 135, self::FLAG_SERVERSIDE],
|
||||
self::MELEE_DAMAGE_MAX => ['mledmgmax', null, null, 136, self::FLAG_SERVERSIDE],
|
||||
self::MELEE_SPEED => ['mlespeed', null, null, 137, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
|
||||
self::MELEE_DPS => ['mledps', null, null, 134, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE | self::FLAG_PROFILER],
|
||||
self::RANGED_DAMAGE_MIN => ['rgddmgmin', null, null, 139, self::FLAG_SERVERSIDE],
|
||||
self::RANGED_DAMAGE_MAX => ['rgddmgmax', null, null, 140, self::FLAG_SERVERSIDE],
|
||||
self::RANGED_SPEED => ['rgdspeed', null, null, 141, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE],
|
||||
self::RANGED_DPS => ['rgddps', null, null, 138, self::FLAG_SERVERSIDE | self::FLAG_FLOAT_VALUE | self::FLAG_PROFILER],
|
||||
self::EXTRA_SOCKETS => ['nsockets', null, null, 100, self::FLAG_SERVERSIDE],
|
||||
self::ARMOR_BONUS => ['armorbonus', null, null, 109, self::FLAG_SERVERSIDE],
|
||||
self::MELEE_ATTACK_POWER => ['mleatkpwr', null, null, 37, self::FLAG_SERVERSIDE | self::FLAG_PROFILER],
|
||||
// v Profiler only v
|
||||
self::EXPERTISE => ['exp', null, null, null, self::FLAG_PROFILER],
|
||||
self::ARMOR_PENETRATION_PCT => ['armorpenpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::MELEE_HIT_PCT => ['mlehitpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::MELEE_CRIT_PCT => ['mlecritstrkpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::MELEE_HASTE_PCT => ['mlehastepct', null, null, null, self::FLAG_PROFILER],
|
||||
self::RANGED_HIT_PCT => ['rgdhitpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::RANGED_CRIT_PCT => ['rgdcritstrkpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::RANGED_HASTE_PCT => ['rgdhastepct', null, null, null, self::FLAG_PROFILER],
|
||||
self::SPELL_HIT_PCT => ['splhitpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::SPELL_CRIT_PCT => ['splcritstrkpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::SPELL_HASTE_PCT => ['splhastepct', null, null, null, self::FLAG_PROFILER],
|
||||
self::MANA_REGENERATION_SPI => ['spimanargn', null, null, null, self::FLAG_PROFILER],
|
||||
self::MANA_REGENERATION_OC => ['oocmanargn', null, null, null, self::FLAG_PROFILER],
|
||||
self::MANA_REGENERATION_IC => ['icmanargn', null, null, null, self::FLAG_PROFILER],
|
||||
self::ARMOR_TOTAL => ['fullarmor', null, null, null, self::FLAG_PROFILER],
|
||||
self::DEFENSE => ['def', null, null, null, self::FLAG_PROFILER],
|
||||
self::DODGE_PCT => ['dodgepct', null, null, null, self::FLAG_PROFILER],
|
||||
self::PARRY_PCT => ['parrypct', null, null, null, self::FLAG_PROFILER],
|
||||
self::BLOCK_PCT => ['blockpct', null, null, null, self::FLAG_PROFILER],
|
||||
self::RESILIENCE_PCT => ['resipct', null, null, null, self::FLAG_PROFILER]
|
||||
);
|
||||
|
||||
/* Combat Rating needed for 1% effect at level 60 (Note: Shaman, Druid, Paladin and Death Knight have a /1.3 modifier on HASTE not set here)
|
||||
* Data taken from gtcombatratings.dbc for level 60 [idx % 100 = 59]
|
||||
* Corrections from gtoctclasscombatratingscalar.dbc with Warrior as base [idx = ratingId + 1]
|
||||
* Maybe create this data during setup, but then again it will never change for 3.3.5a
|
||||
*/
|
||||
private static $crPerPctPoint = array(
|
||||
CR_WEAPON_SKILL => 2.50, CR_DEFENSE_SKILL => 1.50, CR_DODGE => 13.80, CR_PARRY => 13.80, CR_BLOCK => 5.00,
|
||||
CR_HIT_MELEE => 10.00, CR_HIT_RANGED => 10.00, CR_HIT_SPELL => 8.00, CR_CRIT_MELEE => 14.00, CR_CRIT_RANGED => 14.00,
|
||||
CR_CRIT_SPELL => 14.00, CR_HIT_TAKEN_MELEE => 10.00, CR_HIT_TAKEN_RANGED => 10.00, CR_HIT_TAKEN_SPELL => 8.00, CR_CRIT_TAKEN_MELEE => 28.75,
|
||||
CR_CRIT_TAKEN_RANGED => 28.75, CR_CRIT_TAKEN_SPELL => 28.75, CR_HASTE_MELEE => 10.00, CR_HASTE_RANGED => 10.00, CR_HASTE_SPELL => 10.00,
|
||||
CR_WEAPON_SKILL_MAINHAND => 2.50, CR_WEAPON_SKILL_OFFHAND => 2.50, CR_WEAPON_SKILL_RANGED => 2.50, CR_EXPERTISE => 2.50, CR_ARMOR_PENETRATION => 4.69512 / 1.1,
|
||||
);
|
||||
|
||||
public static function isLevelIndependent(int $stat) : bool
|
||||
{
|
||||
if (!isset(self::$data[$stat]))
|
||||
return false;
|
||||
|
||||
return !(self::$data[$stat][self::IDX_FLAGS] & self::FLAG_LVL_SCALING);
|
||||
}
|
||||
|
||||
public static function getRatingPctFactor(int $stat) : float
|
||||
{
|
||||
// Note: this makes the weapon skill related combat ratings inaccessible. Is this relevant..?
|
||||
if (!isset(self::$data[$stat]) || self::$data[$stat][self::IDX_COMBAT_RATING] === null)
|
||||
return 0.0;
|
||||
|
||||
// note: originally any CRIT_TAKEN_RTG stat was set to 0 in favor of RESILIENCE_RTG
|
||||
// we keep the dbc value and just link RESILIENCE_RTG to CRIT_TAKEN_RTG
|
||||
// note2: the js expects some stats to be directly mapped to a combat rating that doesn't exist
|
||||
// picked the next best one in this case and denoted it with a negative value in the $data dump
|
||||
return self::$crPerPctPoint[abs(self::$data[$stat][self::IDX_COMBAT_RATING])];
|
||||
}
|
||||
|
||||
public static function getJsonString(int $stat) : string
|
||||
{
|
||||
if (!isset(self::$data[$stat]))
|
||||
return '';
|
||||
|
||||
return self::$data[$stat][self::IDX_JSON_STR];
|
||||
}
|
||||
|
||||
public static function getFilterCriteriumId(int $stat) : ?int
|
||||
{
|
||||
if (!isset(self::$data[$stat]))
|
||||
return null;
|
||||
|
||||
return self::$data[$stat][self::IDX_FILTER_CR_ID];
|
||||
}
|
||||
|
||||
public static function getFlags(int $stat) : int
|
||||
{
|
||||
if (!isset(self::$data[$stat]))
|
||||
return 0;
|
||||
|
||||
return self::$data[$stat][self::IDX_FLAGS];
|
||||
}
|
||||
|
||||
public static function getJsonStringsFor(int $flags = Stat::FLAG_NONE) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [$s, , , , $f])
|
||||
if ($s && (!$flags || $flags & $f))
|
||||
$x[$k] = $s;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getCombatRatingsFor(int $flags = Stat::FLAG_NONE) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, , $c, , $f])
|
||||
if ($c > 0 && (!$flags || $flags & $f))
|
||||
$x[$k] = $c;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getFilterCriteriumIdFor(int $flags = Stat::FLAG_NONE) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, , , $cr, $f])
|
||||
if ($cr && (!$flags || $flags & $f))
|
||||
$x[$k] = $cr;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getIndexFrom(int $idx, string $match) : int
|
||||
{
|
||||
$i = array_search($match, array_column(self::$data, $idx));
|
||||
if ($i === false)
|
||||
return 0;
|
||||
|
||||
return array_keys(self::$data)[$i];
|
||||
}
|
||||
}
|
||||
|
||||
class StatsContainer implements \Countable
|
||||
{
|
||||
private $store = [];
|
||||
|
||||
private $relSpells = [];
|
||||
private $relEnchantments = [];
|
||||
|
||||
public function __construct(array $relSpells = [], array $relEnchantments = [])
|
||||
{
|
||||
if ($relSpells)
|
||||
$this->relSpells = $relSpells;
|
||||
|
||||
if ($relEnchantments)
|
||||
$this->relEnchantments = $relEnchantments;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Source */
|
||||
/**********/
|
||||
|
||||
public function fromItem(array $item) : self
|
||||
{
|
||||
if (!$item)
|
||||
return $this;
|
||||
|
||||
// convert itemMods to stats
|
||||
for ($i = 1; $i <= 10; $i++)
|
||||
{
|
||||
$mod = $item['statType'.$i];
|
||||
$val = $item['statValue'.$i];
|
||||
if (!$mod || !$val)
|
||||
continue;
|
||||
|
||||
if ($idx = Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $mod))
|
||||
Util::arraySumByKey($this->store, [$idx => $val]);
|
||||
}
|
||||
|
||||
// also occurs as seperate field (gets summed in calculation but not in tooltip)
|
||||
if ($item['tplBlock'])
|
||||
Util::arraySumByKey($this->store, [Stat::BLOCK => $item['tplBlock']]);
|
||||
|
||||
// convert spells to stats
|
||||
for ($i = 1; $i <= 5; $i++)
|
||||
if (in_array($item['spellTrigger'.$i], [SPELL_TRIGGER_EQUIP, SPELL_TRIGGER_USE, SPELL_TRIGGER_USE_NODELAY]))
|
||||
if ($relS = $this->relS($item['spellId'.$i]))
|
||||
$this->fromSpell($relS);
|
||||
|
||||
// for ITEM_CLASS_GEM get stats from enchantment
|
||||
if ($relE = $this->relE($item['gemEnchantmentId']))
|
||||
$this->fromEnchantment($relE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromEnchantment(array $enchantment) : self
|
||||
{
|
||||
if (!$enchantment)
|
||||
return $this;
|
||||
|
||||
for ($i = 1; $i <= 3; $i++)
|
||||
{
|
||||
$type = $enchantment['type'.$i];
|
||||
$object = $enchantment['object'.$i];
|
||||
$amount = $enchantment['amount'.$i]; // !CAUTION! scaling enchantments are initialized with "0" as amount. 0 is a valid amount!
|
||||
|
||||
if ($type == ENCHANTMENT_TYPE_EQUIP_SPELL && ($relS = $this->relS($object)))
|
||||
$this->fromSpell($relS);
|
||||
else
|
||||
foreach ($this->convertEnchantment($type, $object) as $idx)
|
||||
Util::arraySumByKey($this->store, [$idx => $amount]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromSpell(array $spell) : self
|
||||
{
|
||||
if (!$spell)
|
||||
return $this;
|
||||
|
||||
// if spells grant an equal, non-zero amount of SPELL_DAMAGE and SPELL_HEALING, combine them to SPELL_POWER
|
||||
// this probably does not affect enchantments
|
||||
$tmpStore = [];
|
||||
|
||||
for ($i = 1; $i <= 3; $i++)
|
||||
{
|
||||
$eff = $spell['effect'.$i.'Id'];
|
||||
$aura = $spell['effect'.$i.'AuraId'];
|
||||
$mVal = $spell['effect'.$i.'MiscValue'];
|
||||
$amt = $spell['effect'.$i.'BasePoints'] + $spell['effect'.$i.'DieSides'];
|
||||
|
||||
if (in_array($eff, SpellList::EFFECTS_ENCHANTMENT) && ($relE = $this->relE($mVal)))
|
||||
$this->fromEnchantment($relE);
|
||||
else
|
||||
foreach ($this->convertSpellEffect($aura, $mVal, $amt) as $idx)
|
||||
Util::arraySumByKey($tmpStore, [$idx => $amt]);
|
||||
}
|
||||
|
||||
if (!empty($tmpStore[Stat::HEALING_SPELL_POWER]) && !empty($tmpStore[Stat::DAMAGE_SPELL_POWER]) && $tmpStore[Stat::HEALING_SPELL_POWER] == $tmpStore[Stat::DAMAGE_SPELL_POWER])
|
||||
{
|
||||
Util::arraySumByKey($tmpStore, [Stat::SPELL_POWER => $tmpStore[Stat::HEALING_SPELL_POWER]]);
|
||||
unset($tmpStore[Stat::HEALING_SPELL_POWER]);
|
||||
unset($tmpStore[Stat::DAMAGE_SPELL_POWER]);
|
||||
}
|
||||
|
||||
Util::arraySumByKey($this->store, $tmpStore);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromJson(array &$json, bool $pruneFromSrc = false) : self
|
||||
{
|
||||
if (!$json)
|
||||
return $this;
|
||||
|
||||
foreach (Stat::getJsonStringsFor() as $idx => $key)
|
||||
{
|
||||
if (isset($json[$key])) // 0 is a valid amount!
|
||||
{
|
||||
if (Stat::getFlags($idx) & Stat::FLAG_FLOAT_VALUE)
|
||||
Util::arraySumByKey($this->store, [$idx => (float)$json[$key]]);
|
||||
else
|
||||
Util::arraySumByKey($this->store, [$idx => (int)$json[$key]]);
|
||||
}
|
||||
|
||||
if ($pruneFromSrc)
|
||||
unset($json[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromDB(int $type, int $typeId, int $fieldFlags = Stat::FLAG_NONE) : self
|
||||
{
|
||||
foreach (DB::Aowow()->selectRow('SELECT (?#) FROM ?_item_stats WHERE `type` = ?d AND `typeId` = ?d', Stat::getJsonStringsFor($fieldFlags ?: (Stat::FLAG_ITEM | Stat::FLAG_SERVERSIDE)), $type, $typeId) as $key => $amt)
|
||||
{
|
||||
if ($amt === null)
|
||||
continue;
|
||||
|
||||
$idx = Stat::getIndexFrom(Stat::IDX_JSON_STR, $key);
|
||||
$float = Stat::getFlags($idx) & Stat::FLAG_FLOAT_VALUE;
|
||||
|
||||
if (Util::checkNumeric($amt, $float ? NUM_CAST_FLOAT : NUM_CAST_INT))
|
||||
Util::arraySumByKey($this->store, [$idx => $amt]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fromContainer(StatsContainer ...$container) : self
|
||||
{
|
||||
foreach ($container as $c)
|
||||
Util::arraySumByKey($this->store, $c->toRaw());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**********/
|
||||
/* Output */
|
||||
/**********/
|
||||
|
||||
public function toJson(int $outFlags = Stat::FLAG_NONE) : array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($this->store as $stat => $amt)
|
||||
if (!$outFlags || (Stat::getFlags($stat) & $outFlags))
|
||||
$out[Stat::getJsonString($stat)] = $amt;
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function toRaw() : array
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
public function filter(?callable $filterFn = null) : self
|
||||
{
|
||||
$this->store = array_filter($this->store, $filterFn, ARRAY_FILTER_USE_BOTH);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function count() : int
|
||||
{
|
||||
return count($this->store);
|
||||
}
|
||||
|
||||
/****************/
|
||||
/* internal use */
|
||||
/****************/
|
||||
|
||||
private function relE(int $enchantmentId) : array
|
||||
{
|
||||
if ($enchantmentId <= 0 || !isset($this->relEnchantments[$enchantmentId]))
|
||||
return [];
|
||||
|
||||
return $this->relEnchantments[$enchantmentId];
|
||||
}
|
||||
|
||||
private function relS(int $spellId) : array
|
||||
{
|
||||
if ($spellId <= 0 || !isset($this->relSpells[$spellId]))
|
||||
return [];
|
||||
|
||||
return $this->relSpells[$spellId];
|
||||
}
|
||||
|
||||
private static function convertEnchantment(int $type, int $object) : array
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case ENCHANTMENT_TYPE_PRISMATIC_SOCKET:
|
||||
return [Stat::EXTRA_SOCKETS];
|
||||
case ENCHANTMENT_TYPE_DAMAGE:
|
||||
return [Stat::WEAPON_DAMAGE];
|
||||
case ENCHANTMENT_TYPE_TOTEM:
|
||||
return [Stat::WEAPON_DPS];
|
||||
case ENCHANTMENT_TYPE_STAT: // ITEM_MOD_*
|
||||
return [Stat::getIndexFrom(Stat::IDX_ITEM_MOD, $object)];
|
||||
case ENCHANTMENT_TYPE_RESISTANCE:
|
||||
if ($object == SPELL_SCHOOL_NORMAL)
|
||||
return [Stat::ARMOR];
|
||||
if ($object == SPELL_SCHOOL_HOLY)
|
||||
return [Stat::HOLY_RESISTANCE];
|
||||
if ($object == SPELL_SCHOOL_FIRE)
|
||||
return [Stat::FIRE_RESISTANCE];
|
||||
if ($object == SPELL_SCHOOL_NATURE)
|
||||
return [Stat::NATURE_RESISTANCE];
|
||||
if ($object == SPELL_SCHOOL_FROST)
|
||||
return [Stat::FROST_RESISTANCE];
|
||||
if ($object == SPELL_SCHOOL_SHADOW)
|
||||
return [Stat::SHADOW_RESISTANCE];
|
||||
if ($object == SPELL_SCHOOL_ARCANE)
|
||||
return [Stat::ARCANE_RESISTANCE];
|
||||
|
||||
return [];
|
||||
case ENCHANTMENT_TYPE_EQUIP_SPELL: // handled one level up
|
||||
case ENCHANTMENT_TYPE_COMBAT_SPELL: // we do not average effects, so skip
|
||||
case ENCHANTMENT_TYPE_USE_SPELL:
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function convertCombatRating(int $mask) : array
|
||||
{
|
||||
$hitMask = (1 << CR_HIT_MELEE) | (1 << CR_HIT_RANGED) | (1 << CR_HIT_SPELL);
|
||||
if (($mask & $hitMask) == $hitMask)
|
||||
return [Stat::HIT_RTG]; // generic hit rating
|
||||
|
||||
$critMask = (1 << CR_CRIT_MELEE) | (1 << CR_CRIT_RANGED) | (1 << CR_CRIT_SPELL);
|
||||
if (($mask & $critMask) == $critMask)
|
||||
return [Stat::CRIT_RTG]; // generic crit rating
|
||||
|
||||
|
||||
$takentMask = (1 << CR_CRIT_TAKEN_MELEE) | (1 << CR_CRIT_TAKEN_RANGED) | (1 << CR_CRIT_TAKEN_SPELL);
|
||||
if (($mask & $takentMask) == $takentMask)
|
||||
return [Stat::RESILIENCE_RTG]; // resilience
|
||||
|
||||
$result = []; // there really shouldn't be multiple ratings in that mask besides the cases above, but who knows..
|
||||
foreach (Stat::getCombatRatingsFor() as $stat => $cr)
|
||||
if ($mask & (1 << $cr))
|
||||
$result[] = $stat;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function convertSpellEffect(int $auraId, int $miscValue, int &$amount) : array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
switch ($auraId)
|
||||
{
|
||||
case SPELL_AURA_MOD_STAT:
|
||||
if ($miscValue < 0) // all stats
|
||||
return [Stat::AGILITY, Stat::STRENGTH, Stat::INTELLECT, Stat::SPIRIT, Stat::STAMINA];
|
||||
if ($miscValue == STAT_STRENGTH) // one stat
|
||||
return [Stat::STRENGTH];
|
||||
if ($miscValue == STAT_AGILITY)
|
||||
return [Stat::AGILITY];
|
||||
if ($miscValue == STAT_STAMINA)
|
||||
return [Stat::STAMINA];
|
||||
if ($miscValue == STAT_INTELLECT)
|
||||
return [Stat::INTELLECT];
|
||||
if ($miscValue == STAT_SPIRIT)
|
||||
return [Stat::SPIRIT];
|
||||
|
||||
return []; // one bullshit
|
||||
case SPELL_AURA_MOD_INCREASE_HEALTH:
|
||||
case SPELL_AURA_MOD_INCREASE_HEALTH_NONSTACK:
|
||||
case SPELL_AURA_MOD_INCREASE_HEALTH_2:
|
||||
return [Stat::HEALTH];
|
||||
case SPELL_AURA_MOD_DAMAGE_DONE:
|
||||
// + weapon damage
|
||||
if ($miscValue == (1 << SPELL_SCHOOL_NORMAL))
|
||||
return [Stat::WEAPON_DAMAGE];
|
||||
|
||||
// full magic mask
|
||||
if ($miscValue == SPELL_MAGIC_SCHOOLS)
|
||||
return [Stat::DAMAGE_SPELL_POWER];
|
||||
|
||||
// HolySpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_HOLY))
|
||||
$stats[] = Stat::HOLY_SPELL_POWER;
|
||||
|
||||
// FireSpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_FIRE))
|
||||
$stats[] = Stat::FIRE_SPELL_POWER;
|
||||
|
||||
// NatureSpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_NATURE))
|
||||
$stats[] = Stat::NATURE_SPELL_POWER;
|
||||
|
||||
// FrostSpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_FROST))
|
||||
$stats[] = Stat::FROST_SPELL_POWER;
|
||||
|
||||
// ShadowSpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_SHADOW))
|
||||
$stats[] = Stat::SHADOW_SPELL_POWER;
|
||||
|
||||
// ArcaneSpellpower (deprecated; still used in randomproperties)
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_ARCANE))
|
||||
$stats[] = Stat::ARCANE_SPELL_POWER;
|
||||
|
||||
return $stats;
|
||||
case SPELL_AURA_MOD_HEALING_DONE: // not as a mask..
|
||||
return [Stat::HEALING_SPELL_POWER];
|
||||
case SPELL_AURA_MOD_INCREASE_ENERGY: // MiscVal:type see defined Powers only energy/mana in use
|
||||
if ($miscValue == POWER_ENERGY)
|
||||
return [Stat::ENERGY];
|
||||
if ($miscValue == POWER_RAGE)
|
||||
return [Stat::RAGE];
|
||||
if ($miscValue == POWER_MANA)
|
||||
return [Stat::MANA];
|
||||
if ($miscValue == POWER_RUNIC_POWER)
|
||||
return [Stat::RUNIC_POWER];
|
||||
|
||||
return [];
|
||||
case SPELL_AURA_MOD_RATING:
|
||||
case SPELL_AURA_MOD_RATING_FROM_STAT:
|
||||
if ($stat = self::convertCombatRating($miscValue))
|
||||
return $stat;
|
||||
|
||||
return [];
|
||||
case SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE:
|
||||
case SPELL_AURA_MOD_BASE_RESISTANCE:
|
||||
case SPELL_AURA_MOD_RESISTANCE:
|
||||
// Armor only if explicitly specified
|
||||
if ($miscValue == (1 << SPELL_SCHOOL_NORMAL))
|
||||
return [Stat::ARMOR];
|
||||
|
||||
// Holy resistance only if explicitly specified (should it even exist...?)
|
||||
if ($miscValue == (1 << SPELL_SCHOOL_HOLY))
|
||||
return [Stat::HOLY_RESISTANCE];
|
||||
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_FIRE))
|
||||
$stats[] = Stat::FIRE_RESISTANCE;
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_NATURE))
|
||||
$stats[] = Stat::NATURE_RESISTANCE;
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_FROST))
|
||||
$stats[] = Stat::FROST_RESISTANCE;
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_SHADOW))
|
||||
$stats[] = Stat::SHADOW_RESISTANCE;
|
||||
if ($miscValue & (1 << SPELL_SCHOOL_ARCANE))
|
||||
$stats[] = Stat::ARCANE_RESISTANCE;
|
||||
|
||||
return $stats;
|
||||
case SPELL_AURA_PERIODIC_HEAL: // hp5
|
||||
case SPELL_AURA_MOD_REGEN:
|
||||
case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
|
||||
return [Stat::HEALTH_REGENERATION];
|
||||
case SPELL_AURA_MOD_POWER_REGEN: // mp5
|
||||
return [Stat::MANA_REGENERATION];
|
||||
case SPELL_AURA_MOD_ATTACK_POWER:
|
||||
return [Stat::ATTACK_POWER/*, Stat::RANGED_ATTACK_POWER*/];
|
||||
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
|
||||
return [Stat::RANGED_ATTACK_POWER];
|
||||
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
|
||||
return [Stat::BLOCK];
|
||||
case SPELL_AURA_MOD_EXPERTISE:
|
||||
return [Stat::EXPERTISE];
|
||||
case SPELL_AURA_MOD_TARGET_RESISTANCE:
|
||||
$amount = abs($amount); // functionally negative, but we work with the absolute amount
|
||||
if ($miscValue == 0x7C) // SPELL_MAGIC_SCHOOLS & ~SPELL_SCHOOL_HOLY
|
||||
return [Stat::SPELL_PENETRATION];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -137,20 +139,21 @@ class Loot
|
||||
return array_combine($retKeys, $retData);
|
||||
}
|
||||
|
||||
private function getByContainerRecursive(string $tableName, int $lootId, array &$handledRefs, int $groupId = 0, float $baseChance = 1.0) : ?array
|
||||
private function getByContainerRecursive(string $tableName, int $lootId, array &$handledRefs, int $groupId = 0, float $baseChance = 1.0) : array
|
||||
{
|
||||
$loot = [];
|
||||
$rawItems = [];
|
||||
|
||||
if (!$tableName || !$lootId)
|
||||
return null;
|
||||
return [null, null];
|
||||
|
||||
$rows = DB::World()->select('SELECT * FROM ?# WHERE entry = ?d{ AND groupid = ?d}', $tableName, $lootId, $groupId ?: DBSIMPLE_SKIP);
|
||||
if (!$rows)
|
||||
return null;
|
||||
return [null, null];
|
||||
|
||||
$groupChances = [];
|
||||
$nGroupEquals = [];
|
||||
$cnd = new Conditions();
|
||||
foreach ($rows as $entry)
|
||||
{
|
||||
$set = array(
|
||||
@@ -161,6 +164,11 @@ class Loot
|
||||
'groupChance' => 0
|
||||
);
|
||||
|
||||
if ($entry['QuestRequired'])
|
||||
foreach (DB::Aowow()->selectCol('SELECT id FROM ?_quests WHERE (`reqSourceItemId1` = ?d OR `reqSourceItemId2` = ?d OR `reqSourceItemId3` = ?d OR `reqSourceItemId4` = ?d OR `reqItemId1` = ?d OR `reqItemId2` = ?d OR `reqItemId3` = ?d OR `reqItemId4` = ?d OR `reqItemId5` = ?d OR `reqItemId6` = ?d) AND (`cuFlags` & ?d) = 0',
|
||||
$entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], $entry['Item'], CUSTOM_EXCLUDE_FOR_LISTVIEW | CUSTOM_UNAVAILABLE) as $questId)
|
||||
$cnd->addExternalCondition(Conditions::lootTableToConditionSource($tableName), $lootId . ':' . $entry['Item'], [Conditions::QUESTTAKEN, $questId], true);
|
||||
|
||||
// if ($entry['LootMode'] > 1)
|
||||
// {
|
||||
$buff = [];
|
||||
@@ -257,6 +265,12 @@ class Loot
|
||||
$groupChances[$k] = (100 - $sum) / ($nGroupEquals[$k] ?: 1);
|
||||
}
|
||||
|
||||
if ($cnd->getBySourceGroup($lootId, Conditions::lootTableToConditionSource($tableName))->prepare())
|
||||
{
|
||||
self::storeJSGlobals($cnd->getJsGlobals());
|
||||
$cnd->toListviewColumn($loot, $this->extraCols, $lootId, 'content');
|
||||
}
|
||||
|
||||
return [$loot, array_unique($rawItems)];
|
||||
}
|
||||
|
||||
@@ -268,10 +282,6 @@ class Loot
|
||||
return false;
|
||||
|
||||
/*
|
||||
todo (high): implement conditions on loot (and conditions in general)
|
||||
|
||||
also
|
||||
|
||||
// if (is_array($this->entry) && in_array($table, [LOOT_CREATURE, LOOT_GAMEOBJECT])
|
||||
// iterate over the 4 available difficulties and assign modes
|
||||
|
||||
@@ -279,16 +289,16 @@ class Loot
|
||||
modes:{"mode":1,"1":{"count":4408,"outof":16013},"4":{"count":4408,"outof":22531}}
|
||||
*/
|
||||
$handledRefs = [];
|
||||
$struct = self::getByContainerRecursive($table, $this->entry, $handledRefs);
|
||||
if (!$struct)
|
||||
[$lootRows, $itemIds] = self::getByContainerRecursive($table, $this->entry, $handledRefs);
|
||||
if (!$lootRows)
|
||||
return false;
|
||||
|
||||
$items = new ItemList(array(['i.id', $struct[1]], CFG_SQL_LIMIT_NONE));
|
||||
$this->jsGlobals = $items->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED);
|
||||
$items = new ItemList(array(['i.id', $itemIds], Cfg::get('SQL_LIMIT_NONE')));
|
||||
self::storeJSGlobals($items->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED));
|
||||
$foo = $items->getListviewData();
|
||||
|
||||
// assign listview LV rows to loot rows, not the other way round! The same item may be contained multiple times
|
||||
foreach ($struct[0] as $loot)
|
||||
foreach ($lootRows as $loot)
|
||||
{
|
||||
$base = array(
|
||||
'percent' => round($loot['groupChance'] * $loot['realChanceMod'], 3),
|
||||
@@ -303,11 +313,20 @@ class Loot
|
||||
if ($_ = $loot['parentRef'])
|
||||
$base['reference'] = $_;
|
||||
|
||||
if (isset($loot['condition']))
|
||||
$base['condition'] = $loot['condition'];
|
||||
|
||||
if ($_ = self::createStack($loot))
|
||||
$base['pctstack'] = $_;
|
||||
|
||||
if (empty($loot['reference'])) // regular drop
|
||||
{
|
||||
if (!isset($foo[$loot['content']]))
|
||||
{
|
||||
trigger_error('Item #'.$loot['content'].' referenced by loot does not exist!', E_USER_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!User::isInGroup(U_GROUP_EMPLOYEE))
|
||||
{
|
||||
if (!isset($this->results[$loot['content']]))
|
||||
@@ -380,33 +399,36 @@ class Loot
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getByItem(int $entry, int $maxResults = CFG_SQL_LIMIT_DEFAULT, array $lootTableList = []) : bool
|
||||
public function getByItem(int $entry, int $maxResults = -1, array $lootTableList = []) : bool
|
||||
{
|
||||
$this->entry = intVal($entry);
|
||||
$this->entry = $entry;
|
||||
|
||||
if (!$this->entry)
|
||||
return false;
|
||||
|
||||
if ($maxResults < 0)
|
||||
$maxResults = Cfg::get('SQL_LIMIT_DEFAULT');
|
||||
|
||||
// [fileName, tabData, tabName, tabId, extraCols, hiddenCols, visibleCols]
|
||||
$tabsFinal = array(
|
||||
['item', [], '$LANG.tab_containedin', 'contained-in-item', [], [], []],
|
||||
['item', [], '$LANG.tab_disenchantedfrom', 'disenchanted-from', [], [], []],
|
||||
['item', [], '$LANG.tab_prospectedfrom', 'prospected-from', [], [], []],
|
||||
['item', [], '$LANG.tab_milledfrom', 'milled-from', [], [], []],
|
||||
['creature', [], '$LANG.tab_droppedby', 'dropped-by', [], [], []],
|
||||
['creature', [], '$LANG.tab_pickpocketedfrom', 'pickpocketed-from', [], [], []],
|
||||
['creature', [], '$LANG.tab_skinnedfrom', 'skinned-from', [], [], []],
|
||||
['creature', [], '$LANG.tab_minedfromnpc', 'mined-from-npc', [], [], []],
|
||||
['creature', [], '$LANG.tab_salvagedfrom', 'salvaged-from', [], [], []],
|
||||
['creature', [], '$LANG.tab_gatheredfromnpc', 'gathered-from-npc', [], [], []],
|
||||
['quest', [], '$LANG.tab_rewardfrom', 'reward-from-quest', [], [], []],
|
||||
['zone', [], '$LANG.tab_fishedin', 'fished-in-zone', [], [], []],
|
||||
['object', [], '$LANG.tab_containedin', 'contained-in-object', [], [], []],
|
||||
['object', [], '$LANG.tab_minedfrom', 'mined-from-object', [], [], []],
|
||||
['object', [], '$LANG.tab_gatheredfrom', 'gathered-from-object', [], [], []],
|
||||
['object', [], '$LANG.tab_fishedin', 'fished-in-object', [], [], []],
|
||||
['spell', [], '$LANG.tab_createdby', 'created-by', [], [], []],
|
||||
['achievement', [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
|
||||
[Type::ITEM, [], '$LANG.tab_containedin', 'contained-in-item', [], [], []],
|
||||
[Type::ITEM, [], '$LANG.tab_disenchantedfrom', 'disenchanted-from', [], [], []],
|
||||
[Type::ITEM, [], '$LANG.tab_prospectedfrom', 'prospected-from', [], [], []],
|
||||
[Type::ITEM, [], '$LANG.tab_milledfrom', 'milled-from', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_droppedby', 'dropped-by', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_pickpocketedfrom', 'pickpocketed-from', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_skinnedfrom', 'skinned-from', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_minedfromnpc', 'mined-from-npc', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_salvagedfrom', 'salvaged-from', [], [], []],
|
||||
[Type::NPC, [], '$LANG.tab_gatheredfromnpc', 'gathered-from-npc', [], [], []],
|
||||
[Type::QUEST, [], '$LANG.tab_rewardfrom', 'reward-from-quest', [], [], []],
|
||||
[Type::ZONE, [], '$LANG.tab_fishedin', 'fished-in-zone', [], [], []],
|
||||
[Type::OBJECT, [], '$LANG.tab_containedin', 'contained-in-object', [], [], []],
|
||||
[Type::OBJECT, [], '$LANG.tab_minedfrom', 'mined-from-object', [], [], []],
|
||||
[Type::OBJECT, [], '$LANG.tab_gatheredfrom', 'gathered-from-object', [], [], []],
|
||||
[Type::OBJECT, [], '$LANG.tab_fishedin', 'fished-in-object', [], [], []],
|
||||
[Type::SPELL, [], '$LANG.tab_createdby', 'created-by', [], [], []],
|
||||
[Type::ACHIEVEMENT, [], '$LANG.tab_rewardfrom', 'reward-from-achievement', [], [], []]
|
||||
);
|
||||
$refResults = [];
|
||||
$query = 'SELECT
|
||||
@@ -436,6 +458,16 @@ class Loot
|
||||
$this->entry
|
||||
);
|
||||
|
||||
/* i'm currently not seeing a reasonable way to blend this into creature/gobject/etc tabs as one entity may drop the same item multiple times, with and without conditions.
|
||||
if ($newRefs)
|
||||
{
|
||||
$cnd = new Conditions();
|
||||
if ($cnd->getBySourceEntry($this->entry, Conditions::SRC_REFERENCE_LOOT_TEMPLATE))
|
||||
if ($cnd->toListviewColumn($newRefs, $x, $this->entry))
|
||||
self::storejsGlobals($cnd->getJsGlobals());
|
||||
}
|
||||
*/
|
||||
|
||||
while ($newRefs)
|
||||
{
|
||||
$curRefs = $newRefs;
|
||||
@@ -451,14 +483,17 @@ class Loot
|
||||
/*
|
||||
search the real loot-templates for the itemId and gathered refds
|
||||
*/
|
||||
for ($i = 1; $i < count($this->lootTemplates); $i++)
|
||||
foreach ($this->lootTemplates as $lootTemplate)
|
||||
{
|
||||
if ($lootTableList && !in_array($this->lootTemplates[$i], $lootTableList))
|
||||
if ($lootTableList && !in_array($lootTemplate, $lootTableList))
|
||||
continue;
|
||||
|
||||
if ($lootTemplate == LOOT_REFERENCE)
|
||||
continue;
|
||||
|
||||
$result = $this->calcChance(DB::World()->select(
|
||||
sprintf($query, '{lt1.reference IN (?a) OR }(lt1.reference = 0 AND lt1.item = ?d)'),
|
||||
$this->lootTemplates[$i], $this->lootTemplates[$i],
|
||||
$lootTemplate, $lootTemplate,
|
||||
$refResults ? array_keys($refResults) : DBSIMPLE_SKIP,
|
||||
$this->entry
|
||||
));
|
||||
@@ -474,10 +509,10 @@ class Loot
|
||||
}
|
||||
|
||||
// cap fetched entries to the sql-limit to guarantee, that the highest chance items get selected first
|
||||
// screws with GO-loot and skinnig-loot as these templates are shared for several tabs (fish, herb, ore) (herb, ore, leather)
|
||||
// screws with GO-loot and skinning-loot as these templates are shared for several tabs (fish, herb, ore) (herb, ore, leather)
|
||||
$ids = array_slice(array_keys($result), 0, $maxResults);
|
||||
|
||||
switch ($this->lootTemplates[$i])
|
||||
switch ($lootTemplate)
|
||||
{
|
||||
case LOOT_CREATURE: $field = 'lootId'; $tabId = 4; break;
|
||||
case LOOT_PICKPOCKET: $field = 'pickpocketLootId'; $tabId = 5; break;
|
||||
@@ -552,9 +587,9 @@ class Loot
|
||||
case LOOT_SPELL:
|
||||
$conditions = array(
|
||||
'OR',
|
||||
['AND', ['effect1CreateItemId', $this->entry], ['OR', ['effect1Id', SpellList::$effects['itemCreate']], ['effect1AuraId', SpellList::$auras['itemCreate']]]],
|
||||
['AND', ['effect2CreateItemId', $this->entry], ['OR', ['effect2Id', SpellList::$effects['itemCreate']], ['effect2AuraId', SpellList::$auras['itemCreate']]]],
|
||||
['AND', ['effect3CreateItemId', $this->entry], ['OR', ['effect3Id', SpellList::$effects['itemCreate']], ['effect3AuraId', SpellList::$auras['itemCreate']]]],
|
||||
['AND', ['effect1CreateItemId', $this->entry], ['OR', ['effect1Id', SpellList::EFFECTS_ITEM_CREATE], ['effect1AuraId', SpellList::AURAS_ITEM_CREATE]]],
|
||||
['AND', ['effect2CreateItemId', $this->entry], ['OR', ['effect2Id', SpellList::EFFECTS_ITEM_CREATE], ['effect2AuraId', SpellList::AURAS_ITEM_CREATE]]],
|
||||
['AND', ['effect3CreateItemId', $this->entry], ['OR', ['effect3Id', SpellList::EFFECTS_ITEM_CREATE], ['effect3AuraId', SpellList::AURAS_ITEM_CREATE]]],
|
||||
);
|
||||
if ($ids)
|
||||
$conditions[] = ['id', $ids];
|
||||
@@ -568,7 +603,7 @@ class Loot
|
||||
if (!empty($result))
|
||||
$tabsFinal[16][4][] = '$Listview.extraCols.percent';
|
||||
|
||||
if ($srcObj->hasSetFields(['reagent1']))
|
||||
if ($srcObj->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8'))
|
||||
$tabsFinal[16][6][] = 'reagents';
|
||||
|
||||
foreach ($srcObj->iterate() as $_)
|
||||
@@ -580,13 +615,28 @@ class Loot
|
||||
if (!$ids)
|
||||
continue;
|
||||
|
||||
$parentData = [];
|
||||
switch ($tabsFinal[abs($tabId)][0])
|
||||
{
|
||||
case 'creature': // new CreatureList
|
||||
case 'item': // new ItemList
|
||||
case 'zone': // new ZoneList
|
||||
$oName = ucFirst($tabsFinal[abs($tabId)][0]).'List';
|
||||
$srcObj = new $oName(array([$field, $ids]));
|
||||
case TYPE::NPC: // new CreatureList
|
||||
if ($baseIds = DB::Aowow()->selectCol(
|
||||
'SELECT `difficultyEntry1` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry1 IN (?a) UNION
|
||||
SELECT `difficultyEntry2` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry2 IN (?a) UNION
|
||||
SELECT `difficultyEntry3` AS ARRAY_KEY, `id` FROM ?_creature WHERE difficultyEntry3 IN (?a)',
|
||||
$ids, $ids, $ids))
|
||||
{
|
||||
$parentObj = new CreatureList(array(['id', $baseIds]));
|
||||
if (!$parentObj->error)
|
||||
{
|
||||
self::storeJSGlobals($parentObj->getJSGlobals());
|
||||
$parentData = $parentObj->getListviewData();
|
||||
$ids = array_diff($ids, $baseIds);
|
||||
}
|
||||
}
|
||||
|
||||
case Type::ITEM: // new ItemList
|
||||
case Type::ZONE: // new ZoneList
|
||||
$srcObj = Type::newList($tabsFinal[abs($tabId)][0], array([$field, $ids]));
|
||||
if (!$srcObj->error)
|
||||
{
|
||||
$srcData = $srcObj->getListviewData();
|
||||
@@ -594,16 +644,20 @@ class Loot
|
||||
|
||||
foreach ($srcObj->iterate() as $curTpl)
|
||||
{
|
||||
if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_HERBLOOT)
|
||||
if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_HERBALISM)
|
||||
$tabId = 9;
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_ENGINEERLOOT)
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_ENGINEERING)
|
||||
$tabId = 8;
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_MININGLOOT)
|
||||
else if ($tabId < 0 && $curTpl['typeFlags'] & NPC_TYPEFLAG_SKIN_WITH_MINING)
|
||||
$tabId = 7;
|
||||
else if ($tabId < 0)
|
||||
$tabId = abs($tabId); // general case (skinning)
|
||||
|
||||
if (($p = $srcObj->getField('parentId')) && ($d = $parentData[$p] ?? null))
|
||||
$tabsFinal[$tabId][1][] = array_merge($d, $result[$srcObj->getField($field)]);
|
||||
else
|
||||
$tabsFinal[$tabId][1][] = array_merge($srcData[$srcObj->id], $result[$srcObj->getField($field)]);
|
||||
|
||||
$tabsFinal[$tabId][4][] = '$Listview.extraCols.percent';
|
||||
}
|
||||
}
|
||||
@@ -628,7 +682,7 @@ class Loot
|
||||
if ($data[6])
|
||||
$tabData['visibleCols'] = array_unique($data[6]);
|
||||
|
||||
$this->results[$tabId] = [$data[0], $tabData];
|
||||
$this->results[$tabId] = [Type::getFileString($data[0]), $tabData];
|
||||
}
|
||||
|
||||
return true;
|
||||
348
includes/game/misc.php
Normal file
348
includes/game/misc.php
Normal file
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Game
|
||||
{
|
||||
public static $resistanceFields = array(
|
||||
null, 'resHoly', 'resFire', 'resNature', 'resFrost', 'resShadow', 'resArcane'
|
||||
);
|
||||
|
||||
public static $rarityColorStings = array( // zero-indexed
|
||||
'9d9d9d', 'ffffff', '1eff00', '0070dd', 'a335ee', 'ff8000', 'e5cc80', 'e6cc80'
|
||||
);
|
||||
|
||||
public static $specIconStrings = array(
|
||||
-1 => 'inv_misc_questionmark',
|
||||
0 => 'spell_nature_elementalabsorption',
|
||||
6 => ['spell_deathknight_bloodpresence', 'spell_deathknight_frostpresence', 'spell_deathknight_unholypresence' ],
|
||||
11 => ['spell_nature_starfall', 'ability_racial_bearform', 'spell_nature_healingtouch' ],
|
||||
3 => ['ability_hunter_beasttaming', 'ability_marksmanship', 'ability_hunter_swiftstrike' ],
|
||||
8 => ['spell_holy_magicalsentry', 'spell_fire_firebolt02', 'spell_frost_frostbolt02' ],
|
||||
2 => ['spell_holy_holybolt', 'spell_holy_devotionaura', 'spell_holy_auraoflight' ],
|
||||
5 => ['spell_holy_wordfortitude', 'spell_holy_holybolt', 'spell_shadow_shadowwordpain' ],
|
||||
4 => ['ability_rogue_eviscerate', 'ability_backstab', 'ability_stealth' ],
|
||||
7 => ['spell_nature_lightning', 'spell_nature_lightningshield', 'spell_nature_magicimmunity' ],
|
||||
9 => ['spell_shadow_deathcoil', 'spell_shadow_metamorphosis', 'spell_shadow_rainoffire' ],
|
||||
1 => ['ability_rogue_eviscerate', 'ability_warrior_innerrage', 'ability_warrior_defensivestance' ]
|
||||
);
|
||||
|
||||
public static $questClasses = array(
|
||||
-2 => [ 0],
|
||||
0 => [ 1, 3, 4, 8, 9, 10, 11, 12, 25, 28, 33, 36, 38, 40, 41, 44, 45, 46, 47, 51, 85, 130, 132, 139, 154, 267, 1497, 1519, 1537, 2257, 3430, 3431, 3433, 3487, 4080, 4298],
|
||||
1 => [ 14, 15, 16, 17, 141, 148, 188, 215, 220, 331, 357, 361, 363, 400, 405, 406, 440, 490, 493, 618, 1377, 1637, 1638, 1657, 1769, 3524, 3525, 3526, 3557],
|
||||
2 => [ 206, 209, 491, 717, 718, 719, 721, 722, 796, 1176, 1196, 1337, 1477, 1581, 1583, 1584, 1941, 2017, 2057, 2100, 2366, 2367, 2437, 2557, 3535, 3562, 3688, 3713, 3714, 3715, 3716, 3717, 3789, 3790, 3791, 3792, 3842, 3847, 3848, 3849, 3905, 4100, 4131, 4196, 4228, 4264, 4265, 4272, 4277, 4415, 4416, 4494, 4522, 4723, 4809, 4813, 4820],
|
||||
3 => [ 1977, 2159, 2677, 2717, 3428, 3429, 3456, 3457, 3606, 3607, 3805, 3836, 3845, 3923, 3959, 4075, 4273, 4493, 4500, 4603, 4722, 4812, 4987],
|
||||
4 => [ -372, -263, -262, -261, -162, -161, -141, -82, -81, -61],
|
||||
5 => [ -373, -371, -324, -304, -264, -201, -182, -181, -121, -101, -24],
|
||||
6 => [ -25, 2597, 3277, 3358, 3820, 4384, 4710],
|
||||
7 => [-1010, -368, -367, -365, -344, -241, -1],
|
||||
8 => [ 3483, 3518, 3519, 3520, 3521, 3522, 3523, 3679, 3703],
|
||||
9 => [-1005, -1003, -1002, -1001, -376, -375, -374, -370, -369, -366, -364, -41, -22], // 22: seasonal
|
||||
10 => [ 65, 66, 67, 210, 394, 495, 2817, 3537, 3711, 4024, 4197, 4395, 4742]
|
||||
);
|
||||
|
||||
// zoneorsort for quests need updating
|
||||
// partially points non-instanced area with identical name for instance quests
|
||||
public static $questSortFix = array(
|
||||
-221 => 440, // Treasure Map => Tanaris
|
||||
-284 => 0, // Special => Misc (some quests get shuffled into seasonal)
|
||||
151 => 0, // Designer Island => Misc
|
||||
22 => 0, // Programmer Isle
|
||||
35 => 33, // Booty Bay => Stranglethorn Vale
|
||||
131 => 132, // Kharanos => Coldridge Valley
|
||||
24 => 9, // Northshire Abbey => Northshire Valley
|
||||
279 => 36, // Dalaran Crater => Alterac Mountains
|
||||
4342 => 4298, // Acherus: The Ebon Hold => The Scarlet Enclave
|
||||
2079 => 15, // Alcaz Island => Dustwallow Marsh
|
||||
1939 => 440, // Abyssal Sands => Tanaris
|
||||
393 => 363, // Darkspeer Strand => Valley of Trials
|
||||
702 => 141, // Rut'theran Village => Teldrassil
|
||||
221 => 220, // Camp Narache => Red Cloud Mesa
|
||||
1116 => 357, // Feathermoon Stronghold => Feralas
|
||||
236 => 209, // Shadowfang Keep
|
||||
4769 => 4742, // Hrothgar's Landing => Hrothgar's Landing
|
||||
4613 => 4395, // Dalaran City => Dalaran
|
||||
4522 => 210, // Icecrown Citadell => Icecrown
|
||||
3896 => 3703, // Aldor Rise => Shattrath City
|
||||
3696 => 3522, // The Barrier Hills => Blade's Edge Mountains
|
||||
2839 => 2597, // Alterac Valley
|
||||
19 => 1977, // Zul'Gurub
|
||||
4445 => 4273, // Ulduar
|
||||
2300 => 1941, // Caverns of Time
|
||||
3545 => 3535, // Hellfire Citadel
|
||||
2562 => 3457, // Karazhan
|
||||
3840 => 3959, // Black Temple
|
||||
1717 => 491, // Razorfen Kraul
|
||||
978 => 1176, // Zul'Farrak
|
||||
133 => 721, // Gnomeregan
|
||||
3607 => 3905, // Serpentshrine Cavern
|
||||
3845 => 3842, // Tempest Keep
|
||||
1517 => 1337, // Uldaman
|
||||
1417 => 1477 // Sunken Temple
|
||||
);
|
||||
|
||||
public static $questSubCats = array(
|
||||
1 => [132], // Dun Morogh: Coldridge Valley
|
||||
12 => [9], // Elwynn Forest: Northshire Valley
|
||||
141 => [188], // Teldrassil: Shadowglen
|
||||
3524 => [3526], // Azuremyst Isle: Ammen Vale
|
||||
|
||||
14 => [363], // Durotar: Valley of Trials
|
||||
85 => [154], // Tirisfal Glades: Deathknell
|
||||
215 => [220], // Mulgore: Red Cloud Mesa
|
||||
3430 => [3431], // Eversong Woods: Sunstrider Isle
|
||||
|
||||
46 => [25], // Burning Steppes: Blackrock Mountain
|
||||
361 => [1769], // Felwood: Timbermaw Hold
|
||||
3519 => [3679], // Terokkar: Skettis
|
||||
3535 => [3562, 3713, 3714], // Hellfire Citadel
|
||||
3905 => [3715, 3716, 3717], // Coilfang Reservoir
|
||||
3688 => [3789, 3790, 3792], // Auchindoun
|
||||
1941 => [2366, 2367, 4100], // Caverns of Time
|
||||
3842 => [3847, 3848, 3849], // Tempest Keep
|
||||
4522 => [4809, 4813, 4820] // Icecrown Citadel
|
||||
);
|
||||
|
||||
/* why:
|
||||
Because petSkills (and ranged weapon skills) are the only ones with more than two skillLines attached. Because Left Joining ?_spell with ?_skillLineability causes more trouble than it has uses.
|
||||
Because this is more or less the only reaonable way to fit all that information into one database field, so..
|
||||
.. the indizes of this array are bits of skillLine2OrMask in ?_spell if skillLineId1 is negative
|
||||
*/
|
||||
public static $skillLineMask = array( // idx => [familyId, skillLineId]
|
||||
-1 => array( // Pets (Hunter)
|
||||
[ 1, 208], [ 2, 209], [ 3, 203], [ 4, 210], [ 5, 211], [ 6, 212], [ 7, 213], // Wolf, Cat, Spider, Bear, Boar, Crocolisk, Carrion Bird
|
||||
[ 8, 214], [ 9, 215], [11, 217], [12, 218], [20, 236], [21, 251], [24, 653], // Crab, Gorilla, Raptor, Tallstrider, Scorpid, Turtle, Bat
|
||||
[25, 654], [26, 655], [27, 656], [30, 763], [31, 767], [32, 766], [33, 765], // Hyena, Bird of Prey, Wind Serpent, Dragonhawk, Ravager, Warp Stalker, Sporebat
|
||||
[34, 764], [35, 768], [37, 775], [38, 780], [39, 781], [41, 783], [42, 784], // Nether Ray, Serpent, Moth, Chimaera, Devilsaur, Silithid, Worm
|
||||
[43, 786], [44, 785], [45, 787], [46, 788] // Rhino, Wasp, Core Hound, Spirit Beast
|
||||
),
|
||||
-2 => array( // Pets (Warlock)
|
||||
[15, 189], [16, 204], [17, 205], [19, 207], [23, 188], [29, 761] // Felhunter, Voidwalker, Succubus, Doomguard, Imp, Felguard
|
||||
),
|
||||
-3 => array( // Ranged Weapons
|
||||
[null, 45], [null, 46], [null, 226] // Bow, Gun, Crossbow
|
||||
)
|
||||
);
|
||||
|
||||
public static $sockets = array( // jsStyle Strings
|
||||
'meta', 'red', 'yellow', 'blue'
|
||||
);
|
||||
|
||||
public static function getReputationLevelForPoints($pts)
|
||||
{
|
||||
if ($pts >= 41999)
|
||||
return REP_EXALTED;
|
||||
else if ($pts >= 20999)
|
||||
return REP_REVERED;
|
||||
else if ($pts >= 8999)
|
||||
return REP_HONORED;
|
||||
else if ($pts >= 2999)
|
||||
return REP_FRIENDLY;
|
||||
else if ($pts >= 0)
|
||||
return REP_NEUTRAL;
|
||||
else if ($pts >= -3000)
|
||||
return REP_UNFRIENDLY;
|
||||
else if ($pts >= -6000)
|
||||
return REP_HOSTILE;
|
||||
else
|
||||
return REP_HATED;
|
||||
}
|
||||
|
||||
public static function getTaughtSpells(&$spell)
|
||||
{
|
||||
$extraIds = [-1]; // init with -1 to prevent empty-array errors
|
||||
$lookup = [-1];
|
||||
switch (gettype($spell))
|
||||
{
|
||||
case 'object':
|
||||
if (get_class($spell) != __NAMESPACE__.'\SpellList')
|
||||
return [];
|
||||
|
||||
$lookup[] = $spell->id;
|
||||
foreach ($spell->canTeachSpell() as $idx)
|
||||
$extraIds[] = $spell->getField('effect'.$idx.'TriggerSpell');
|
||||
|
||||
break;
|
||||
case 'integer':
|
||||
$lookup[] = $spell;
|
||||
break;
|
||||
case 'array':
|
||||
$lookup = $spell;
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
// note: omits required spell and chance in skill_discovery_template
|
||||
$data = array_merge(
|
||||
DB::World()->selectCol('SELECT spellId FROM spell_learn_spell WHERE entry IN (?a)', $lookup),
|
||||
DB::World()->selectCol('SELECT spellId FROM skill_discovery_template WHERE reqSpell IN (?a)', $lookup),
|
||||
$extraIds
|
||||
);
|
||||
|
||||
// return list of integers, not strings
|
||||
$data = array_map('intVal', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function getPageText($ptId)
|
||||
{
|
||||
$pages = [];
|
||||
while ($ptId)
|
||||
{
|
||||
if ($row = DB::World()->selectRow('SELECT ptl.Text AS Text_loc?d, pt.* FROM page_text pt LEFT JOIN page_text_locale ptl ON pt.ID = ptl.ID AND locale = ? WHERE pt.ID = ?d', Lang::getLocale()->value, Lang::getLocale()->json(), $ptId))
|
||||
{
|
||||
$ptId = $row['NextPageID'];
|
||||
$pages[] = Util::parseHtmlText(Util::localizedString($row, 'Text'));
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('Referenced PageTextId #'.$ptId.' is not in DB', E_USER_WARNING);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
public static function getQuotesForCreature(int $creatureId, bool $asHTML = false, string $talkSource = '') : array
|
||||
{
|
||||
$nQuotes = 0;
|
||||
$quotes = [];
|
||||
$soundIds = [];
|
||||
|
||||
$quoteSrc = DB::World()->select(
|
||||
'SELECT ct.`GroupID` AS ARRAY_KEY, ct.`ID` AS ARRAY_KEY2, ct.`Type` AS "talkType", ct.TextRange AS "range",
|
||||
IFNULL(bct.`LanguageID`, ct.`Language`) AS "lang",
|
||||
IFNULL(NULLIF(bct.`Text`, ""), IFNULL(NULLIF(bct.`Text1`, ""), IFNULL(ct.`Text`, ""))) AS "text_loc0",
|
||||
{ IFNULL(NULLIF(bctl.`Text`, ""), IFNULL(NULLIF(bctl.`Text1`, ""), IFNULL(ctl.`Text`, ""))) AS text_loc?d, }
|
||||
IF(bct.`SoundEntriesID` > 0, bct.`SoundEntriesID`, ct.`Sound`) AS "soundId"
|
||||
FROM creature_text ct
|
||||
{ LEFT JOIN creature_text_locale ctl ON ct.`CreatureID` = ctl.`CreatureID` AND ct.`GroupID` = ctl.`GroupID` AND ct.`ID` = ctl.`ID` AND ctl.`Locale` = ? }
|
||||
LEFT JOIN broadcast_text bct ON ct.`BroadcastTextId` = bct.`ID`
|
||||
{ LEFT JOIN broadcast_text_locale bctl ON ct.`BroadcastTextId` = bctl.`ID` AND bctl.`locale` = ? }
|
||||
WHERE ct.`CreatureID` = ?d',
|
||||
Lang::getLocale()->value ?: DBSIMPLE_SKIP,
|
||||
Lang::getLocale()->value ? Lang::getLocale()->json() : DBSIMPLE_SKIP,
|
||||
Lang::getLocale()->value ? Lang::getLocale()->json() : DBSIMPLE_SKIP,
|
||||
$creatureId
|
||||
);
|
||||
|
||||
foreach ($quoteSrc as $grp => $text)
|
||||
{
|
||||
$group = [];
|
||||
foreach ($text as $t)
|
||||
{
|
||||
if ($t['soundId'])
|
||||
$soundIds[] = $t['soundId'];
|
||||
|
||||
$msg = Util::localizedString($t, 'text');
|
||||
if (!$msg)
|
||||
continue;
|
||||
|
||||
// fixup .. either set %s for emotes or dont >.<
|
||||
if (in_array($t['talkType'], [2, 16]) && strpos($msg, '%s') === false)
|
||||
$msg = '%s '.$msg;
|
||||
|
||||
// fixup: bad case-insensitivity
|
||||
$msg = Util::parseHtmlText(str_replace('%S', '%s', htmlentities($msg)), !$asHTML);
|
||||
|
||||
if ($talkSource)
|
||||
$msg = sprintf($msg, $talkSource);
|
||||
|
||||
// make type css compatible
|
||||
switch ($t['talkType'])
|
||||
{
|
||||
case 1: // yell:
|
||||
case 14: $t['talkType'] = 1; break; // - dark red
|
||||
case 2: // emote:
|
||||
case 16: // "
|
||||
case 3: // boss emote:
|
||||
case 41: $t['talkType'] = 4; break; // - orange
|
||||
case 4: // whisper:
|
||||
case 15: // "
|
||||
case 5: // boss whisper:
|
||||
case 42: $t['talkType'] = 3; break; // - pink-ish
|
||||
default: $t['talkType'] = 2; // [type: 0, 12] say: yellow-ish
|
||||
|
||||
}
|
||||
|
||||
// prefix
|
||||
$pre = '';
|
||||
if ($t['talkType'] != 4)
|
||||
$pre = ($talkSource ?: '%s').' '.Lang::npc('textTypes', $t['talkType']).Lang::main('colon').($t['lang'] ? '['.Lang::game('languages', $t['lang']).'] ' : null);
|
||||
|
||||
if ($asHTML)
|
||||
$msg = '<div><span class="s'.$t['talkType'].'">%s'.($t['range'] ? sprintf(Util::$dfnString, Lang::npc('textRanges', $t['range']), $msg) : $msg).'</span></div>';
|
||||
else
|
||||
$msg = '[div][span class=s'.$t['talkType'].']%s'.html_entity_decode($msg).'[/span][/div]';
|
||||
|
||||
$line = array(
|
||||
'range' => $t['range'],
|
||||
'text' => $msg,
|
||||
'prefix' => $pre
|
||||
);
|
||||
|
||||
|
||||
$nQuotes++;
|
||||
$group[] = $line;
|
||||
}
|
||||
|
||||
if ($group)
|
||||
$quotes[$grp] = $group;
|
||||
}
|
||||
|
||||
return [$quotes, $nQuotes, $soundIds];
|
||||
}
|
||||
|
||||
public static function getBreakpointsForSkill(int $skillId, int $reqLevel) : array
|
||||
{
|
||||
if ($skillId == SKILL_FISHING)
|
||||
return array(
|
||||
round(sqrt(.25) * $reqLevel), // 25% valid catches
|
||||
round(sqrt(.50) * $reqLevel), // 50% valid catches
|
||||
round(sqrt(.75) * $reqLevel), // 75% valid catches
|
||||
$reqLevel // 100% valid catches
|
||||
);
|
||||
|
||||
switch ($skillId)
|
||||
{
|
||||
case SKILL_SKINNING:
|
||||
$reqLevel /= 5; // we pass creature level * 5 (so, skill value), but formula depends on actual creature level
|
||||
if ($reqLevel < 10)
|
||||
$reqLevel = 0;
|
||||
else if ($reqLevel < 20)
|
||||
$reqLevel = ($reqLevel - 10) * 10;
|
||||
else
|
||||
$reqLevel *= 5;
|
||||
case SKILL_HERBALISM:
|
||||
case SKILL_LOCKPICKING:
|
||||
case SKILL_JEWELCRAFTING:
|
||||
case SKILL_INSCRIPTION:
|
||||
case SKILL_MINING:
|
||||
case SKILL_ENGINEERING:
|
||||
$points = [$reqLevel]; // red/orange
|
||||
|
||||
if ($reqLevel + 25 <= MAX_SKILL) // orange/yellow
|
||||
$points[] = $reqLevel + 25;
|
||||
|
||||
if ($reqLevel + 50 <= MAX_SKILL) // yellow/green
|
||||
$points[] = $reqLevel + 50;
|
||||
|
||||
if ($reqLevel + 100 <= MAX_SKILL) // green/grey
|
||||
$points[] = $reqLevel + 100;
|
||||
|
||||
return $points;
|
||||
default:
|
||||
return [$reqLevel];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
160
includes/game/worldposition.class.php
Normal file
160
includes/game/worldposition.class.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
abstract class WorldPosition
|
||||
{
|
||||
private static array $alphaMapCache = [];
|
||||
private static array $capitalCities = array( // capitals take precedence over their surrounding area
|
||||
1497, 1637, 1638, 3487, // Undercity, Ogrimmar, Thunder Bluff, Silvermoon City
|
||||
1519, 1537, 1657, 3557, // Stormwind City, Ironforge, Darnassus, The Exodar
|
||||
3703, 4395 // Shattrath City, Dalaran
|
||||
);
|
||||
|
||||
private static function alphaMapCheck(int $areaId, array &$set) : bool
|
||||
{
|
||||
$file = 'cache/alphaMaps/'.$areaId.'.png';
|
||||
if (!file_exists($file)) // file does not exist (probably instanced area)
|
||||
return false;
|
||||
|
||||
// invalid and corner cases (literally)
|
||||
if (empty($set['posX']) || empty($set['posY']) || $set['posX'] >= 100 || $set['posY'] >= 100)
|
||||
{
|
||||
$set = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty(self::$alphaMapCache[$areaId]))
|
||||
self::$alphaMapCache[$areaId] = imagecreatefrompng($file);
|
||||
|
||||
// alphaMaps are 1000 x 1000, adapt points [black => valid point]
|
||||
if (!imagecolorat(self::$alphaMapCache[$areaId], $set['posX'] * 10, $set['posY'] * 10))
|
||||
$set = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function checkZonePos(array $points) : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($points as $res)
|
||||
{
|
||||
if (self::alphaMapCheck($res['areaId'], $res))
|
||||
{
|
||||
if (!$res)
|
||||
continue;
|
||||
|
||||
// some rough measure how central the spawn is on the map (the lower the number, the better)
|
||||
// 0: perfect center; 1: touches a border
|
||||
$q = abs( (($res['posX'] - 50) / 50) * (($res['posY'] - 50) / 50) );
|
||||
|
||||
if (empty($result) || $result[0] > $q)
|
||||
$result = [$q, $res];
|
||||
}
|
||||
// capitals (auto-discovered) and no hand-made alphaMap available
|
||||
else if (in_array($res['areaId'], self::$capitalCities))
|
||||
return $res;
|
||||
// add with lowest quality if alpha map is missing
|
||||
else if (empty($result))
|
||||
$result = [1.0, $res];
|
||||
}
|
||||
|
||||
// spawn does not really match on a map, but we need at least one result
|
||||
if (!$result)
|
||||
{
|
||||
usort($points, function ($a, $b) { return ($a['dist'] < $b['dist']) ? -1 : 1; });
|
||||
$result = [1.0, $points[0]];
|
||||
}
|
||||
|
||||
return $result[1];
|
||||
}
|
||||
|
||||
public static function getForGUID(int $type, int ...$guids) : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
switch ($type)
|
||||
{
|
||||
case Type::NPC:
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM creature WHERE `guid` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::OBJECT:
|
||||
$result = DB::World()->select('SELECT `guid` AS ARRAY_KEY, `id`, `map` AS `mapId`, `position_x` AS `posX`, `position_y` AS `posY` FROM gameobject WHERE `guid` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::SOUND:
|
||||
$result = DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `soundId` AS `id`, `mapId`, `posX`, `posY` FROM ?_soundemitters WHERE `id` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::ZONE:
|
||||
$result = DB::Aowow()->select('SELECT -`id` AS ARRAY_KEY, `id`, `parentMapId` AS `mapId`, `parentX` AS `posX`, `parentY` AS `posY` FROM ?_zones WHERE -`id` IN (?a)', $guids);
|
||||
break;
|
||||
case Type::AREATRIGGER:
|
||||
$result = [];
|
||||
if ($base = array_filter($guids, fn($x) => $x > 0))
|
||||
$result = array_replace($result, DB::AoWoW()->select('SELECT `id` AS ARRAY_KEY, `id`, `mapId`, `posX`, `posY` FROM ?_areatrigger WHERE `id` IN (?a)', $base));
|
||||
if ($endpoints = array_filter($guids, fn($x) => $x < 0))
|
||||
$result = array_replace($result, DB::World()->select(
|
||||
'SELECT -`ID` AS ARRAY_KEY, ID AS `id`, `target_map` AS `mapId`, `target_position_x` AS `posX`, `target_position_y` AS `posY` FROM areatrigger_teleport WHERE -`id` IN (?a) UNION
|
||||
SELECT -`entryorguid` AS ARRAY_KEY, entryorguid AS `id`, `action_param1` AS `mapId`, `target_x` AS `posX`, `target_y` AS `posY` FROM smart_scripts WHERE -`entryorguid` IN (?a) AND `source_type` = ?d AND `action_type` = ?d',
|
||||
$endpoints, $endpoints, SmartAI::SRC_TYPE_AREATRIGGER, SmartAction::ACTION_TELEPORT
|
||||
));
|
||||
break;
|
||||
default:
|
||||
trigger_error('WorldPosition::getForGUID - unsupported TYPE #'.$type, E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($diff = array_diff($guids, array_keys($result)))
|
||||
trigger_error('WorldPosition::getForGUID - no spawn points for TYPE #'.$type.' GUIDS: '.implode(', ', $diff), E_USER_WARNING);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function toZonePos(int $mapId, float $mapX, float $mapY, int $preferedAreaId = 0, int $preferedFloor = -1) : array
|
||||
{
|
||||
if (!$mapId < 0)
|
||||
return [];
|
||||
|
||||
$query =
|
||||
'SELECT
|
||||
x.`id`,
|
||||
x.`areaId`,
|
||||
IF(x.`defaultDungeonMapId` < 0, x.`floor` + 1, x.`floor`) AS `floor`,
|
||||
IF(dm.`id` IS NOT NULL OR x.`defaultDungeonMapId` < 0, 1, 0) AS `multifloor`,
|
||||
ROUND((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`), 1) AS `posX`,
|
||||
ROUND((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`), 1) AS `posY`,
|
||||
SQRT(POWER(ABS((x.`maxY` - ?d) * 100 / (x.`maxY` - x.`minY`) - 50), 2) +
|
||||
POWER(ABS((x.`maxX` - ?d) * 100 / (x.`maxX` - x.`minX`) - 50), 2)) AS `dist`
|
||||
FROM
|
||||
(SELECT 0 AS `id`, `areaId`, `mapId`, `right` AS `minY`, `left` AS `maxY`, `top` AS `maxX`, `bottom` AS `minX`, 0 AS `floor`, 0 AS `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma UNION
|
||||
SELECT dm.`id`, `areaId`, wma.`mapId`, `minY`, `maxY`, `maxX`, `minX`, `floor`, `worldMapAreaId`, `defaultDungeonMapId` FROM ?_worldmaparea wma
|
||||
JOIN ?_dungeonmap dm ON dm.`mapId` = wma.`mapId` WHERE wma.`mapId` NOT IN (0, 1, 530, 571) OR wma.`areaId` = 4395) x
|
||||
LEFT JOIN
|
||||
?_dungeonmap dm ON dm.`mapId` = x.`mapId` AND dm.`worldMapAreaId` = x.`worldMapAreaId` AND dm.`floor` <> x.`floor` AND dm.`worldMapAreaId` > 0
|
||||
WHERE
|
||||
x.`mapId` = ?d AND IF(?d, x.`areaId` = ?d, x.`areaId` <> 0){ AND x.`floor` = ?d - IF(x.`defaultDungeonMapId` < 0, 1, 0)}
|
||||
GROUP BY
|
||||
x.`id`, x.`areaId`
|
||||
HAVING
|
||||
(`posX` BETWEEN 0.1 AND 99.9 AND `posY` BETWEEN 0.1 AND 99.9)
|
||||
ORDER BY
|
||||
`multifloor` DESC, `dist` ASC';
|
||||
|
||||
// dist BETWEEN 0 (center) AND 70.7 (corner)
|
||||
$points = DB::Aowow()->select($query, $mapY, $mapX, $mapY, $mapX, $mapId, $preferedAreaId, $preferedAreaId, $preferedFloor < 0 ? DBSIMPLE_SKIP : $preferedFloor);
|
||||
if (!$points) // retry: pre-instance subareas belong to the instance-maps but are displayed on the outside. There also cases where the zone reaches outside it's own map.
|
||||
$points = DB::Aowow()->select($query, $mapY, $mapX, $mapY, $mapX, $mapId, 0, 0, DBSIMPLE_SKIP);
|
||||
if (!is_array($points))
|
||||
{
|
||||
trigger_error('WorldPosition::toZonePos - query failed', E_USER_ERROR);
|
||||
return [];
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,82 +1,243 @@
|
||||
<?php
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
namespace Aowow;
|
||||
|
||||
mb_internal_encoding('UTF-8');
|
||||
error_reporting(E_ALL);
|
||||
mysqli_report(MYSQLI_REPORT_ERROR);
|
||||
|
||||
define('AOWOW_REVISION', 40);
|
||||
define('OS_WIN', substr(PHP_OS, 0, 3) == 'WIN'); // OS_WIN as per compile info of php
|
||||
define('CLI', PHP_SAPI === 'cli');
|
||||
define('CLI_HAS_E', CLI && // WIN10 and later usually support ANSI escape sequences
|
||||
(!OS_WIN || (function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT))));
|
||||
|
||||
|
||||
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'/*, 'gmp'*/];
|
||||
$badExt = [];
|
||||
$error = '';
|
||||
if ($ext = array_filter($reqExt, fn($x) => !extension_loaded($x)))
|
||||
$error .= 'Required Extension <b>'.implode(', ', $ext)."</b> was not found. Please check if it should exist, using \"<i>php -m</i>\"\n\n";
|
||||
|
||||
if ($ext = array_filter($badExt, fn($x) => extension_loaded($x)))
|
||||
$error .= 'Loaded Extension <b>'.implode(', ', $ext)."</b> is incompatible and must be disabled.\n\n";
|
||||
|
||||
if (version_compare(PHP_VERSION, '8.2.0') < 0)
|
||||
$error .= 'PHP Version <b>8.2</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
|
||||
|
||||
if ($error)
|
||||
die(CLI ? strip_tags($error) : $error);
|
||||
|
||||
|
||||
require_once 'includes/defines.php';
|
||||
require_once 'includes/locale.class.php';
|
||||
require_once 'localization/lang.class.php';
|
||||
require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master)
|
||||
require_once 'includes/database.class.php'; // wrap DBSimple
|
||||
require_once 'includes/utilities.php'; // helper functions
|
||||
require_once 'includes/type.class.php'; // DB types storage and factory
|
||||
require_once 'includes/cfg.class.php'; // Config holder
|
||||
require_once 'includes/user.class.php'; // Session handling (could be skipped for CLI context except for username and password validation used in account creation)
|
||||
require_once 'includes/game/misc.php'; // Misc game related data & functions
|
||||
|
||||
// game client data interfaces
|
||||
spl_autoload_register(function (string $class) : void
|
||||
{
|
||||
if ($i = strrpos($class, '\\'))
|
||||
$class = substr($class, $i + 1);
|
||||
|
||||
if (preg_match('/[^\w]/i', $class))
|
||||
return;
|
||||
|
||||
if ($class == 'Stat' || $class == 'StatsContainer') // entity statistics conversion
|
||||
require_once 'includes/game/chrstatistics.php';
|
||||
else if (file_exists('includes/game/'.strtolower($class).'.class.php'))
|
||||
require_once 'includes/game/'.strtolower($class).'.class.php';
|
||||
});
|
||||
|
||||
// our site components
|
||||
spl_autoload_register(function (string $class) : void
|
||||
{
|
||||
if ($i = strrpos($class, '\\'))
|
||||
$class = substr($class, $i + 1);
|
||||
|
||||
if (preg_match('/[^\w]/i', $class))
|
||||
return;
|
||||
|
||||
if (file_exists('includes/components/'.strtolower($class).'.class.php'))
|
||||
require_once 'includes/components/'.strtolower($class).'.class.php';
|
||||
else if (file_exists('includes/components/frontend/'.strtolower($class).'.class.php'))
|
||||
require_once 'includes/components/frontend/'.strtolower($class).'.class.php';
|
||||
});
|
||||
|
||||
// TC systems in components
|
||||
spl_autoload_register(function (string $class) : void
|
||||
{
|
||||
switch ($class)
|
||||
{
|
||||
case __NAMESPACE__.'\SmartAI':
|
||||
case __NAMESPACE__.'\SmartEvent':
|
||||
case __NAMESPACE__.'\SmartAction':
|
||||
case __NAMESPACE__.'\SmartTarget':
|
||||
require_once 'includes/components/SmartAI/SmartAI.class.php';
|
||||
require_once 'includes/components/SmartAI/SmartEvent.class.php';
|
||||
require_once 'includes/components/SmartAI/SmartAction.class.php';
|
||||
require_once 'includes/components/SmartAI/SmartTarget.class.php';
|
||||
break;
|
||||
case __NAMESPACE__.'\Conditions':
|
||||
require_once 'includes/components/Conditions/Conditions.class.php';
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// autoload List-classes, associated filters
|
||||
spl_autoload_register(function (string $class) : void
|
||||
{
|
||||
if ($i = strrpos($class, '\\'))
|
||||
$class = substr($class, $i + 1);
|
||||
|
||||
if (preg_match('/[^\w]/i', $class))
|
||||
return;
|
||||
|
||||
if (!stripos($class, 'list'))
|
||||
return;
|
||||
|
||||
$class = strtolower(str_replace('ListFilter', 'List', $class));
|
||||
|
||||
$cl = match ($class)
|
||||
{
|
||||
'localprofilelist',
|
||||
'remoteprofilelist' => 'profile',
|
||||
'localarenateamlist',
|
||||
'remotearenateamlist' => 'arenateam',
|
||||
'localguildlist',
|
||||
'remoteguildlist' => 'guild',
|
||||
default => strtr($class, ['list' => ''])
|
||||
};
|
||||
|
||||
if (file_exists('includes/types/'.$cl.'.class.php'))
|
||||
{
|
||||
require_once 'includes/types/basetype.class.php';
|
||||
require_once 'includes/types/'.$cl.'.class.php';
|
||||
}
|
||||
else
|
||||
throw new \Exception('could not register type class: '.$cl);
|
||||
});
|
||||
|
||||
// endpoint loader
|
||||
spl_autoload_register(function (string $class) : void
|
||||
{
|
||||
if ($i = strrpos($class, '\\'))
|
||||
$class = substr($class, $i + 1);
|
||||
|
||||
if (preg_match('/[^\w]/i', $class))
|
||||
return;
|
||||
|
||||
$class = strtolower($class);
|
||||
|
||||
if (stripos($class, 'ajax') === 0) // handles ajax and jsonp requests
|
||||
{
|
||||
if (file_exists('includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php'))
|
||||
{
|
||||
require_once 'includes/ajaxHandler/ajaxHandler.class.php';
|
||||
require_once 'includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php';
|
||||
}
|
||||
else
|
||||
throw new \Exception('could not register ajaxHandler class: '.$class);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (stripos($class, 'page')) // handles templated pages
|
||||
{
|
||||
if (file_exists('pages/'.strtr($class, ['page' => '']).'.php'))
|
||||
{
|
||||
require_once 'pages/genericPage.class.php';
|
||||
require_once 'pages/'.strtr($class, ['page' => '']).'.php';
|
||||
}
|
||||
else if ($class == 'genericpage') // may be called directly in fatal error case
|
||||
require_once 'pages/genericPage.class.php';
|
||||
}
|
||||
});
|
||||
|
||||
set_error_handler(function(int $errNo, string $errStr, string $errFile, int $errLine) : bool
|
||||
{
|
||||
// either from test function or handled separately
|
||||
if (strstr($errStr, 'mysqli_connect') && $errNo == E_WARNING)
|
||||
return true;
|
||||
|
||||
// we do not log deprecation notices
|
||||
if ($errNo & (E_DEPRECATED | E_USER_DEPRECATED))
|
||||
return true;
|
||||
|
||||
$logLevel = match($errNo)
|
||||
{
|
||||
E_RECOVERABLE_ERROR, E_USER_ERROR => LOG_LEVEL_ERROR,
|
||||
E_WARNING, E_USER_WARNING => LOG_LEVEL_WARN,
|
||||
E_NOTICE, E_USER_NOTICE => LOG_LEVEL_INFO,
|
||||
default => 0
|
||||
};
|
||||
$errName = match($errNo)
|
||||
{
|
||||
E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
|
||||
E_USER_ERROR => 'USER_ERROR',
|
||||
E_USER_WARNING, E_WARNING => 'WARNING',
|
||||
E_USER_NOTICE, E_NOTICE => 'NOTICE',
|
||||
default => 'UNKNOWN_ERROR' // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored
|
||||
};
|
||||
|
||||
if (DB::isConnected(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `post`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $errNo, $errFile, $errLine, CLI ? 'CLI' : substr($_SERVER['QUERY_STRING'] ?? '', 0, 250), empty($_POST) ? '' : http_build_query($_POST), User::$groups, $errStr
|
||||
);
|
||||
|
||||
if (CLI)
|
||||
CLI::write($errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine, $logLevel);
|
||||
else if (Cfg::get('DEBUG') >= $logLevel)
|
||||
Util::addNote($errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine, U_GROUP_EMPLOYEE, $logLevel);
|
||||
|
||||
return true;
|
||||
}, E_ALL);
|
||||
|
||||
// handle exceptions
|
||||
set_exception_handler(function (\Throwable $e) : void
|
||||
{
|
||||
if (DB::isConnected(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `post`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $e->getCode(), $e->getFile(), $e->getLine(), CLI ? 'CLI' : substr($_SERVER['QUERY_STRING'] ?? '', 0, 250), empty($_POST) ? '' : http_build_query($_POST), User::$groups, $e->getMessage()
|
||||
);
|
||||
|
||||
if (CLI)
|
||||
fwrite(STDERR, "\nException - ".$e->getMessage()."\n ".$e->getFile(). '('.$e->getLine().")\n".$e->getTraceAsString()."\n\n");
|
||||
else
|
||||
{
|
||||
Util::addNote('Exception - '.$e->getMessage().' @ '.$e->getFile(). ':'.$e->getLine()."\n".$e->getTraceAsString(), U_GROUP_EMPLOYEE, LOG_LEVEL_ERROR);
|
||||
(new GenericPage())->error();
|
||||
}
|
||||
});
|
||||
|
||||
// handle fatal errors
|
||||
register_shutdown_function(function() : void
|
||||
{
|
||||
if ($e = error_get_last())
|
||||
{
|
||||
if (DB::isConnected(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `post`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $e['type'], $e['file'], $e['line'], CLI ? 'CLI' : substr($_SERVER['QUERY_STRING'] ?? '', 0, 250), empty($_POST) ? '' : http_build_query($_POST), User::$groups, $e['message']
|
||||
);
|
||||
|
||||
if (CLI)
|
||||
fwrite(STDERR, "\nFatal Error - ".$e['message'].' @ '.$e['file']. ':'.$e['line']."\n\n");
|
||||
else if (User::isInGroup(U_GROUP_EMPLOYEE))
|
||||
echo "\nFatal Error - ".$e['message'].' @ '.$e['file']. ':'.$e['line']."\n\n";
|
||||
}
|
||||
});
|
||||
|
||||
// Setup DB-Wrapper
|
||||
if (file_exists('config/config.php'))
|
||||
require_once 'config/config.php';
|
||||
else
|
||||
$AoWoWconf = [];
|
||||
|
||||
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
|
||||
define('OS_WIN', substr(PHP_OS, 0, 3) == 'WIN');
|
||||
|
||||
|
||||
require_once 'includes/defines.php';
|
||||
require_once 'includes/libs/DbSimple/Generic.php'; // Libraray: http://en.dklab.ru/lib/DbSimple (using variant: https://github.com/ivan1986/DbSimple/tree/master)
|
||||
require_once 'includes/utilities.php'; // helper functions
|
||||
require_once 'includes/game.php'; // game related data & functions
|
||||
require_once 'includes/profiler.class.php';
|
||||
require_once 'includes/user.class.php';
|
||||
require_once 'includes/markup.class.php'; // manipulate markup text
|
||||
require_once 'includes/database.class.php'; // wrap DBSimple
|
||||
require_once 'includes/community.class.php'; // handle comments, screenshots and videos
|
||||
require_once 'includes/loot.class.php'; // build lv-tabs containing loot-information
|
||||
require_once 'includes/smartAI.class.php';
|
||||
require_once 'localization/lang.class.php';
|
||||
require_once 'pages/genericPage.class.php';
|
||||
|
||||
|
||||
// autoload List-classes, associated filters and pages
|
||||
spl_autoload_register(function ($class) {
|
||||
$class = strtolower(str_replace('ListFilter', 'List', $class));
|
||||
|
||||
if (class_exists($class)) // already registered
|
||||
return;
|
||||
|
||||
if (preg_match('/[^\w]/i', $class)) // name should contain only letters
|
||||
return;
|
||||
|
||||
if (stripos($class, 'list'))
|
||||
{
|
||||
require_once 'includes/basetype.class.php';
|
||||
|
||||
$cl = strtr($class, ['list' => '']);
|
||||
if ($cl == 'remoteprofile' || $cl == 'localprofile')
|
||||
$cl = 'profile';
|
||||
if ($cl == 'remotearenateam' || $cl == 'localarenateam')
|
||||
$cl = 'arenateam';
|
||||
if ($cl == 'remoteguild' || $cl == 'localguild')
|
||||
$cl = 'guild';
|
||||
|
||||
if (file_exists('includes/types/'.$cl.'.class.php'))
|
||||
require_once 'includes/types/'.$cl.'.class.php';
|
||||
else
|
||||
throw new Exception('could not register type class: '.$cl);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (stripos($class, 'ajax') === 0)
|
||||
{
|
||||
require_once 'includes/ajaxHandler.class.php'; // handles ajax and jsonp requests
|
||||
|
||||
if (file_exists('includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php'))
|
||||
require_once 'includes/ajaxHandler/'.strtr($class, ['ajax' => '']).'.class.php';
|
||||
else
|
||||
throw new Exception('could not register ajaxHandler class: '.$class);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (file_exists('pages/'.strtr($class, ['page' => '']).'.php'))
|
||||
require_once 'pages/'.strtr($class, ['page' => '']).'.php';
|
||||
});
|
||||
|
||||
|
||||
// Setup DB-Wrapper
|
||||
if (!empty($AoWoWconf['aowow']['db']))
|
||||
DB::load(DB_AOWOW, $AoWoWconf['aowow']);
|
||||
|
||||
@@ -91,188 +252,63 @@ if (!empty($AoWoWconf['characters']))
|
||||
if (!empty($charDBInfo))
|
||||
DB::load(DB_CHARACTERS . $realm, $charDBInfo);
|
||||
|
||||
$AoWoWconf = null; // empty auths
|
||||
|
||||
// load config to constants
|
||||
function loadConfig(bool $noPHP = false) : void
|
||||
{
|
||||
$sets = DB::isConnectable(DB_AOWOW) ? DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value`, `flags` FROM ?_config') : [];
|
||||
foreach ($sets as $k => $v)
|
||||
{
|
||||
$php = $v['flags'] & CON_FLAG_PHP;
|
||||
if ($php && $noPHP)
|
||||
continue;
|
||||
|
||||
// this should not have been possible
|
||||
if (!strlen($v['value']) && !($v['flags'] & CON_FLAG_TYPE_STRING) && !$php)
|
||||
{
|
||||
trigger_error('Aowow config value CFG_'.strtoupper($k).' is empty - config will not be used!', E_USER_ERROR);
|
||||
continue;
|
||||
}
|
||||
// for CLI and early errors in erb context
|
||||
Lang::load(Locale::EN);
|
||||
|
||||
if ($v['flags'] & CON_FLAG_TYPE_INT)
|
||||
$val = intVal($v['value']);
|
||||
else if ($v['flags'] & CON_FLAG_TYPE_FLOAT)
|
||||
$val = floatVal($v['value']);
|
||||
else if ($v['flags'] & CON_FLAG_TYPE_BOOL)
|
||||
$val = (bool)$v['value'];
|
||||
else if ($v['flags'] & CON_FLAG_TYPE_STRING)
|
||||
$val = preg_replace("/[\p{C}]/ui", '', $v['value']);
|
||||
else if ($php)
|
||||
{
|
||||
trigger_error('PHP config value '.strtolower($k).' has no type set - config will not be used!', E_USER_ERROR);
|
||||
continue;
|
||||
}
|
||||
else // if (!$php)
|
||||
{
|
||||
trigger_error('Aowow config value CFG_'.strtoupper($k).' has no type set - value forced to 0!', E_USER_ERROR);
|
||||
$val = 0;
|
||||
}
|
||||
|
||||
if ($php)
|
||||
ini_set(strtolower($k), $val);
|
||||
else if (!defined('CFG_'.strtoupper($k)))
|
||||
define('CFG_'.strtoupper($k), $val);
|
||||
}
|
||||
}
|
||||
loadConfig();
|
||||
|
||||
// handle non-fatal errors and notices
|
||||
error_reporting(!empty($AoWoWconf['aowow']) && CFG_DEBUG ? E_AOWOW : 0);
|
||||
set_error_handler(function($errNo, $errStr, $errFile, $errLine)
|
||||
{
|
||||
$errName = 'unknown error'; // errors not in this list can not be handled by set_error_handler (as per documentation) or are ignored
|
||||
$uGroup = U_GROUP_EMPLOYEE;
|
||||
|
||||
if ($errNo == E_WARNING) // 0x0002
|
||||
$errName = 'E_WARNING';
|
||||
else if ($errNo == E_PARSE) // 0x0004
|
||||
$errName = 'E_PARSE';
|
||||
else if ($errNo == E_NOTICE) // 0x0008
|
||||
$errName = 'E_NOTICE';
|
||||
else if ($errNo == E_USER_ERROR) // 0x0100
|
||||
$errName = 'E_USER_ERROR';
|
||||
else if ($errNo == E_USER_WARNING) // 0x0200
|
||||
$errName = 'E_USER_WARNING';
|
||||
else if ($errNo == E_USER_NOTICE) // 0x0400
|
||||
{
|
||||
$errName = 'E_USER_NOTICE';
|
||||
$uGroup = U_GROUP_STAFF;
|
||||
}
|
||||
else if ($errNo == E_RECOVERABLE_ERROR) // 0x1000
|
||||
$errName = 'E_RECOVERABLE_ERROR';
|
||||
|
||||
Util::addNote($uGroup, $errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine);
|
||||
if (CLI)
|
||||
CLI::write($errName.' - '.$errStr.' @ '.$errFile. ':'.$errLine, $errNo & 0x40A ? CLI::LOG_WARN : CLI::LOG_ERROR);
|
||||
|
||||
if (DB::isConnectable(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $errNo, $errFile, $errLine, CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $errStr
|
||||
);
|
||||
|
||||
return true;
|
||||
}, E_AOWOW);
|
||||
|
||||
// handle exceptions
|
||||
set_exception_handler(function ($ex)
|
||||
{
|
||||
Util::addNote(U_GROUP_EMPLOYEE, 'Exception - '.$ex->getMessage().' @ '.$ex->getFile(). ':'.$ex->getLine()."\n".$ex->getTraceAsString());
|
||||
|
||||
if (DB::isConnectable(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $ex->getCode(), $ex->getFile(), $ex->getLine(), CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $ex->getMessage()
|
||||
);
|
||||
|
||||
if (!CLI)
|
||||
(new GenericPage())->error();
|
||||
else
|
||||
echo 'Exception - '.$ex->getMessage()."\n ".$ex->getFile(). '('.$ex->getLine().")\n".$ex->getTraceAsString()."\n";
|
||||
});
|
||||
|
||||
// handle fatal errors
|
||||
register_shutdown_function(function()
|
||||
{
|
||||
if (($e = error_get_last()) && $e['type'] & (E_ERROR | E_COMPILE_ERROR | E_CORE_ERROR))
|
||||
{
|
||||
Util::addNote(U_GROUP_EMPLOYEE, 'Fatal Error - '.$e['message'].' @ '.$e['file']. ':'.$e['line']);
|
||||
|
||||
if (DB::isConnectable(DB_AOWOW))
|
||||
DB::Aowow()->query('INSERT INTO ?_errors (`date`, `version`, `phpError`, `file`, `line`, `query`, `userGroups`, `message`) VALUES (UNIX_TIMESTAMP(), ?d, ?d, ?, ?d, ?, ?d, ?) ON DUPLICATE KEY UPDATE `date` = UNIX_TIMESTAMP()',
|
||||
AOWOW_REVISION, $e['type'], $e['file'], $e['line'], CLI ? 'CLI' : ($_SERVER['QUERY_STRING'] ?? ''), User::$groups, $e['message']
|
||||
);
|
||||
|
||||
if (CLI)
|
||||
echo 'Fatal Error - '.$e['message'].' @ '.$e['file']. ':'.$e['line']."\n";
|
||||
|
||||
// cant generate a page for web view :(
|
||||
die();
|
||||
}
|
||||
});
|
||||
|
||||
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || (!empty($AoWoWconf['aowow']) && CFG_FORCE_SSL);
|
||||
if (defined('CFG_STATIC_HOST')) // points js to images & scripts
|
||||
define('STATIC_URL', ($secure ? 'https://' : 'http://').CFG_STATIC_HOST);
|
||||
|
||||
if (defined('CFG_SITE_HOST')) // points js to executable files
|
||||
define('HOST_URL', ($secure ? 'https://' : 'http://').CFG_SITE_HOST);
|
||||
// load config from DB
|
||||
Cfg::load();
|
||||
|
||||
|
||||
if (!CLI)
|
||||
{
|
||||
if (!defined('CFG_SITE_HOST') || !defined('CFG_STATIC_HOST'))
|
||||
die('error: SITE_HOST or STATIC_HOST not configured');
|
||||
// not displaying the brb gnomes as static_host is missing, but eh...
|
||||
if (!DB::isConnected(DB_AOWOW) || !DB::isConnected(DB_WORLD) || !Cfg::get('HOST_URL') || !Cfg::get('STATIC_URL'))
|
||||
(new GenericPage())->maintenance();
|
||||
|
||||
// Setup Session
|
||||
if (CFG_SESSION_CACHE_DIR && Util::writeDir(CFG_SESSION_CACHE_DIR))
|
||||
session_save_path(getcwd().'/'.CFG_SESSION_CACHE_DIR);
|
||||
$cacheDir = Cfg::get('SESSION_CACHE_DIR');
|
||||
if ($cacheDir && Util::writeDir($cacheDir))
|
||||
session_save_path(getcwd().'/'.$cacheDir);
|
||||
|
||||
session_set_cookie_params(15 * YEAR, '/', '', $secure, true);
|
||||
session_set_cookie_params(15 * YEAR, '/', '', (($_SERVER['HTTPS'] ?? 'off') != 'off') || Cfg::get('FORCE_SSL'), true);
|
||||
session_cache_limiter('private');
|
||||
if (!session_start())
|
||||
{
|
||||
trigger_error('failed to start session', E_USER_ERROR);
|
||||
exit;
|
||||
(new GenericPage())->error();
|
||||
}
|
||||
|
||||
if (!empty($AoWoWconf['aowow']) && User::init())
|
||||
if (User::init())
|
||||
User::save(); // save user-variables in session
|
||||
|
||||
// set up some logging (~10 queries will execute before we init the user and load the config)
|
||||
if (CFG_DEBUG && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
|
||||
// hard override locale for this call (should this be here..?)
|
||||
if (isset($_GET['locale']) && ($loc = Locale::tryFrom((int)$_GET['locale'])))
|
||||
Lang::load($loc);
|
||||
else
|
||||
Lang::load(User::$preferedLoc);
|
||||
|
||||
// set up some logging (some queries will execute before we init the user and load the config)
|
||||
if (Cfg::get('DEBUG') >= LOG_LEVEL_INFO && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
|
||||
{
|
||||
DB::Aowow()->setLogger(['DB', 'logger']);
|
||||
DB::World()->setLogger(['DB', 'logger']);
|
||||
DB::Aowow()->setLogger(DB::profiler(...));
|
||||
DB::World()->setLogger(DB::profiler(...));
|
||||
if (DB::isConnected(DB_AUTH))
|
||||
DB::Auth()->setLogger(['DB', 'logger']);
|
||||
DB::Auth()->setLogger(DB::profiler(...));
|
||||
|
||||
if (!empty($AoWoWconf['characters']))
|
||||
foreach ($AoWoWconf['characters'] as $idx => $__)
|
||||
if (DB::isConnected(DB_CHARACTERS . $idx))
|
||||
DB::Characters($idx)->setLogger(['DB', 'logger']);
|
||||
}
|
||||
|
||||
// hard-override locale for this call (should this be here..?)
|
||||
// all strings attached..
|
||||
if (!empty($AoWoWconf['aowow']))
|
||||
{
|
||||
if (isset($_GET['locale']) && (int)$_GET['locale'] <= MAX_LOCALES && (int)$_GET['locale'] >= 0)
|
||||
if (CFG_LOCALES & (1 << $_GET['locale']))
|
||||
User::useLocale($_GET['locale']);
|
||||
|
||||
Lang::load(User::$localeString);
|
||||
DB::Characters($idx)->setLogger(DB::profiler(...));
|
||||
}
|
||||
|
||||
// parse page-parameters .. sanitize before use!
|
||||
$str = explode('&', mb_strtolower($_SERVER['QUERY_STRING'] ?? ''), 2)[0];
|
||||
$str = explode('&', $_SERVER['QUERY_STRING'] ?? '', 2)[0];
|
||||
$_ = explode('=', $str, 2);
|
||||
$pageCall = $_[0];
|
||||
$pageCall = mb_strtolower($_[0]);
|
||||
$pageParam = $_[1] ?? '';
|
||||
|
||||
Util::$wowheadLink = 'http://'.Util::$subDomains[User::$localeId].'.wowhead.com/'.$str;
|
||||
}
|
||||
else if (!empty($AoWoWconf['aowow']))
|
||||
Lang::load('enus');
|
||||
|
||||
$AoWoWconf = null; // empty auths
|
||||
|
||||
?>
|
||||
|
||||
@@ -93,6 +93,8 @@ require_once __DIR__ . '/CacherImpl.php';
|
||||
*/
|
||||
abstract class DbSimple_Database extends DbSimple_LastError
|
||||
{
|
||||
private $attributes;
|
||||
|
||||
/**
|
||||
* Public methods.
|
||||
*/
|
||||
@@ -1142,7 +1144,7 @@ abstract class DbSimple_Database extends DbSimple_LastError
|
||||
$len = 0;
|
||||
$values = array();
|
||||
foreach ($rows[0] as $k=>$v) {
|
||||
$len += strlen($v);
|
||||
$len += strlen($v ?? '');
|
||||
if ($len > $this->MAX_LOG_ROW_LEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class DbSimple_Mysqli extends DbSimple_Database
|
||||
{
|
||||
var $link;
|
||||
|
||||
private $_lastQuery;
|
||||
|
||||
/**
|
||||
* constructor(string $dsn)
|
||||
* Connect to MySQL server.
|
||||
@@ -163,6 +165,17 @@ class DbSimple_Mysqli extends DbSimple_Database
|
||||
$result = mysqli_query($this->link, $queryMain[0]);
|
||||
if ($result === false)
|
||||
return $this->_setDbError($queryMain[0]);
|
||||
|
||||
if ($this->link->warning_count) {
|
||||
if ($warn = $this->link->query("SHOW WARNINGS")) {
|
||||
while ($warnRow = $warn->fetch_row())
|
||||
if ($warnRow[0] === 'Warning')
|
||||
$this->_setLastError(-$warnRow[1], $warnRow[2], $queryMain[0]);
|
||||
|
||||
$warn->close();
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_object($result)) {
|
||||
if (preg_match('/^\s* INSERT \s+/six', $queryMain[0]))
|
||||
{
|
||||
|
||||
179
includes/locale.class.php
Normal file
179
includes/locale.class.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
enum Locale : int
|
||||
{ // unused by TC WoW self
|
||||
case EN = 0; // enGB, enUS
|
||||
case KR = 1; // koKR ?
|
||||
case FR = 2; // frFR
|
||||
case DE = 3; // deDE
|
||||
case CN = 4; // zhCN, enCN
|
||||
case TW = 5; // zhTW, enTW x
|
||||
case ES = 6; // esES
|
||||
case MX = 7; // esMX x
|
||||
case RU = 8; // ruRU
|
||||
case JP = 9; // jaJP x x x
|
||||
case PT = 10; // ptPT, ptBR x ?
|
||||
case IT = 11; // itIT x x x
|
||||
|
||||
private const MASK_ALL = 0b000101011101; // technically supported locales
|
||||
|
||||
public function domain() : string // our subdomain / locale in web context
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::EN => 'en',
|
||||
self::KR => 'ko',
|
||||
self::FR => 'fr',
|
||||
self::DE => 'de',
|
||||
self::CN => 'cn',
|
||||
self::TW => 'tw',
|
||||
self::ES => 'es',
|
||||
self::MX => 'mx',
|
||||
self::RU => 'ru',
|
||||
self::JP => 'jp',
|
||||
self::PT => 'pt',
|
||||
self::IT => 'it'
|
||||
};
|
||||
}
|
||||
|
||||
public function json() : string // internal usage / json string
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::EN => 'enus',
|
||||
self::KR => 'kokr',
|
||||
self::FR => 'frfr',
|
||||
self::DE => 'dede',
|
||||
self::CN => 'zhcn',
|
||||
self::TW => 'zhtw',
|
||||
self::ES => 'eses',
|
||||
self::MX => 'esmx',
|
||||
self::RU => 'ruru',
|
||||
self::JP => 'jajp',
|
||||
self::PT => 'ptpt',
|
||||
self::IT => 'itit'
|
||||
};
|
||||
}
|
||||
|
||||
public function title() : string // localized language name
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::EN => 'English',
|
||||
self::KR => '한국어',
|
||||
self::FR => 'Français',
|
||||
self::DE => 'Deutsch',
|
||||
self::CN => '简体中文',
|
||||
self::TW => '繁體中文',
|
||||
self::ES => 'Español',
|
||||
self::MX => 'Mexicano',
|
||||
self::RU => 'Русский',
|
||||
self::JP => '日本語',
|
||||
self::PT => 'Português',
|
||||
self::IT => 'Italiano'
|
||||
};
|
||||
}
|
||||
|
||||
public function gameDirs() : array // setup data source / wow client locale code
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::EN => ['enGB', 'enUS', ''],
|
||||
self::KR => ['koKR'],
|
||||
self::FR => ['frFR'],
|
||||
self::DE => ['deDE'],
|
||||
self::CN => ['zhCN', 'enCN'],
|
||||
self::TW => ['zhTW', 'enTW'],
|
||||
self::ES => ['esES'],
|
||||
self::MX => ['esMX'],
|
||||
self::RU => ['ruRU'],
|
||||
self::JP => ['jaJP'],
|
||||
self::PT => ['ptPT', 'ptBR'],
|
||||
self::IT => ['itIT']
|
||||
};
|
||||
}
|
||||
|
||||
public function httpCode() : array // HTTP_ACCEPT_LANGUAGE
|
||||
{
|
||||
return match ($this)
|
||||
{
|
||||
self::EN => ['en', 'en-au', 'en-bz', 'en-ca', 'en-ie', 'en-jm', 'en-nz', 'en-ph', 'en-za', 'en-tt', 'en-gb', 'en-us', 'en-zw'],
|
||||
self::KR => ['ko', 'ko-kp', 'ko-kr'],
|
||||
self::FR => ['fr', 'fr-be', 'fr-ca', 'fr-fr', 'fr-lu', 'fr-mc', 'fr-ch'],
|
||||
self::DE => ['de', 'de-at', 'de-de', 'de-li', 'de-lu', 'de-ch'],
|
||||
self::CN => ['zh', 'zh-hk', 'zh-cn', 'zh-sg'],
|
||||
self::TW => ['tw', 'zh-tw'],
|
||||
self::ES => ['es', 'es-ar', 'es-bo', 'es-cl', 'es-co', 'es-cr', 'es-do', 'es-ec', 'es-sv', 'es-gt', 'es-hn', 'es-ni', 'es-pa', 'es-py', 'es-pe', 'es-pr', 'es-es', 'es-uy', 'es-ve'],
|
||||
self::MX => ['mx', 'es-mx'],
|
||||
self::RU => ['ru', 'ru-mo'],
|
||||
self::JP => ['ja'],
|
||||
self::PT => ['pt', 'pt-br'],
|
||||
self::IT => ['it', 'it-ch']
|
||||
};
|
||||
}
|
||||
|
||||
public function isLogographic() : bool
|
||||
{
|
||||
return $this == Locale::CN || $this == Locale::TW || $this == Locale::KR;
|
||||
}
|
||||
|
||||
public function validate() : ?self
|
||||
{
|
||||
return ($this->maskBit() & self::MASK_ALL & (Cfg::get('LOCALES') ?: 0xFFFF)) ? $this : null;
|
||||
}
|
||||
|
||||
public function maskBit() : int
|
||||
{
|
||||
return (1 << $this->value);
|
||||
}
|
||||
|
||||
public static function tryFromDomain(string $str) : ?self
|
||||
{
|
||||
foreach (self::cases() as $l)
|
||||
if ($l->validate() && $str == $l->domain())
|
||||
return $l;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function tryFromHttpAcceptLanguage(string $httpAccept) : ?self
|
||||
{
|
||||
if (!$httpAccept)
|
||||
return null;
|
||||
|
||||
$available = [];
|
||||
|
||||
// e.g.: de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||
foreach (explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $loc)
|
||||
if (preg_match('/([a-z\-]+)(?:\s*;\s*q\s*=\s*([\.\d]+))?/ui', $loc, $m, PREG_UNMATCHED_AS_NULL))
|
||||
$available[Util::lower($m[1])] = floatVal($m[2] ?? 1); // no quality set: assume 100%
|
||||
|
||||
arsort($available, SORT_NUMERIC); // highest quality on top
|
||||
|
||||
foreach ($available as $code => $_)
|
||||
foreach (self::cases() as $l)
|
||||
if ($l->validate() && in_array($code, $l->httpCode()))
|
||||
return $l;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getFallback() : self
|
||||
{
|
||||
foreach (Locale::cases() as $l)
|
||||
if ($l->validate())
|
||||
return $l;
|
||||
|
||||
// wow, you really fucked up your config mate!
|
||||
trigger_error('Locale::getFallback - there are no valid locales', E_USER_ERROR);
|
||||
return self::EN;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,148 +0,0 @@
|
||||
<?php
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
/*
|
||||
this is just a skeleton for now
|
||||
at some point i'll need to (at least rudamentary) parse
|
||||
back and forth between markup and html
|
||||
*/
|
||||
|
||||
class Markup
|
||||
{
|
||||
private $text = '';
|
||||
private $jsGlobals = [];
|
||||
|
||||
private static $dbTagPattern = '/(?<!\\\\)\[(npc|object|item|itemset|quest|spell|zone|faction|pet|achievement|statistic|title|event|class|race|skill|currency|emote|enchantment|money|sound|icondb)=(-?\d+)[^\]]*\]/i';
|
||||
|
||||
public function __construct($text)
|
||||
{
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
public function parseGlobalsFromText(&$jsg = [])
|
||||
{
|
||||
if (preg_match_all(self::$dbTagPattern, $this->text, $matches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach ($matches as $match)
|
||||
{
|
||||
if ($match[1] == 'statistic')
|
||||
$match[1] = 'achievement';
|
||||
else if ($match[1] == 'icondb')
|
||||
$match[1] = 'icon';
|
||||
|
||||
if ($match[1] == 'money')
|
||||
{
|
||||
if (stripos($match[0], 'items'))
|
||||
{
|
||||
if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i+=2)
|
||||
$this->jsGlobals[Type::ITEM][$sm[$i]] = $sm[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (stripos($match[0], 'currency'))
|
||||
{
|
||||
if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i+=2)
|
||||
$this->jsGlobals[Type::CURRENCY][$sm[$i]] = $sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
|
||||
$this->jsGlobals[$type][$match[2]] = $match[2];
|
||||
}
|
||||
}
|
||||
|
||||
Util::mergeJsGlobals($jsg, $this->jsGlobals);
|
||||
|
||||
return $this->jsGlobals;
|
||||
}
|
||||
|
||||
public function stripTags($globals = [])
|
||||
{
|
||||
// since this is an article the db-tags should already be parsed
|
||||
$text = preg_replace_callback(self::$dbTagPattern, function ($match) use ($globals) {
|
||||
if ($match[1] == 'statistic')
|
||||
$match[1] = 'achievement';
|
||||
else if ($match[1] == 'icondb')
|
||||
$match[1] = 'icon';
|
||||
else if ($match[1] == 'money')
|
||||
{
|
||||
$moneys = [];
|
||||
if (stripos($match[0], 'items'))
|
||||
{
|
||||
if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i += 2)
|
||||
{
|
||||
if (!empty($globals[Type::ITEM][1][$sm[$i]]))
|
||||
$moneys[] = $globals[Type::ITEM][1][$sm[$i]]['name'];
|
||||
else
|
||||
$moneys[] = Util::ucFirst(Lang::game('item')).' #'.$sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stripos($match[0], 'currency'))
|
||||
{
|
||||
if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch))
|
||||
{
|
||||
$sm = explode(',', $submatch[1]);
|
||||
for ($i = 0; $i < count($sm); $i += 2)
|
||||
{
|
||||
if (!empty($globals[Type::CURRENCY][1][$sm[$i]]))
|
||||
$moneys[] = $globals[Type::CURRENCY][1][$sm[$i]]['name'];
|
||||
else
|
||||
$moneys[] = Util::ucFirst(Lang::game('curency')).' #'.$sm[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Lang::concat($moneys);
|
||||
}
|
||||
if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1]))
|
||||
{
|
||||
if (!empty($globals[$type][1][$match[2]]))
|
||||
return $globals[$type][1][$match[2]]['name'];
|
||||
else
|
||||
return Util::ucFirst(Lang::game($match[1])).' #'.$match[2];
|
||||
}
|
||||
|
||||
trigger_error('Markup::stripTags() - encountered unhandled db-tag: '.var_export($match));
|
||||
return '';
|
||||
}, $this->text);
|
||||
|
||||
$text = str_replace('[br]', "\n", $text);
|
||||
$stripped = '';
|
||||
|
||||
$inTag = false;
|
||||
for ($i = 0; $i < strlen($text); $i++)
|
||||
{
|
||||
if ($text[$i] == '[')
|
||||
$inTag = true;
|
||||
if (!$inTag)
|
||||
$stripped .= $text[$i];
|
||||
if ($text[$i] == ']')
|
||||
$inTag = false;
|
||||
}
|
||||
|
||||
return $stripped;
|
||||
}
|
||||
|
||||
public function fromHtml()
|
||||
{
|
||||
}
|
||||
|
||||
public function toHtml()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
355
includes/setup/cli.class.php
Normal file
355
includes/setup/cli.class.php
Normal file
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
abstract class CLI
|
||||
{
|
||||
private const CHR_BELL = 7;
|
||||
private const CHR_BACK = 8;
|
||||
private const CHR_TAB = 9;
|
||||
private const CHR_LF = 10;
|
||||
private const CHR_CR = 13;
|
||||
private const CHR_ESC = 27;
|
||||
private const CHR_BACKSPACE = 127;
|
||||
|
||||
public const LOG_NONE = -1;
|
||||
public const LOG_BLANK = 0;
|
||||
public const LOG_ERROR = LOG_LEVEL_ERROR;
|
||||
public const LOG_WARN = LOG_LEVEL_WARN;
|
||||
public const LOG_INFO = LOG_LEVEL_INFO;
|
||||
public const LOG_OK = 4;
|
||||
|
||||
private static $logHandle = null;
|
||||
private static $hasReadline = null;
|
||||
|
||||
private static $overwriteLast = false;
|
||||
|
||||
/********************/
|
||||
/* formatted output */
|
||||
/********************/
|
||||
|
||||
public static function writeTable(array $out, bool $timestamp = false, bool $headless = false) : void
|
||||
{
|
||||
if (!$out)
|
||||
return;
|
||||
|
||||
$pads = [];
|
||||
$nCols = 0;
|
||||
|
||||
foreach ($out as $i => $row)
|
||||
{
|
||||
if (!is_array($out[0]))
|
||||
{
|
||||
unset($out[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$nCols = max($nCols, count($row));
|
||||
|
||||
for ($j = 0; $j < $nCols; $j++)
|
||||
$pads[$j] = max($pads[$j] ?? 0, mb_strlen(self::purgeEscapes($row[$j] ?? '')));
|
||||
}
|
||||
|
||||
foreach ($out as $i => $row)
|
||||
{
|
||||
for ($j = 0; $j < $nCols; $j++)
|
||||
{
|
||||
if (!isset($row[$j]))
|
||||
break;
|
||||
|
||||
$len = ($pads[$j] - mb_strlen(self::purgeEscapes($row[$j])));
|
||||
for ($k = 0; $k < $len; $k++) // can't use str_pad(). it counts invisible chars.
|
||||
$row[$j] .= ' ';
|
||||
}
|
||||
|
||||
if ($i || $headless)
|
||||
self::write(' '.implode(' ' . self::tblDelim(' ') . ' ', $row), self::LOG_NONE, $timestamp);
|
||||
else
|
||||
self::write(self::tblHead(' '.implode(' ', $row)), self::LOG_NONE, $timestamp);
|
||||
}
|
||||
|
||||
if (!$headless)
|
||||
self::write(self::tblHead(str_pad('', array_sum($pads) + count($pads) * 3 - 2)), self::LOG_NONE, $timestamp);
|
||||
|
||||
self::write();
|
||||
}
|
||||
|
||||
|
||||
/***********/
|
||||
/* logging */
|
||||
/***********/
|
||||
|
||||
public static function initLogFile(string $file = '') : void
|
||||
{
|
||||
if (!$file)
|
||||
return;
|
||||
|
||||
$file = self::nicePath($file);
|
||||
if (!file_exists($file))
|
||||
self::$logHandle = fopen($file, 'w');
|
||||
else
|
||||
{
|
||||
$logFileParts = pathinfo($file);
|
||||
|
||||
$i = 1;
|
||||
while (file_exists($logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : '')))
|
||||
$i++;
|
||||
|
||||
$file = $logFileParts['dirname'].'/'.$logFileParts['filename'].$i.(isset($logFileParts['extension']) ? '.'.$logFileParts['extension'] : '');
|
||||
self::$logHandle = fopen($file, 'w');
|
||||
}
|
||||
}
|
||||
|
||||
private static function tblHead(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[1;48;5;236m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
private static function tblDelim(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[48;5;236m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function grey(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[90m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function red(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[31m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function green(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[32m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function yellow(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[33m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function blue(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[36m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function bold(string $str) : string
|
||||
{
|
||||
return CLI_HAS_E ? "\e[1m".$str."\e[0m" : $str;
|
||||
}
|
||||
|
||||
public static function write(string $txt = '', int $lvl = self::LOG_BLANK, bool $timestamp = true, bool $tmpRow = false) : void
|
||||
{
|
||||
$msg = '';
|
||||
if ($txt)
|
||||
{
|
||||
if ($timestamp)
|
||||
$msg = str_pad(date('H:i:s'), 10);
|
||||
|
||||
switch ($lvl)
|
||||
{
|
||||
case self::LOG_ERROR: // red critical error
|
||||
$msg .= '['.self::red('ERR').'] ';
|
||||
break;
|
||||
case self::LOG_WARN: // yellow notice
|
||||
$msg .= '['.self::yellow('WARN').'] ';
|
||||
break;
|
||||
case self::LOG_OK: // green success
|
||||
$msg .= '['.self::green('OK').'] ';
|
||||
break;
|
||||
case self::LOG_INFO: // blue info
|
||||
$msg .= '['.self::blue('INFO').'] ';
|
||||
break;
|
||||
case self::LOG_BLANK:
|
||||
$msg .= ' ';
|
||||
break;
|
||||
}
|
||||
|
||||
$msg .= $txt;
|
||||
}
|
||||
|
||||
// https://shiroyasha.svbtle.com/escape-sequences-a-quick-guide-1#movement_1
|
||||
$msg = (self::$overwriteLast && CLI_HAS_E ? "\e[1G\e[0K" : "\n") . $msg;
|
||||
self::$overwriteLast = $tmpRow;
|
||||
|
||||
fwrite($lvl == self::LOG_ERROR ? STDERR : STDOUT, $msg);
|
||||
|
||||
if (self::$logHandle) // remove control sequences from log
|
||||
fwrite(self::$logHandle, self::purgeEscapes($msg));
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
private static function purgeEscapes(string $msg) : string
|
||||
{
|
||||
return preg_replace(["/\e\[[\d;]+[mK]/", "/\e\[\d+G/"], ['', "\n"], $msg);
|
||||
}
|
||||
|
||||
public static function nicePath(string $fileOrPath, string ...$pathParts) : string
|
||||
{
|
||||
$path = '';
|
||||
|
||||
if ($pathParts)
|
||||
{
|
||||
foreach ($pathParts as &$pp)
|
||||
$pp = trim($pp);
|
||||
|
||||
$path .= implode(DIRECTORY_SEPARATOR, $pathParts);
|
||||
}
|
||||
|
||||
$path .= ($path ? DIRECTORY_SEPARATOR : '').trim($fileOrPath);
|
||||
|
||||
// remove double quotes (from erroneous user input), single quotes are
|
||||
// valid chars for filenames and removing those mutilates several wow icons
|
||||
$path = str_replace('"', '', $path);
|
||||
|
||||
if (!$path) // empty strings given. (faulty dbc data?)
|
||||
return '';
|
||||
|
||||
if (DIRECTORY_SEPARATOR == '/') // *nix
|
||||
{
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = preg_replace('/\/+/i', '/', $path);
|
||||
}
|
||||
else if (DIRECTORY_SEPARATOR == '\\') // win
|
||||
{
|
||||
$path = str_replace('/', '\\', $path);
|
||||
$path = preg_replace('/\\\\+/i', '\\', $path);
|
||||
}
|
||||
else
|
||||
self::write('Dafuq! Your directory separator is "'.DIRECTORY_SEPARATOR.'". Please report this!', self::LOG_ERROR);
|
||||
|
||||
// resolve *nix home shorthand
|
||||
if (!OS_WIN)
|
||||
{
|
||||
if (preg_match('/^~(\w+)\/.*/i', $path, $m))
|
||||
$path = '/home/'.substr($path, 1);
|
||||
else if (substr($path, 0, 2) == '~/')
|
||||
$path = getenv('HOME').substr($path, 1);
|
||||
else if ($path[0] == DIRECTORY_SEPARATOR && substr($path, 0, 6) != '/home/')
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
||||
/**************/
|
||||
/* read input */
|
||||
/**************/
|
||||
|
||||
/*
|
||||
since the CLI on WIN ist not interactive, the following things have to be considered
|
||||
you do not receive keystrokes but whole strings upon pressing <Enter> (wich also appends a \r)
|
||||
as such <ESC> and probably other control chars can not be registered
|
||||
this also means, you can't hide input at all, least process it
|
||||
*/
|
||||
|
||||
public static function read(array $fields, ?array &$userInput = []) : bool
|
||||
{
|
||||
// first time set
|
||||
if (self::$hasReadline === null)
|
||||
self::$hasReadline = function_exists('readline_callback_handler_install');
|
||||
|
||||
// prevent default output if able
|
||||
if (self::$hasReadline)
|
||||
readline_callback_handler_install('', function() { });
|
||||
|
||||
if (!STDIN)
|
||||
return false;
|
||||
|
||||
stream_set_blocking(STDIN, false);
|
||||
|
||||
// pad default values onto $fields
|
||||
array_walk($fields, function(&$val, $_, $pad) { $val += $pad; }, ['', false, false, '']);
|
||||
|
||||
foreach ($fields as $name => [$desc, $isHidden, $singleChar, $validPattern])
|
||||
{
|
||||
$charBuff = '';
|
||||
|
||||
if ($desc)
|
||||
fwrite(STDOUT, "\n".$desc.": ");
|
||||
|
||||
while (true) {
|
||||
if (feof(STDIN))
|
||||
return false;
|
||||
|
||||
$r = [STDIN];
|
||||
$w = $e = null;
|
||||
$n = stream_select($r, $w, $e, 200000);
|
||||
|
||||
if (!$n || !in_array(STDIN, $r))
|
||||
continue;
|
||||
|
||||
// stream_get_contents is always blocking under WIN - fgets should work similary as php always receives a terminated line of text
|
||||
$chars = str_split(OS_WIN ? fgets(STDIN) : stream_get_contents(STDIN));
|
||||
$ordinals = array_map('ord', $chars);
|
||||
|
||||
if ($ordinals[0] == self::CHR_ESC)
|
||||
{
|
||||
if (count($ordinals) == 1)
|
||||
{
|
||||
fwrite(STDOUT, chr(self::CHR_BELL));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($chars as $idx => $char)
|
||||
{
|
||||
$keyId = $ordinals[$idx];
|
||||
|
||||
// skip char if horizontal tab or \r if followed by \n
|
||||
if ($keyId == self::CHR_TAB || ($keyId == self::CHR_CR && ($ordinals[$idx + 1] ?? '') == self::CHR_LF))
|
||||
continue;
|
||||
|
||||
if ($keyId == self::CHR_BACKSPACE)
|
||||
{
|
||||
if (!$charBuff)
|
||||
continue 2;
|
||||
|
||||
$charBuff = mb_substr($charBuff, 0, -1);
|
||||
if (!$isHidden && self::$hasReadline)
|
||||
fwrite(STDOUT, chr(self::CHR_BACK)." ".chr(self::CHR_BACK));
|
||||
}
|
||||
// standalone \n or \r
|
||||
else if ($keyId == self::CHR_LF || $keyId == self::CHR_CR)
|
||||
{
|
||||
$userInput[$name] = $charBuff;
|
||||
break 2;
|
||||
}
|
||||
else if (!$validPattern || preg_match($validPattern, $char))
|
||||
{
|
||||
$charBuff .= $char;
|
||||
if (!$isHidden && self::$hasReadline)
|
||||
fwrite(STDOUT, $char);
|
||||
|
||||
if ($singleChar && self::$hasReadline)
|
||||
{
|
||||
$userInput[$name] = $charBuff;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwrite(STDOUT, chr(self::CHR_BELL));
|
||||
|
||||
foreach ($userInput as $ui)
|
||||
if (strlen($ui))
|
||||
return true;
|
||||
|
||||
$userInput = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
39
includes/setup/timer.class.php
Normal file
39
includes/setup/timer.class.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class Timer
|
||||
{
|
||||
private $t_cur = 0;
|
||||
private $t_new = 0;
|
||||
private $intv = 0;
|
||||
|
||||
public function __construct(int $intervall)
|
||||
{
|
||||
$this->intv = $intervall / 1000; // in msec
|
||||
$this->t_cur = microtime(true);
|
||||
}
|
||||
|
||||
public function update() : bool
|
||||
{
|
||||
$this->t_new = microtime(true);
|
||||
if ($this->t_new > $this->t_cur + $this->intv)
|
||||
{
|
||||
$this->t_cur = $this->t_cur + $this->intv;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reset() : void
|
||||
{
|
||||
$this->t_cur = microtime(true) - $this->intv;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
define('AOWOW_REVISION', 33);
|
||||
define('CLI', PHP_SAPI === 'cli');
|
||||
|
||||
|
||||
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'/*, 'gmp'*/];
|
||||
$error = '';
|
||||
foreach ($reqExt as $r)
|
||||
if (!extension_loaded($r))
|
||||
$error .= 'Required Extension <b>'.$r."</b> was not found. Please check if it should exist, using \"<i>php -m</i>\"\n\n";
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.4.0') < 0)
|
||||
$error .= 'PHP Version <b>7.4</b> or higher required! Your version is <b>'.PHP_VERSION."</b>.\nCore functions are unavailable!\n";
|
||||
|
||||
if ($error)
|
||||
{
|
||||
echo CLI ? strip_tags($error) : $error;
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
// include all necessities, set up basics
|
||||
require_once 'includes/kernel.php';
|
||||
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
260
includes/type.class.php
Normal file
260
includes/type.class.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
abstract class Type
|
||||
{
|
||||
public const NPC = 1;
|
||||
public const OBJECT = 2;
|
||||
public const ITEM = 3;
|
||||
public const ITEMSET = 4;
|
||||
public const QUEST = 5;
|
||||
public const SPELL = 6;
|
||||
public const ZONE = 7;
|
||||
public const FACTION = 8;
|
||||
public const PET = 9;
|
||||
public const ACHIEVEMENT = 10;
|
||||
public const TITLE = 11;
|
||||
public const WORLDEVENT = 12;
|
||||
public const CHR_CLASS = 13;
|
||||
public const CHR_RACE = 14;
|
||||
public const SKILL = 15;
|
||||
public const STATISTIC = 16;
|
||||
public const CURRENCY = 17;
|
||||
// PROJECT = 18;
|
||||
public const SOUND = 19;
|
||||
// BUILDING = 20;
|
||||
// FOLLOWER = 21;
|
||||
// MISSION_ABILITY = 22;
|
||||
// MISSION = 23;
|
||||
// SHIP = 25;
|
||||
// THREAT = 26;
|
||||
// RESOURCE = 27;
|
||||
// CHAMPION = 28;
|
||||
public const ICON = 29;
|
||||
// ORDER_ADVANCEMENT = 30;
|
||||
// FOLLOWER_ALLIANCE = 31;
|
||||
// FOLLOWER_HORDE = 32;
|
||||
// SHIP_ALLIANCE = 33;
|
||||
// SHIP_HORDE = 34;
|
||||
// CHAMPION_ALLIANCE = 35;
|
||||
// CHAMPION_HORDE = 36;
|
||||
// TRANSMOG_ITEM = 37;
|
||||
// BFA_CHAMPION = 38;
|
||||
// BFA_CHAMPION_ALLIANCE = 39;
|
||||
// AFFIX = 40;
|
||||
// BFA_CHAMPION_HORDE = 41;
|
||||
// AZERITE_ESSENCE_POWER = 42;
|
||||
// AZERITE_ESSENCE = 43;
|
||||
// STORYLINE = 44;
|
||||
// ADVENTURE_COMBATANT_ABILITY = 46;
|
||||
// ENCOUNTER = 47;
|
||||
// COVENANT = 48;
|
||||
// SOULBIND = 49;
|
||||
// DI_ITEM = 50;
|
||||
// GATHERER_SCREENSHOT = 91;
|
||||
// GATHERER_GUIDE_IMAGE = 98;
|
||||
public const PROFILE = 100;
|
||||
// our own things
|
||||
public const GUILD = 101;
|
||||
// TRANSMOG_SET = 101; // future conflict inc.
|
||||
public const ARENA_TEAM = 102;
|
||||
// OUTFIT = 110;
|
||||
// GEAR_SET = 111;
|
||||
// GATHERER_LISTVIEW = 158;
|
||||
// GATHERER_SURVEY_COVENANTS = 161;
|
||||
// NEWS_POST = 162;
|
||||
// BATTLE_PET_ABILITY = 200;
|
||||
public const GUIDE = 300; // should have been 100, but conflicts with old version of Profile/List
|
||||
public const USER = 500;
|
||||
public const EMOTE = 501;
|
||||
public const ENCHANTMENT = 502;
|
||||
public const AREATRIGGER = 503;
|
||||
public const MAIL = 504;
|
||||
// Blizzard API things
|
||||
// MOUNT = -1000;
|
||||
// RECIPE = -1001;
|
||||
// BATTLE_PET = -1002;
|
||||
|
||||
public const FLAG_NONE = 0x0;
|
||||
public const FLAG_RANDOM_SEARCHABLE = 0x1;
|
||||
public const FLAG_FILTRABLE = 0x2;
|
||||
public const FLAG_DB_TYPE = 0x4;
|
||||
public const FLAG_HAS_ICON = 0x8;
|
||||
|
||||
public const IDX_LIST_OBJ = 0;
|
||||
public const IDX_FILE_STR = 1;
|
||||
public const IDX_JSG_TPL = 2;
|
||||
public const IDX_FLAGS = 3;
|
||||
|
||||
private static array $data = array(
|
||||
self::NPC => [__NAMESPACE__ . '\CreatureList', 'npc', 'g_npcs', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::OBJECT => [__NAMESPACE__ . '\GameObjectList', 'object', 'g_objects', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::ITEM => [__NAMESPACE__ . '\ItemList', 'item', 'g_items', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::ITEMSET => [__NAMESPACE__ . '\ItemsetList', 'itemset', 'g_itemsets', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::QUEST => [__NAMESPACE__ . '\QuestList', 'quest', 'g_quests', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::SPELL => [__NAMESPACE__ . '\SpellList', 'spell', 'g_spells', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::ZONE => [__NAMESPACE__ . '\ZoneList', 'zone', 'g_gatheredzones', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::FACTION => [__NAMESPACE__ . '\FactionList', 'faction', 'g_factions', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::PET => [__NAMESPACE__ . '\PetList', 'pet', 'g_pets', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::ACHIEVEMENT => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::TITLE => [__NAMESPACE__ . '\TitleList', 'title', 'g_titles', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::WORLDEVENT => [__NAMESPACE__ . '\WorldEventList', 'event', 'g_holidays', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::CHR_CLASS => [__NAMESPACE__ . '\CharClassList', 'class', 'g_classes', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::CHR_RACE => [__NAMESPACE__ . '\CharRaceList', 'race', 'g_races', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::SKILL => [__NAMESPACE__ . '\SkillList', 'skill', 'g_skills', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::STATISTIC => [__NAMESPACE__ . '\AchievementList', 'achievement', 'g_achievements', self::FLAG_NONE], // alias for achievements; exists only for Markup
|
||||
self::CURRENCY => [__NAMESPACE__ . '\CurrencyList', 'currency', 'g_gatheredcurrencies', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::SOUND => [__NAMESPACE__ . '\SoundList', 'sound', 'g_sounds', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::ICON => [__NAMESPACE__ . '\IconList', 'icon', 'g_icons', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON],
|
||||
self::GUIDE => [__NAMESPACE__ . '\GuideList', 'guide', '', self::FLAG_NONE],
|
||||
self::PROFILE => [__NAMESPACE__ . '\ProfileList', 'profile', '', self::FLAG_FILTRABLE], // x - not known in javascript
|
||||
self::GUILD => [__NAMESPACE__ . '\GuildList', 'guild', '', self::FLAG_FILTRABLE], // x
|
||||
self::ARENA_TEAM => [__NAMESPACE__ . '\ArenaTeamList', 'arena-team', '', self::FLAG_FILTRABLE], // x
|
||||
self::USER => [__NAMESPACE__ . '\UserList', 'user', 'g_users', self::FLAG_NONE], // x
|
||||
self::EMOTE => [__NAMESPACE__ . '\EmoteList', 'emote', 'g_emotes', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE],
|
||||
self::ENCHANTMENT => [__NAMESPACE__ . '\EnchantmentList', 'enchantment', 'g_enchantments', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::AREATRIGGER => [__NAMESPACE__ . '\AreatriggerList', 'areatrigger', '', self::FLAG_FILTRABLE | self::FLAG_DB_TYPE],
|
||||
self::MAIL => [__NAMESPACE__ . '\MailList', 'mail', '', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE]
|
||||
);
|
||||
|
||||
|
||||
/********************/
|
||||
/* Field Operations */
|
||||
/********************/
|
||||
|
||||
public static function newList(int $type, array $conditions = []) : ?BaseType
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return null;
|
||||
|
||||
return new (self::$data[$type][self::IDX_LIST_OBJ])($conditions);
|
||||
}
|
||||
|
||||
public static function newFilter(string $fileStr, array|string $data, array $opts = []) : ?Filter
|
||||
{
|
||||
$x = self::getFileStringsFor(self::FLAG_FILTRABLE);
|
||||
if ($type = array_search($fileStr, $x))
|
||||
return new (self::$data[$type][self::IDX_LIST_OBJ].'Filter')($data, $opts);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function validateIds(int $type, int|array $ids) : array
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return [];
|
||||
|
||||
if (!(self::$data[$type][self::IDX_FLAGS] & self::FLAG_DB_TYPE))
|
||||
return [];
|
||||
|
||||
return DB::Aowow()->selectCol('SELECT `id` FROM ?# WHERE `id` IN (?a)', self::$data[$type][self::IDX_LIST_OBJ]::$dataTable, (array)$ids);
|
||||
}
|
||||
|
||||
public static function hasIcon(int $type) : bool
|
||||
{
|
||||
return self::exists($type) && self::$data[$type][self::IDX_FLAGS] & self::FLAG_HAS_ICON;
|
||||
}
|
||||
|
||||
public static function isRandomSearchable(int $type) : bool
|
||||
{
|
||||
return self::exists($type) && self::$data[$type][self::IDX_FLAGS] & self::FLAG_RANDOM_SEARCHABLE;
|
||||
}
|
||||
|
||||
public static function getFileString(int $type) : string
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return '';
|
||||
|
||||
return self::$data[$type][self::IDX_FILE_STR];
|
||||
}
|
||||
|
||||
public static function getJSGlobalString(int $type) : string
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return '';
|
||||
|
||||
return self::$data[$type][self::IDX_JSG_TPL];
|
||||
}
|
||||
|
||||
public static function getJSGlobalTemplate(int $type) : array
|
||||
{
|
||||
if (!self::exists($type) || !self::$data[$type][self::IDX_JSG_TPL])
|
||||
return [];
|
||||
|
||||
// [key, [data], [extraData]]
|
||||
return [self::$data[$type][self::IDX_JSG_TPL], [], []];
|
||||
}
|
||||
|
||||
public static function checkClassAttrib(int $type, string $attr, ?int $attrVal = null) : bool
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return false;
|
||||
|
||||
return isset((self::$data[$type][self::IDX_LIST_OBJ])::$$attr) && ($attrVal === null || ((self::$data[$type][self::IDX_LIST_OBJ])::$$attr & $attrVal));
|
||||
}
|
||||
|
||||
public static function getClassAttrib(int $type, string $attr) : mixed
|
||||
{
|
||||
if (!self::exists($type))
|
||||
return null;
|
||||
|
||||
return (self::$data[$type][self::IDX_LIST_OBJ])::$$attr ?? null;
|
||||
}
|
||||
|
||||
public static function exists(int $type) : ?int
|
||||
{
|
||||
return !empty(self::$data[$type]) ? $type : null;
|
||||
}
|
||||
|
||||
public static function getIndexFrom(int $idx, string $match) : int
|
||||
{
|
||||
$i = array_search($match, array_column(self::$data, $idx));
|
||||
if ($i === false)
|
||||
return 0;
|
||||
|
||||
return array_keys(self::$data)[$i];
|
||||
}
|
||||
|
||||
|
||||
/*********************/
|
||||
/* Column Operations */
|
||||
/*********************/
|
||||
|
||||
public static function getClassesFor(int $flags = 0x0, string $attr = '', ?int $attrVal = null) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [$o, , , $f])
|
||||
if ($o && (!$flags || $flags & $f))
|
||||
if (!$attr || self::checkClassAttrib($k, $attr, $attrVal))
|
||||
$x[$k] = $o;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getFileStringsFor(int $flags = 0x0) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, $s, , $f])
|
||||
if ($s && (!$flags || $flags & $f))
|
||||
$x[$k] = $s;
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
public static function getJSGTemplatesFor(int $flags = 0x0) : array
|
||||
{
|
||||
$x = [];
|
||||
foreach (self::$data as $k => [, , $a, $f])
|
||||
if ($a && (!$flags || $flags & $f))
|
||||
$x[$k] = $a;
|
||||
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -25,7 +27,7 @@ class AchievementList extends BaseType
|
||||
todo: evaluate TC custom-data-tables: a*_criteria_data should be merged on installation
|
||||
*/
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
@@ -257,12 +259,15 @@ class AchievementList extends BaseType
|
||||
return $x;
|
||||
}
|
||||
|
||||
public function getSourceData()
|
||||
public function getSourceData(int $id = 0) : array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
if ($id && $id != $this->id)
|
||||
continue;
|
||||
|
||||
$data[$this->id] = array(
|
||||
"n" => $this->getField('name', true),
|
||||
"s" => $this->curTpl['faction'],
|
||||
@@ -278,11 +283,12 @@ class AchievementList extends BaseType
|
||||
|
||||
class AchievementListFilter extends Filter
|
||||
{
|
||||
|
||||
protected $enums = array(
|
||||
protected string $type = 'achievements';
|
||||
protected array $enums = array(
|
||||
4 => parent::ENUM_ZONE, // location
|
||||
11 => array(
|
||||
327 => 160, // Lunar Festival
|
||||
335 => 187, // Love is in the Air
|
||||
423 => 187, // Love is in the Air
|
||||
181 => 159, // Noblegarden
|
||||
201 => 163, // Children's Week
|
||||
341 => 161, // Midsummer Fire Festival
|
||||
@@ -292,8 +298,8 @@ class AchievementListFilter extends Filter
|
||||
141 => 156, // Feast of Winter Veil
|
||||
409 => -3456, // Day of the Dead
|
||||
398 => -3457, // Pirates' Day
|
||||
FILTER_ENUM_ANY => true,
|
||||
FILTER_ENUM_NONE => false,
|
||||
parent::ENUM_ANY => true,
|
||||
parent::ENUM_NONE => false,
|
||||
283 => -1, // valid events without achievements
|
||||
285 => -1, 353 => -1, 420 => -1,
|
||||
400 => -1, 284 => -1, 374 => -1,
|
||||
@@ -301,116 +307,100 @@ class AchievementListFilter extends Filter
|
||||
)
|
||||
);
|
||||
|
||||
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
|
||||
2 => [FILTER_CR_BOOLEAN, 'reward_loc0', true ], // givesreward
|
||||
3 => [FILTER_CR_STRING, 'reward', STR_LOCALIZED ], // rewardtext
|
||||
4 => [FILTER_CR_NYI_PH, null, 1, ], // location [enum]
|
||||
5 => [FILTER_CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_FIRST_SERIES, null], // first in series [yn]
|
||||
6 => [FILTER_CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_LAST_SERIES, null], // last in series [yn]
|
||||
7 => [FILTER_CR_BOOLEAN, 'chainId', ], // partseries
|
||||
9 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
10 => [FILTER_CR_STRING, 'ic.name', ], // icon
|
||||
11 => [FILTER_CR_CALLBACK, 'cbRelEvent', null, null], // related event [enum]
|
||||
14 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
15 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
16 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
18 => [FILTER_CR_STAFFFLAG, 'flags', ] // flags
|
||||
protected array $genericFilter = array(
|
||||
2 => [parent::CR_BOOLEAN, 'reward_loc0', true ], // givesreward
|
||||
3 => [parent::CR_STRING, 'reward', STR_LOCALIZED ], // rewardtext
|
||||
4 => [parent::CR_NYI_PH, null, 1, ], // location [enum]
|
||||
5 => [parent::CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_FIRST_SERIES, null], // first in series [yn]
|
||||
6 => [parent::CR_CALLBACK, 'cbSeries', ACHIEVEMENT_CU_LAST_SERIES, null], // last in series [yn]
|
||||
7 => [parent::CR_BOOLEAN, 'chainId', ], // partseries
|
||||
9 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
10 => [parent::CR_STRING, 'ic.name', ], // icon
|
||||
11 => [parent::CR_CALLBACK, 'cbRelEvent', null, null], // related event [enum]
|
||||
14 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
15 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
16 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
18 => [parent::CR_STAFFFLAG, 'flags', ] // flags
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_RANGE, [2, 18], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / description - only printable chars, no delimiter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // extended name search
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'si' => [FILTER_V_LIST, [1, 2, 3, -1, -2], false], // side
|
||||
'minpt' => [FILTER_V_RANGE, [1, 99], false], // required level min
|
||||
'maxpt' => [FILTER_V_RANGE, [1, 99], false] // required level max
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_RANGE, [2, 18], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 99999]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiters
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / description - only printable chars, no delimiter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // extended name search
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH, -SIDE_ALLIANCE, -SIDE_HORDE], false], // side
|
||||
'minpt' => [parent::V_RANGE, [1, 99], false], // required level min
|
||||
'maxpt' => [parent::V_RANGE, [1, 99], false] // required level max
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
// name ex: +description, +rewards
|
||||
if (isset($_v['na']))
|
||||
if ($_v['na'])
|
||||
{
|
||||
$_ = [];
|
||||
if (isset($_v['ex']) && $_v['ex'] == 'on')
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'reward_loc'.User::$localeId, 'description_loc'.User::$localeId]);
|
||||
if ($_v['ex'] == 'on')
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'reward_loc'.Lang::getLocale()->value, 'description_loc'.Lang::getLocale()->value]);
|
||||
else
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
|
||||
|
||||
if ($_)
|
||||
$parts[] = $_;
|
||||
}
|
||||
|
||||
// points min
|
||||
if (isset($_v['minpt']))
|
||||
if ($_v['minpt'])
|
||||
$parts[] = ['points', $_v['minpt'], '>='];
|
||||
|
||||
// points max
|
||||
if (isset($_v['maxpt']))
|
||||
if ($_v['maxpt'])
|
||||
$parts[] = ['points', $_v['maxpt'], '<='];
|
||||
|
||||
// faction (side)
|
||||
if (isset($_v['si']))
|
||||
if ($_v['si'])
|
||||
{
|
||||
switch ($_v['si'])
|
||||
$parts[] = match ($_v['si'])
|
||||
{
|
||||
case -1: // faction, exclusive both
|
||||
case -2:
|
||||
$parts[] = ['faction', -$_v['si']];
|
||||
break;
|
||||
case 1: // faction, inclusive both
|
||||
case 2:
|
||||
case 3: // both
|
||||
$parts[] = ['faction', $_v['si'], '&'];
|
||||
break;
|
||||
}
|
||||
-SIDE_ALLIANCE, // equals faction
|
||||
-SIDE_HORDE => ['faction', -$_v['si']],
|
||||
SIDE_ALLIANCE, // includes faction
|
||||
SIDE_HORDE,
|
||||
SIDE_BOTH => ['faction', $_v['si'], '&']
|
||||
};
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbRelEvent($cr, $value)
|
||||
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!isset($this->enums[$cr[0]][$cr[1]]))
|
||||
return false;
|
||||
if (!isset($this->enums[$cr][$crs]))
|
||||
return null;
|
||||
|
||||
$_ = $this->enums[$cr[0]][$cr[1]];
|
||||
$_ = $this->enums[$cr][$crs];
|
||||
if (is_int($_))
|
||||
return ($_ > 0) ? ['category', $_] : ['id', abs($_)];
|
||||
else
|
||||
{
|
||||
$ids = array_filter($this->enums[$cr[0]], function($x) { return is_int($x) && $x > 0; });
|
||||
$ids = array_filter($this->enums[$cr], fn($x) => is_int($x) && $x > 0);
|
||||
|
||||
return ['category', $ids, $_ ? null : '!'];
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbSeries($cr, $value)
|
||||
protected function cbSeries(int $cr, int $crs, string $crv, int $seriesFlag) : ?array
|
||||
{
|
||||
if ($this->int2Bool($cr[1]))
|
||||
return $cr[1] ? ['AND', ['chainId', 0, '!'], ['cuFlags', $value, '&']] : ['AND', ['chainId', 0, '!'], [['cuFlags', $value, '&'], 0]];
|
||||
if ($this->int2Bool($crs))
|
||||
return $crs ? ['AND', ['chainId', 0, '!'], ['cuFlags', $seriesFlag, '&']] : ['AND', ['chainId', 0, '!'], [['cuFlags', $seriesFlag, '&'], 0]];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -11,16 +13,17 @@ class AreaTriggerList extends BaseType
|
||||
public static $type = Type::AREATRIGGER;
|
||||
public static $brickFile = 'areatrigger';
|
||||
public static $dataTable = '?_areatrigger';
|
||||
public static $contribute = CONTRIBUTE_CO;
|
||||
|
||||
protected $queryBase = 'SELECT a.*, a.id AS ARRAY_KEY FROM ?_areatrigger a';
|
||||
protected $queryOpts = array(
|
||||
'a' => [['s']],
|
||||
's' => ['j' => ['?_spawns s ON s.type = 503 AND s.typeId = a.id', true], 's' => ', s.areaId']
|
||||
'a' => [['s']], // guid < 0 are teleporter targets, so exclude them here
|
||||
's' => ['j' => ['?_spawns s ON s.`type` = 503 AND s.`typeId` = a.`id` AND s.`guid` > 0', true], 's' => ', GROUP_CONCAT(s.`areaId`) AS "areaId"', 'g' => 'a.`id`']
|
||||
);
|
||||
|
||||
public function __construct($conditions)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
foreach ($this->iterate() as $id => &$_curTpl)
|
||||
if (!$_curTpl['name'])
|
||||
@@ -40,7 +43,7 @@ class AreaTriggerList extends BaseType
|
||||
);
|
||||
|
||||
if ($_ = $this->curTpl['areaId'])
|
||||
$data[$this->id]['location'] = [$_];
|
||||
$data[$this->id]['location'] = explode(',', $_);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -56,43 +59,33 @@ class AreaTriggerList extends BaseType
|
||||
|
||||
class AreaTriggerListFilter extends Filter
|
||||
{
|
||||
protected $genericFilter = array(
|
||||
2 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT] // id
|
||||
protected string $type = 'areatrigger';
|
||||
protected array $genericFilter = array(
|
||||
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT] // id
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_LIST, [2], true ], // criteria ids
|
||||
'crs' => [FILTER_V_RANGE, [1, 6], true ], // criteria operators
|
||||
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - all criteria are numeric here
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'ty' => [FILTER_V_RANGE, [0, 5], true ] // types
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_LIST, [2], true ], // criteria ids
|
||||
'crs' => [parent::V_RANGE, [1, 6], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - all criteria are numeric here
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'ty' => [parent::V_RANGE, [0, 5], true ] // types
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
// name [str]
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name']))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name']))
|
||||
$parts[] = $_;
|
||||
|
||||
// type [list]
|
||||
if (isset($_v['ty']))
|
||||
if ($_v['ty'])
|
||||
$parts[] = ['type', $_v['ty']];
|
||||
|
||||
return $parts;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -10,6 +12,8 @@ class ArenaTeamList extends BaseType
|
||||
|
||||
private $rankOrder = [];
|
||||
|
||||
public static $contribute = CONTRIBUTE_NONE;
|
||||
|
||||
public function getListviewData()
|
||||
{
|
||||
$data = [];
|
||||
@@ -42,76 +46,46 @@ class ArenaTeamList extends BaseType
|
||||
|
||||
class ArenaTeamListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = [];
|
||||
protected $genericFilter = [];
|
||||
use TrProfilerFilter;
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [FILTER_V_LIST, [1, 2], false], // side
|
||||
'sz' => [FILTER_V_LIST, [2, 3, 5], false], // tema size
|
||||
'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server
|
||||
protected string $type = 'arenateams';
|
||||
protected array $genericFilter = [];
|
||||
protected array $inputFields = array(
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [parent::V_LIST, [1, 2], false], // side
|
||||
'sz' => [parent::V_LIST, [2, 3, 5], false], // tema size
|
||||
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr) { }
|
||||
public array $extraOpts = [];
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = $this->fiData['v'];
|
||||
$_v = $this->values;
|
||||
|
||||
// region (rg), battlegroup (bg) and server (sv) are passed to ArenaTeamList as miscData and handled there
|
||||
|
||||
// name [str]
|
||||
if (!empty($_v['na']))
|
||||
if ($_ = $this->modularizeString(['at.name'], $_v['na'], !empty($_v['ex']) && $_v['ex'] == 'on'))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['at.name'], $_v['na'], $_v['ex'] == 'on'))
|
||||
$parts[] = $_;
|
||||
|
||||
// side [list]
|
||||
if (!empty($_v['si']))
|
||||
{
|
||||
if ($_v['si'] == 1)
|
||||
$parts[] = ['c.race', [1, 3, 4, 7, 11]];
|
||||
else if ($_v['si'] == 2)
|
||||
$parts[] = ['c.race', [2, 5, 6, 8, 10]];
|
||||
}
|
||||
if ($_v['si'] == SIDE_ALLIANCE)
|
||||
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
|
||||
else if ($_v['si'] == SIDE_HORDE)
|
||||
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
|
||||
|
||||
// size [int]
|
||||
if (!empty($_v['sz']))
|
||||
if ($_v['sz'])
|
||||
$parts[] = ['at.type', $_v['sz']];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbRegionCheck(&$v)
|
||||
{
|
||||
if (in_array($v, Util::$regions))
|
||||
{
|
||||
$this->parentCats[0] = $v; // directly redirect onto this region
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbServerCheck(&$v)
|
||||
{
|
||||
foreach (Profiler::getRealms() as $realm)
|
||||
if ($realm['name'] == $v)
|
||||
{
|
||||
$this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,13 +99,14 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
);
|
||||
|
||||
private $members = [];
|
||||
private $rankOrder = [];
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
// select DB by realm
|
||||
if (!$this->selectRealms($miscData))
|
||||
{
|
||||
trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING);
|
||||
trigger_error('RemoteArenaTeamList::__construct - cannot access any realm.', E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +129,7 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
foreach ($this->iterate() as $guid => &$curTpl)
|
||||
{
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
|
||||
// realm, rank
|
||||
$r = explode(':', $guid);
|
||||
@@ -167,7 +142,15 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('arena team "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
trigger_error('arena team #'.$guid.' belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// empty name
|
||||
if (!$curTpl['name'])
|
||||
{
|
||||
trigger_error('arena team #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
@@ -202,11 +185,14 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
);
|
||||
|
||||
// equalize subject distribution across realms
|
||||
$limit = CFG_SQL_LIMIT_DEFAULT;
|
||||
foreach ($conditions as $c)
|
||||
if (is_int($c))
|
||||
$limit = $c;
|
||||
|
||||
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
|
||||
if (!$limit) // int:0 means unlimited, so skip early
|
||||
return;
|
||||
|
||||
$total = array_sum($distrib);
|
||||
foreach ($distrib as &$d)
|
||||
$d = ceil($limit * $d / $total);
|
||||
@@ -228,8 +214,11 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
}
|
||||
}
|
||||
|
||||
public function initializeLocalEntries()
|
||||
public function initializeLocalEntries() : void
|
||||
{
|
||||
if (!$this->templates)
|
||||
return;
|
||||
|
||||
$profiles = [];
|
||||
// init members for tooltips
|
||||
foreach ($this->members as $realmId => $teams)
|
||||
@@ -238,7 +227,7 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
foreach ($teams as $team)
|
||||
$gladiators = array_merge($gladiators, array_keys($team));
|
||||
|
||||
$profiles[$realmId] = new RemoteProfileList(array(['c.guid', $gladiators], CFG_SQL_LIMIT_NONE), ['sv' => $realmId]);
|
||||
$profiles[$realmId] = new RemoteProfileList(array(['c.guid', $gladiators], Cfg::get('SQL_LIMIT_NONE')), ['sv' => $realmId]);
|
||||
|
||||
if (!$profiles[$realmId]->error)
|
||||
$profiles[$realmId]->initializeLocalEntries();
|
||||
@@ -260,7 +249,7 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
|
||||
// basic arena team data
|
||||
foreach (Util::createSqlBatchInsert($data) as $ins)
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team (?#) VALUES '.$ins, array_keys(reset($data)));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($data)));
|
||||
|
||||
// merge back local ids
|
||||
$localIds = DB::Aowow()->selectCol(
|
||||
@@ -282,15 +271,31 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
|
||||
$memberData = [];
|
||||
foreach ($teams as $teamId => $team)
|
||||
{
|
||||
$clearMembers = [];
|
||||
foreach ($team as $memberId => $member)
|
||||
{
|
||||
$clearMembers[] = $profiles[$realmId]->getEntry($realmId.':'.$memberId)['id'];
|
||||
$memberData[] = array(
|
||||
'arenaTeamId' => $localIds[$realmId.':'.$teamId],
|
||||
'profileId' => $profiles[$realmId]->getEntry($realmId.':'.$memberId)['id'],
|
||||
'captain' => $member[2]
|
||||
);
|
||||
}
|
||||
|
||||
// Delete members from other teams of the same type
|
||||
DB::Aowow()->query(
|
||||
'DELETE atm
|
||||
FROM ?_profiler_arena_team_member atm
|
||||
JOIN ?_profiler_arena_team at ON atm.`arenaTeamId` = at.`id` AND at.`type` = ?d
|
||||
WHERE atm.`profileId` IN (?a)',
|
||||
$data[$realmId.':'.$teamId]['type'] ?? 0,
|
||||
$clearMembers
|
||||
);
|
||||
}
|
||||
|
||||
foreach (Util::createSqlBatchInsert($memberData) as $ins)
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_arena_team_member (?#) VALUES '.$ins, array_keys(reset($memberData)));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_arena_team_member (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `profileId` = `profileId`', array_keys(reset($memberData)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,18 +304,50 @@ class RemoteArenaTeamList extends ArenaTeamList
|
||||
class LocalArenaTeamList extends ArenaTeamList
|
||||
{
|
||||
protected $queryBase = 'SELECT at.*, at.id AS ARRAY_KEY FROM ?_profiler_arena_team at';
|
||||
protected $queryOpts = array(
|
||||
'at' => [['atm', 'c'], 'g' => 'ARRAY_KEY', 'o' => 'rating DESC'],
|
||||
'atm' => ['j' => '?_profiler_arena_team_member atm ON atm.arenaTeamId = at.id'],
|
||||
'c' => ['j' => '?_profiler_profiles c ON c.id = atm.profileId', 's' => ', BIT_OR(IF(c.race IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS faction']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
// graft realm selection from miscData onto conditions
|
||||
if (isset($miscData['sv']))
|
||||
$realms = array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv']));
|
||||
|
||||
if (isset($miscData['rg']))
|
||||
$realms = array_filter($realms, fn($x) => $x['region'] == $miscData['rg']);
|
||||
|
||||
if (!$realms)
|
||||
{
|
||||
trigger_error('LocalArenaTeamList::__construct - cannot access any realm.', E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($conditions)
|
||||
{
|
||||
array_unshift($conditions, 'AND');
|
||||
$conditions = ['AND', ['realm', array_keys($realms)], $conditions];
|
||||
}
|
||||
else
|
||||
$conditions = [['realm', array_keys($realms)]];
|
||||
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
// post processing
|
||||
$members = DB::Aowow()->selectCol('SELECT *, arenaTeamId AS ARRAY_KEY, profileId AS ARRAY_KEY2 FROM ?_profiler_arena_team_member WHERE arenaTeamId IN (?a)', $this->getFoundIDs());
|
||||
$members = DB::Aowow()->select(
|
||||
'SELECT `arenaTeamId` AS ARRAY_KEY, p.`id` AS ARRAY_KEY2, p.`name` AS "0", p.`class` AS "1", atm.`captain` AS "2"
|
||||
FROM ?_profiler_arena_team_member atm
|
||||
JOIN ?_profiler_profiles p ON p.`id` = atm.`profileId`
|
||||
WHERE `arenaTeamId` IN (?a)',
|
||||
$this->getFoundIDs()
|
||||
);
|
||||
|
||||
foreach ($this->iterate() as $id => &$curTpl)
|
||||
{
|
||||
@@ -324,9 +361,9 @@ class LocalArenaTeamList extends ArenaTeamList
|
||||
}
|
||||
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
|
||||
$curTpl['members'] = $members[$id];
|
||||
$curTpl['members'] = array_values($members[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -12,9 +14,9 @@ class CharClassList extends BaseType
|
||||
|
||||
protected $queryBase = 'SELECT c.*, id AS ARRAY_KEY FROM ?_classes c';
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct($conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
foreach ($this->iterate() as $k => &$_curTpl)
|
||||
$_curTpl['skills'] = explode(' ', $_curTpl['skills']);
|
||||
@@ -56,7 +58,6 @@ class CharClassList extends BaseType
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function addRewardsToJScript(&$ref) { }
|
||||
public function renderTooltip() { }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -45,7 +47,6 @@ class CharRaceList extends BaseType
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function addRewardsToJScript(&$ref) { }
|
||||
public function renderTooltip() { }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -9,7 +11,7 @@ class CreatureList extends BaseType
|
||||
use spawnHelper;
|
||||
|
||||
public static $type = Type::NPC;
|
||||
public static $brickFile = 'creature';
|
||||
public static $brickFile = 'npc';
|
||||
public static $dataTable = '?_creature';
|
||||
|
||||
protected $queryBase = 'SELECT ct.*, ct.id AS ARRAY_KEY FROM ?_creature ct';
|
||||
@@ -24,7 +26,7 @@ class CreatureList extends BaseType
|
||||
's' => ['j' => ['?_spawns s ON s.type = 1 AND s.typeId = ct.id', true]]
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
@@ -257,238 +259,227 @@ class CreatureList extends BaseType
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getSourceData()
|
||||
public function getSourceData(int $id = 0) : array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
if ($id && $id != $this->id)
|
||||
continue;
|
||||
|
||||
$data[$this->id] = array(
|
||||
'n' => $this->getField('parentId') ? $this->getField('parent', true) : $this->getField('name', true),
|
||||
't' => Type::NPC,
|
||||
'ti' => $this->getField('parentId') ?: $this->id,
|
||||
// 'bd' => (int)($this->curTpl['cuFlags'] & NPC_CU_INSTANCE_BOSS || ($this->curTpl['typeFlags'] & 0x4 && $this->curTpl['rank']))
|
||||
// 'z' where am i spawned
|
||||
// 'dd' DungeonDifficulty requires 'z'
|
||||
'ti' => $this->getField('parentId') ?: $this->id
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function addRewardsToJScript(&$refs) { }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class CreatureListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = null;
|
||||
protected $enums = array(
|
||||
3 => array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92, 54, 946, 67, 1052, 749,
|
||||
47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809, 911, 890, 970, 169, 730, 72, 70, 932, 1156, 933,
|
||||
510, 1126, 1067, 1073, 509, 941, 1105, 990, 934, 935, 1094, 1119, 1124, 1064, 967, 1091, 59, 947, 81, 576, 922, 68, 1050, 1085, 889,
|
||||
589, 270)
|
||||
protected string $type = 'npcs';
|
||||
protected array $enums = array(
|
||||
3 => parent::ENUM_FACTION, // faction
|
||||
6 => parent::ENUM_ZONE, // foundin
|
||||
42 => parent::ENUM_FACTION, // increasesrepwith
|
||||
43 => parent::ENUM_FACTION, // decreasesrepwith
|
||||
38 => parent::ENUM_EVENT // relatedevent
|
||||
);
|
||||
|
||||
// cr => [type, field, misc, extraCol]
|
||||
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
|
||||
1 => [FILTER_CR_CALLBACK, 'cbHealthMana', 'healthMax', 'healthMin'], // health [num]
|
||||
2 => [FILTER_CR_CALLBACK, 'cbHealthMana', 'manaMin', 'manaMax' ], // mana [num]
|
||||
3 => [FILTER_CR_CALLBACK, 'cbFaction', null, null ], // faction [enum]
|
||||
5 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_REPAIRER ], // canrepair
|
||||
6 => [FILTER_CR_ENUM, 's.areaId', null ], // foundin
|
||||
7 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [enum]
|
||||
8 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [enum]
|
||||
9 => [FILTER_CR_BOOLEAN, 'lootId', ], // lootable
|
||||
10 => [FILTER_CR_CALLBACK, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn]
|
||||
11 => [FILTER_CR_BOOLEAN, 'pickpocketLootId', ], // pickpocketable
|
||||
12 => [FILTER_CR_CALLBACK, 'cbMoneyDrop', null, null ], // averagemoneydropped [op] [int]
|
||||
15 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_HERBLOOT, null ], // gatherable [yn]
|
||||
16 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_MININGLOOT, null ], // minable [yn]
|
||||
18 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_AUCTIONEER ], // auctioneer
|
||||
19 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BANKER ], // banker
|
||||
20 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_BATTLEMASTER ], // battlemaster
|
||||
21 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_FLIGHT_MASTER ], // flightmaster
|
||||
22 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // guildmaster
|
||||
23 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_INNKEEPER ], // innkeeper
|
||||
24 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_CLASS_TRAINER ], // talentunlearner
|
||||
25 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // tabardvendor
|
||||
27 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_STABLE_MASTER ], // stablemaster
|
||||
28 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_TRAINER ], // trainer
|
||||
29 => [FILTER_CR_FLAG, 'npcflag', NPC_FLAG_VENDOR ], // vendor
|
||||
31 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
32 => [FILTER_CR_FLAG, 'cuFlags', NPC_CU_INSTANCE_BOSS ], // instanceboss
|
||||
33 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
34 => [FILTER_CR_NYI_PH, 1, null ], // usemodel [str] - displayId -> id:creatureDisplayInfo.dbc/model -> id:cratureModelData.dbc/modelPath
|
||||
35 => [FILTER_CR_STRING, 'textureString' ], // useskin
|
||||
37 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id
|
||||
38 => [FILTER_CR_CALLBACK, 'cbRelEvent', null, null ], // relatedevent [enum]
|
||||
40 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
41 => [FILTER_CR_NYI_PH, 1, null ], // haslocation [yn] [staff]
|
||||
42 => [FILTER_CR_CALLBACK, 'cbReputation', '>', null ], // increasesrepwith [enum]
|
||||
43 => [FILTER_CR_CALLBACK, 'cbReputation', '<', null ], // decreasesrepwith [enum]
|
||||
44 => [FILTER_CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_ENGINEERLOOT, null ] // salvageable [yn]
|
||||
protected array $genericFilter = array(
|
||||
1 => [parent::CR_CALLBACK, 'cbHealthMana', 'healthMax', 'healthMin'], // health [num]
|
||||
2 => [parent::CR_CALLBACK, 'cbHealthMana', 'manaMin', 'manaMax' ], // mana [num]
|
||||
3 => [parent::CR_CALLBACK, 'cbFaction', null, null ], // faction [enum]
|
||||
5 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_REPAIRER ], // canrepair
|
||||
6 => [parent::CR_ENUM, 's.areaId', false, true ], // foundin
|
||||
7 => [parent::CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [enum]
|
||||
8 => [parent::CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [enum]
|
||||
9 => [parent::CR_BOOLEAN, 'lootId', ], // lootable
|
||||
10 => [parent::CR_CALLBACK, 'cbRegularSkinLoot', NPC_TYPEFLAG_SPECIALLOOT ], // skinnable [yn]
|
||||
11 => [parent::CR_BOOLEAN, 'pickpocketLootId', ], // pickpocketable
|
||||
12 => [parent::CR_CALLBACK, 'cbMoneyDrop', null, null ], // averagemoneydropped [op] [int]
|
||||
15 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_HERBALISM, null ], // gatherable [yn]
|
||||
16 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_MINING, null ], // minable [yn]
|
||||
18 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_AUCTIONEER ], // auctioneer
|
||||
19 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_BANKER ], // banker
|
||||
20 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_BATTLEMASTER ], // battlemaster
|
||||
21 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_FLIGHT_MASTER ], // flightmaster
|
||||
22 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // guildmaster
|
||||
23 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_INNKEEPER ], // innkeeper
|
||||
24 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_CLASS_TRAINER ], // talentunlearner
|
||||
25 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_GUILD_MASTER ], // tabardvendor
|
||||
27 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_STABLE_MASTER ], // stablemaster
|
||||
28 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_TRAINER ], // trainer
|
||||
29 => [parent::CR_FLAG, 'npcflag', NPC_FLAG_VENDOR ], // vendor
|
||||
31 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
32 => [parent::CR_FLAG, 'cuFlags', NPC_CU_INSTANCE_BOSS ], // instanceboss
|
||||
33 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
34 => [parent::CR_STRING, 'modelId', STR_MATCH_EXACT | STR_ALLOW_SHORT ], // usemodel [str] (wants int in string fmt <_<)
|
||||
35 => [parent::CR_STRING, 'textureString' ], // useskin [str]
|
||||
37 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true ], // id
|
||||
38 => [parent::CR_CALLBACK, 'cbRelEvent', null, null ], // relatedevent [enum]
|
||||
40 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
41 => [parent::CR_NYI_PH, 1, null ], // haslocation [yn] [staff]
|
||||
42 => [parent::CR_CALLBACK, 'cbReputation', '>', null ], // increasesrepwith [enum]
|
||||
43 => [parent::CR_CALLBACK, 'cbReputation', '<', null ], // decreasesrepwith [enum]
|
||||
44 => [parent::CR_CALLBACK, 'cbSpecialSkinLoot', NPC_TYPEFLAG_SKIN_WITH_ENGINEERING, null ] // salvageable [yn]
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_LIST, [[1, 3],[5, 12], 15, 16, [18, 25], [27, 29], [31, 35], 37, 38, [40, 44]], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 9999]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_REGEX, '/[\p{C}:;%\\\\]/ui', true ], // criteria values - only printable chars, no delimiter
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / subname - only printable chars, no delimiter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // also match subname
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'fa' => [FILTER_V_CALLBACK, 'cbPetFamily', true ], // pet family [list] - cat[0] == 1
|
||||
'minle' => [FILTER_V_RANGE, [1, 99], false], // min level [int]
|
||||
'maxle' => [FILTER_V_RANGE, [1, 99], false], // max level [int]
|
||||
'cl' => [FILTER_V_RANGE, [0, 4], true ], // classification [list]
|
||||
'ra' => [FILTER_V_LIST, [-1, 0, 1], false], // react alliance [int]
|
||||
'rh' => [FILTER_V_LIST, [-1, 0, 1], false] // react horde [int]
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_LIST, [[1, 3],[5, 12], 15, 16, [18, 25], [27, 29], [31, 35], 37, 38, [40, 44]], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 9999]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiter
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / subname - only printable chars, no delimiter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // also match subname
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'fa' => [parent::V_CALLBACK, 'cbPetFamily', true ], // pet family [list] - cat[0] == 1
|
||||
'minle' => [parent::V_RANGE, [1, 99], false], // min level [int]
|
||||
'maxle' => [parent::V_RANGE, [1, 99], false], // max level [int]
|
||||
'cl' => [parent::V_RANGE, [0, 4], true ], // classification [list]
|
||||
'ra' => [parent::V_LIST, [-1, 0, 1], false], // react alliance [int]
|
||||
'rh' => [parent::V_LIST, [-1, 0, 1], false] // react horde [int]
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
public array $extraOpts = [];
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
// name [str]
|
||||
if (isset($_v['na']))
|
||||
if ($_v['na'])
|
||||
{
|
||||
$_ = [];
|
||||
if (isset($_v['ex']) && $_v['ex'] == 'on')
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'subname_loc'.User::$localeId]);
|
||||
if ($_v['ex'] == 'on')
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'subname_loc'.Lang::getLocale()->value]);
|
||||
else
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
|
||||
|
||||
if ($_)
|
||||
$parts[] = $_;
|
||||
}
|
||||
|
||||
// pet family [list]
|
||||
if (isset($_v['fa']))
|
||||
if ($_v['fa'])
|
||||
$parts[] = ['family', $_v['fa']];
|
||||
|
||||
// creatureLevel min [int]
|
||||
if (isset($_v['minle']))
|
||||
if ($_v['minle'])
|
||||
$parts[] = ['minLevel', $_v['minle'], '>='];
|
||||
|
||||
// creatureLevel max [int]
|
||||
if (isset($_v['maxle']))
|
||||
if ($_v['maxle'])
|
||||
$parts[] = ['maxLevel', $_v['maxle'], '<='];
|
||||
|
||||
// classification [list]
|
||||
if (isset($_v['cl']))
|
||||
if ($_v['cl'])
|
||||
$parts[] = ['rank', $_v['cl']];
|
||||
|
||||
// react Alliance [int]
|
||||
if (isset($_v['ra']))
|
||||
if ($_v['ra'])
|
||||
$parts[] = ['ft.A', $_v['ra']];
|
||||
|
||||
// react Horde [int]
|
||||
if (isset($_v['rh']))
|
||||
if ($_v['rh'])
|
||||
$parts[] = ['ft.H', $_v['rh']];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbPetFamily(&$val)
|
||||
protected function cbPetFamily(string &$val) : bool
|
||||
{
|
||||
if (!$this->parentCats || $this->parentCats[0] != 1)
|
||||
return false;
|
||||
|
||||
if (!Util::checkNumeric($val, NUM_REQ_INT))
|
||||
if (!Util::checkNumeric($val, NUM_CAST_INT))
|
||||
return false;
|
||||
|
||||
$type = FILTER_V_LIST;
|
||||
$type = parent::V_LIST;
|
||||
$valid = [[1, 9], 11, 12, 20, 21, [24, 27], [30, 35], [37, 39], [41, 46]];
|
||||
|
||||
return $this->checkInput($type, $valid, $val);
|
||||
}
|
||||
|
||||
protected function cbRelEvent($cr)
|
||||
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT))
|
||||
return false;
|
||||
|
||||
if ($cr[1] == FILTER_ENUM_ANY)
|
||||
if ($crs == parent::ENUM_ANY)
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0');
|
||||
$cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
|
||||
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_creature WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $cGuids];
|
||||
|
||||
return [0];
|
||||
}
|
||||
else if ($cr[1] == FILTER_ENUM_NONE)
|
||||
else if ($crs == parent::ENUM_NONE)
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0');
|
||||
$cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
|
||||
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_creature WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $cGuids, '!'];
|
||||
|
||||
return [0];
|
||||
}
|
||||
else if ($cr[1])
|
||||
else if (in_array($crs, $this->enums[$cr]))
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId = ?d', $cr[1]);
|
||||
$cGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_creature WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` = ?d', $crs))
|
||||
if ($cGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM `game_event_creature` WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $cGuids];
|
||||
|
||||
return [0];
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbMoneyDrop($cr)
|
||||
protected function cbMoneyDrop(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
return ['AND', ['((minGold + maxGold) / 2)', $cr[2], $cr[1]]];
|
||||
return ['AND', ['((minGold + maxGold) / 2)', $crv, $crs]];
|
||||
}
|
||||
|
||||
protected function cbQuestRelation($cr, $field, $val)
|
||||
protected function cbQuestRelation(int $cr, int $crs, string $crv, $field, $val) : ?array
|
||||
{
|
||||
switch ($cr[1])
|
||||
switch ($crs)
|
||||
{
|
||||
case 1: // any
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!']];
|
||||
case 2: // alliance
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_HORDE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&']];
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_HORDE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&']];
|
||||
case 3: // horde
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']];
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']];
|
||||
case 4: // both
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
|
||||
return ['AND', ['qse.method', $val, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
|
||||
case 5: // none
|
||||
$this->extraOpts['ct']['h'][] = $field.' = 0';
|
||||
return [1];
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbHealthMana($cr, $minField, $maxField)
|
||||
protected function cbHealthMana(int $cr, int $crs, string $crv, $minField, $maxField) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
// remap OP for this special case
|
||||
switch ($cr[1])
|
||||
switch ($crs)
|
||||
{
|
||||
case '=': // min > max is totally possible
|
||||
$this->extraOpts['ct']['h'][] = $minField.' = '.$maxField.' AND '.$minField.' = '.$cr[2];
|
||||
$this->extraOpts['ct']['h'][] = $minField.' = '.$maxField.' AND '.$minField.' = '.$crv;
|
||||
break;
|
||||
case '>':
|
||||
case '>=':
|
||||
case '<':
|
||||
case '<=':
|
||||
$this->extraOpts['ct']['h'][] = 'IF('.$minField.' > '.$maxField.', '.$maxField.', '.$minField.') '.$cr[1].' '.$cr[2];
|
||||
$this->extraOpts['ct']['h'][] = 'IF('.$minField.' > '.$maxField.', '.$maxField.', '.$minField.') '.$crs.' '.$crv;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -496,54 +487,53 @@ class CreatureListFilter extends Filter
|
||||
return [1]; // always true, use post-filter
|
||||
}
|
||||
|
||||
protected function cbSpecialSkinLoot($cr, $typeFlag)
|
||||
protected function cbSpecialSkinLoot(int $cr, int $crs, string $crv, $typeFlag) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
|
||||
if ($cr[1])
|
||||
if ($crs)
|
||||
return ['AND', ['skinLootId', 0, '>'], ['typeFlags', $typeFlag, '&']];
|
||||
else
|
||||
return ['OR', ['skinLootId', 0], [['typeFlags', $typeFlag, '&'], 0]];
|
||||
}
|
||||
|
||||
protected function cbRegularSkinLoot($cr, $typeFlag)
|
||||
protected function cbRegularSkinLoot(int $cr, int $crs, string $crv, $typeFlag) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
if ($cr[1])
|
||||
if ($crs)
|
||||
return ['AND', ['skinLootId', 0, '>'], [['typeFlags', $typeFlag, '&'], 0]];
|
||||
else
|
||||
return ['OR', ['skinLootId', 0], ['typeFlags', $typeFlag, '&']];
|
||||
}
|
||||
|
||||
protected function cbReputation($cr, $op)
|
||||
protected function cbReputation(int $cr, int $crs, string $crv, $op) : ?array
|
||||
{
|
||||
if (!in_array($cr[1], $this->enums[3])) // reuse
|
||||
return false;
|
||||
if (!in_array($crs, $this->enums[$cr]))
|
||||
return null;
|
||||
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE id = ?d', $cr[1]))
|
||||
$this->formData['reputationCols'][] = [$cr[1], Util::localizedString($_, 'name')];
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE `id` = ?d', $crs))
|
||||
$this->fiReputationCols[] = [$crs, Util::localizedString($_, 'name')];
|
||||
|
||||
if ($cIds = DB::World()->selectCol('SELECT creature_id FROM creature_onkill_reputation WHERE (RewOnKillRepFaction1 = ?d AND RewOnKillRepValue1 '.$op.' 0) OR (RewOnKillRepFaction2 = ?d AND RewOnKillRepValue2 '.$op.' 0)', $cr[1], $cr[1]))
|
||||
if ($cIds = DB::World()->selectCol('SELECT `creature_id` FROM creature_onkill_reputation WHERE (`RewOnKillRepFaction1` = ?d AND `RewOnKillRepValue1` '.$op.' 0) OR (`RewOnKillRepFaction2` = ?d AND `RewOnKillRepValue2` '.$op.' 0)', $crs, $crs))
|
||||
return ['id', $cIds];
|
||||
else
|
||||
return [0];
|
||||
}
|
||||
|
||||
protected function cbFaction($cr)
|
||||
protected function cbFaction(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT))
|
||||
return false;
|
||||
|
||||
if (!in_array($cr[1], $this->enums[$cr[0]]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crs, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if (!in_array($crs, $this->enums[$cr]))
|
||||
return null;
|
||||
|
||||
$facTpls = [];
|
||||
$facs = new FactionList(array('OR', ['parentFactionId', $cr[1]], ['id', $cr[1]]));
|
||||
$facs = new FactionList(array('OR', ['parentFactionId', $crs], ['id', $crs]));
|
||||
foreach ($facs->iterate() as $__)
|
||||
$facTpls = array_merge($facTpls, $facs->getField('templateIds'));
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -16,13 +18,12 @@ class CurrencyList extends BaseType
|
||||
'ic' => ['j' => ['?_icons ic ON ic.id = c.iconId', true], 's' => ', ic.name AS iconString']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
foreach ($this->iterate() as &$_curTpl)
|
||||
if (!$_curTpl['iconString'])
|
||||
$_curTpl['iconString'] = 'inv_misc_questionmark';
|
||||
$_curTpl['iconString'] = $_curTpl['iconString'] ?: DEFAULT_ICON;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +52,9 @@ class CurrencyList extends BaseType
|
||||
{
|
||||
// todo (low): find out, why i did this in the first place
|
||||
if ($this->id == 104) // in case of honor commit sebbuku
|
||||
$icon = ['inv_bannerpvp_02', 'inv_bannerpvp_01']; // ['alliance', 'horde'];
|
||||
$icon = ['pvp-currency-alliance', 'pvp-currency-horde'];
|
||||
else if ($this->id == 103) // also arena-icon diffs from item-icon
|
||||
$icon = ['money_arena', 'money_arena'];
|
||||
$icon = ['pvp-arenapoints-icon', 'pvp-arenapoints-icon'];
|
||||
else
|
||||
$icon = [$this->curTpl['iconString'], $this->curTpl['iconString']];
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -12,9 +14,9 @@ class EmoteList extends BaseType
|
||||
|
||||
protected $queryBase = 'SELECT e.*, e.id AS ARRAY_KEY FROM ?_emotes e';
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as &$curTpl)
|
||||
@@ -33,7 +35,7 @@ class EmoteList extends BaseType
|
||||
$data[$this->id] = array(
|
||||
'id' => $this->curTpl['id'],
|
||||
'name' => $this->curTpl['cmd'],
|
||||
'preview' => $this->getField('self', true) ?: ($this->getField('noTarget', true) ?: $this->getField('target', true))
|
||||
'preview' => Util::parseHtmlText($this->getField('meToExt', true) ?: $this->getField('meToNone', true) ?: $this->getField('extToMe', true) ?: $this->getField('extToExt', true) ?: $this->getField('extToNone', true), true)
|
||||
);
|
||||
|
||||
// [nyi] sounds
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -12,9 +14,9 @@ class EnchantmentList extends BaseType
|
||||
public static $brickFile = 'enchantment';
|
||||
public static $dataTable = '?_itemenchantment';
|
||||
|
||||
private $jsonStats = [];
|
||||
private $relSpells = [];
|
||||
private $triggerIds = [];
|
||||
private array $jsonStats = [];
|
||||
private ?SpellList $relSpells = null;
|
||||
private array $triggerIds = [];
|
||||
|
||||
protected $queryBase = 'SELECT ie.*, ie.id AS ARRAY_KEY FROM ?_itemenchantment ie';
|
||||
protected $queryOpts = array( // 502 => Type::ENCHANTMENT
|
||||
@@ -22,9 +24,11 @@ class EnchantmentList extends BaseType
|
||||
'is' => ['j' => ['?_item_stats `is` ON `is`.`type` = 502 AND `is`.`typeId` = `ie`.`id`', true], 's' => ', `is`.*'],
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
$relSpells = [];
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as &$curTpl)
|
||||
@@ -35,45 +39,40 @@ class EnchantmentList extends BaseType
|
||||
if ($curTpl['object'.$i] <= 0)
|
||||
continue;
|
||||
|
||||
switch ($curTpl['type'.$i])
|
||||
switch ($curTpl['type'.$i]) // SPELL_TRIGGER_* just reused for wording
|
||||
{
|
||||
case 1:
|
||||
case ENCHANTMENT_TYPE_COMBAT_SPELL:
|
||||
$proc = -$this->getField('ppmRate') ?: ($this->getField('procChance') ?: $this->getField('amount'.$i));
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], 2, $curTpl['charges'], $proc];
|
||||
$this->relSpells[] = $curTpl['object'.$i];
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_HIT, $curTpl['charges'], $proc];
|
||||
$relSpells[] = $curTpl['object'.$i];
|
||||
break;
|
||||
case 3:
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], 1, $curTpl['charges'], 0];
|
||||
$this->relSpells[] = $curTpl['object'.$i];
|
||||
case ENCHANTMENT_TYPE_EQUIP_SPELL:
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_EQUIP, $curTpl['charges'], 0];
|
||||
$relSpells[] = $curTpl['object'.$i];
|
||||
break;
|
||||
case 7:
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], 0, $curTpl['charges'], 0];
|
||||
$this->relSpells[] = $curTpl['object'.$i];
|
||||
case ENCHANTMENT_TYPE_USE_SPELL:
|
||||
$curTpl['spells'][$i] = [$curTpl['object'.$i], SPELL_TRIGGER_USE, $curTpl['charges'], 0];
|
||||
$relSpells[] = $curTpl['object'.$i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// floats are fetched as string from db :<
|
||||
$curTpl['dmg'] = floatVal($curTpl['dmg']);
|
||||
$curTpl['dps'] = floatVal($curTpl['dps']);
|
||||
|
||||
// remove zero-stats
|
||||
foreach (Game::$itemMods as $str)
|
||||
if ($curTpl[$str] == 0) // empty(0.0f) => true .. yeah, sure
|
||||
unset($curTpl[$str]);
|
||||
|
||||
if ($curTpl['dps'] == 0)
|
||||
unset($curTpl['dps']);
|
||||
// issue with scaling stats enchantments
|
||||
// stats are stored as NOT NULL to be usable by the search filters and such become indistinguishable from scaling enchantments that _actually_ use the value 0
|
||||
// so filter the stats container and if it is empty, rebuild from self. .. there are no mixed scaling/static enchantments, right!?
|
||||
$this->jsonStats[$this->id] = (new StatsContainer())->fromJson($curTpl, true)->filter();
|
||||
if (!count($this->jsonStats[$this->id]))
|
||||
$this->jsonStats[$this->id]->fromEnchantment($curTpl);
|
||||
}
|
||||
|
||||
if ($this->relSpells)
|
||||
$this->relSpells = new SpellList(array(['id', $this->relSpells]));
|
||||
if ($relSpells)
|
||||
$this->relSpells = new SpellList(array(['id', $relSpells]));
|
||||
}
|
||||
|
||||
// use if you JUST need the name
|
||||
public static function getName($id)
|
||||
{
|
||||
$n = DB::Aowow()->SelectRow('SELECT name_loc0, name_loc2, name_loc3, name_loc4, name_loc6, name_loc8 FROM ?_itemenchantment WHERE id = ?d', $id );
|
||||
$n = DB::Aowow()->SelectRow('SELECT `name_loc0`, `name_loc2`, `name_loc3`, `name_loc4`, `name_loc6`, `name_loc8` FROM ?_itemenchantment WHERE `id` = ?d', $id );
|
||||
return Util::localizedString($n, 'name');
|
||||
}
|
||||
// end static use
|
||||
@@ -99,107 +98,39 @@ class EnchantmentList extends BaseType
|
||||
if ($this->curTpl['requiredLevel'] > 0)
|
||||
$data[$this->id]['reqlevel'] = $this->curTpl['requiredLevel'];
|
||||
|
||||
foreach ($this->curTpl['spells'] as $s)
|
||||
foreach ($this->curTpl['spells'] as [$spellId, $trigger, $charges, $procChance])
|
||||
{
|
||||
// enchant is procing or onUse
|
||||
if ($s[1] == 2 || $s[1] == 0)
|
||||
$data[$this->id]['spells'][$s[0]] = $s[2];
|
||||
// spell is procing
|
||||
else if ($this->relSpells && $this->relSpells->getEntry($s[0]) && ($_ = $this->relSpells->canTriggerSpell()))
|
||||
$trgSpell = 0;
|
||||
if ($this->relSpells && $this->relSpells->getEntry($spellId) && ($_ = $this->relSpells->canTriggerSpell()))
|
||||
{
|
||||
foreach ($_ as $idx)
|
||||
{
|
||||
$this->triggerIds[] = $this->relSpells->getField('effect'.$idx.'TriggerSpell');
|
||||
$data[$this->id]['spells'][$this->relSpells->getField('effect'.$idx.'TriggerSpell')] = $s[2];
|
||||
if ($trgSpell = $this->relSpells->getField('effect'.$idx.'TriggerSpell'))
|
||||
{
|
||||
$this->triggerIds[] = $trgSpell;
|
||||
$data[$this->id]['spells'][$trgSpell] = $charges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spell was not proccing
|
||||
if (!$trgSpell)
|
||||
$data[$this->id]['spells'][$spellId] = $charges;
|
||||
}
|
||||
|
||||
if (!$data[$this->id]['spells'])
|
||||
unset($data[$this->id]['spells']);
|
||||
|
||||
Util::arraySumByKey($data[$this->id], $this->getStatGain());
|
||||
Util::arraySumByKey($data[$this->id], $this->jsonStats[$this->id]->toJson());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getStatGain($addScalingKeys = false)
|
||||
public function getStatGainForCurrent() : array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach (Game::$itemMods as $str)
|
||||
if (isset($this->curTpl[$str]))
|
||||
$data[$str] = $this->curTpl[$str];
|
||||
|
||||
if (isset($this->curTpl['dps']))
|
||||
$data['dps'] = $this->curTpl['dps'];
|
||||
|
||||
// scaling enchantments are saved as 0 to item_stats, thus return empty
|
||||
if ($addScalingKeys)
|
||||
{
|
||||
$spellStats = [];
|
||||
if ($this->relSpells)
|
||||
$spellStats = $this->relSpells->getStatGain();
|
||||
|
||||
for ($h = 1; $h <= 3; $h++)
|
||||
{
|
||||
$obj = (int)$this->curTpl['object'.$h];
|
||||
|
||||
switch ($this->curTpl['type'.$h])
|
||||
{
|
||||
case 3: // TYPE_EQUIP_SPELL Spells from ObjectX (use of amountX?)
|
||||
if (!empty($spellStats[$obj]))
|
||||
foreach ($spellStats[$obj] as $mod => $_)
|
||||
if ($str = Game::$itemMods[$mod])
|
||||
Util::arraySumByKey($data, [$str => 0]);
|
||||
|
||||
$obj = null;
|
||||
break;
|
||||
case 4: // TYPE_RESISTANCE +AmountX resistance for ObjectX School
|
||||
switch ($obj)
|
||||
{
|
||||
case 0: // Physical
|
||||
$obj = ITEM_MOD_ARMOR;
|
||||
break;
|
||||
case 1: // Holy
|
||||
$obj = ITEM_MOD_HOLY_RESISTANCE;
|
||||
break;
|
||||
case 2: // Fire
|
||||
$obj = ITEM_MOD_FIRE_RESISTANCE;
|
||||
break;
|
||||
case 3: // Nature
|
||||
$obj = ITEM_MOD_NATURE_RESISTANCE;
|
||||
break;
|
||||
case 4: // Frost
|
||||
$obj = ITEM_MOD_FROST_RESISTANCE;
|
||||
break;
|
||||
case 5: // Shadow
|
||||
$obj = ITEM_MOD_SHADOW_RESISTANCE;
|
||||
break;
|
||||
case 6: // Arcane
|
||||
$obj = ITEM_MOD_ARCANE_RESISTANCE;
|
||||
break;
|
||||
default:
|
||||
$obj = null;
|
||||
}
|
||||
break;
|
||||
case 5: // TYPE_STAT +AmountX for Statistic by type of ObjectX
|
||||
if ($obj < 2) // [mana, health] are on [0, 1] respectively and are expected on [1, 2] ..
|
||||
$obj++; // 0 is weaponDmg .. ehh .. i messed up somewhere
|
||||
|
||||
break; // stats are directly assigned below
|
||||
default: // TYPE_NONE dnd stuff; skip assignment below
|
||||
$obj = null;
|
||||
}
|
||||
|
||||
if ($obj !== null)
|
||||
if ($str = Game::$itemMods[$obj]) // check if we use these mods
|
||||
Util::arraySumByKey($data, [$str => 0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
return $this->jsonStats[$this->id]->toJson();
|
||||
}
|
||||
|
||||
public function getRelSpell($id)
|
||||
@@ -237,107 +168,94 @@ class EnchantmentList extends BaseType
|
||||
|
||||
class EnchantmentListFilter extends Filter
|
||||
{
|
||||
protected $enums = array(
|
||||
3 => array( // requiresprof
|
||||
null, 171, 164, 185, 333, 202, 129, 755, 165, 186, 197, true, false, 356, 182, 773
|
||||
)
|
||||
protected string $type = 'enchantments';
|
||||
protected array $enums = array(
|
||||
3 => parent::ENUM_PROFESSION // requiresprof
|
||||
);
|
||||
|
||||
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
|
||||
2 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
3 => [FILTER_CR_ENUM, 'skillLine' ], // requiresprof
|
||||
4 => [FILTER_CR_NUMERIC, 'skillLevel', NUM_CAST_INT ], // reqskillrank
|
||||
5 => [FILTER_CR_BOOLEAN, 'conditionId' ], // hascondition
|
||||
10 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
11 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
12 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
20 => [FILTER_CR_NUMERIC, 'is.str', NUM_CAST_INT, true], // str
|
||||
21 => [FILTER_CR_NUMERIC, 'is.agi', NUM_CAST_INT, true], // agi
|
||||
22 => [FILTER_CR_NUMERIC, 'is.sta', NUM_CAST_INT, true], // sta
|
||||
23 => [FILTER_CR_NUMERIC, 'is.int', NUM_CAST_INT, true], // int
|
||||
24 => [FILTER_CR_NUMERIC, 'is.spi', NUM_CAST_INT, true], // spi
|
||||
25 => [FILTER_CR_NUMERIC, 'is.arcres', NUM_CAST_INT, true], // arcres
|
||||
26 => [FILTER_CR_NUMERIC, 'is.firres', NUM_CAST_INT, true], // firres
|
||||
27 => [FILTER_CR_NUMERIC, 'is.natres', NUM_CAST_INT, true], // natres
|
||||
28 => [FILTER_CR_NUMERIC, 'is.frores', NUM_CAST_INT, true], // frores
|
||||
29 => [FILTER_CR_NUMERIC, 'is.shares', NUM_CAST_INT, true], // shares
|
||||
30 => [FILTER_CR_NUMERIC, 'is.holres', NUM_CAST_INT, true], // holres
|
||||
32 => [FILTER_CR_NUMERIC, 'is.dps', NUM_CAST_FLOAT, true], // dps
|
||||
34 => [FILTER_CR_NUMERIC, 'is.dmg', NUM_CAST_FLOAT, true], // dmg
|
||||
37 => [FILTER_CR_NUMERIC, 'is.mleatkpwr', NUM_CAST_INT, true], // mleatkpwr
|
||||
38 => [FILTER_CR_NUMERIC, 'is.rgdatkpwr', NUM_CAST_INT, true], // rgdatkpwr
|
||||
39 => [FILTER_CR_NUMERIC, 'is.rgdhitrtng', NUM_CAST_INT, true], // rgdhitrtng
|
||||
40 => [FILTER_CR_NUMERIC, 'is.rgdcritstrkrtng', NUM_CAST_INT, true], // rgdcritstrkrtng
|
||||
41 => [FILTER_CR_NUMERIC, 'is.armor' , NUM_CAST_INT, true], // armor
|
||||
42 => [FILTER_CR_NUMERIC, 'is.defrtng', NUM_CAST_INT, true], // defrtng
|
||||
43 => [FILTER_CR_NUMERIC, 'is.block', NUM_CAST_INT, true], // block
|
||||
44 => [FILTER_CR_NUMERIC, 'is.blockrtng', NUM_CAST_INT, true], // blockrtng
|
||||
45 => [FILTER_CR_NUMERIC, 'is.dodgertng', NUM_CAST_INT, true], // dodgertng
|
||||
46 => [FILTER_CR_NUMERIC, 'is.parryrtng', NUM_CAST_INT, true], // parryrtng
|
||||
48 => [FILTER_CR_NUMERIC, 'is.splhitrtng', NUM_CAST_INT, true], // splhitrtng
|
||||
49 => [FILTER_CR_NUMERIC, 'is.splcritstrkrtng', NUM_CAST_INT, true], // splcritstrkrtng
|
||||
50 => [FILTER_CR_NUMERIC, 'is.splheal', NUM_CAST_INT, true], // splheal
|
||||
51 => [FILTER_CR_NUMERIC, 'is.spldmg', NUM_CAST_INT, true], // spldmg
|
||||
52 => [FILTER_CR_NUMERIC, 'is.arcsplpwr', NUM_CAST_INT, true], // arcsplpwr
|
||||
53 => [FILTER_CR_NUMERIC, 'is.firsplpwr', NUM_CAST_INT, true], // firsplpwr
|
||||
54 => [FILTER_CR_NUMERIC, 'is.frosplpwr', NUM_CAST_INT, true], // frosplpwr
|
||||
55 => [FILTER_CR_NUMERIC, 'is.holsplpwr', NUM_CAST_INT, true], // holsplpwr
|
||||
56 => [FILTER_CR_NUMERIC, 'is.natsplpwr', NUM_CAST_INT, true], // natsplpwr
|
||||
57 => [FILTER_CR_NUMERIC, 'is.shasplpwr', NUM_CAST_INT, true], // shasplpwr
|
||||
60 => [FILTER_CR_NUMERIC, 'is.healthrgn', NUM_CAST_INT, true], // healthrgn
|
||||
61 => [FILTER_CR_NUMERIC, 'is.manargn', NUM_CAST_INT, true], // manargn
|
||||
77 => [FILTER_CR_NUMERIC, 'is.atkpwr', NUM_CAST_INT, true], // atkpwr
|
||||
78 => [FILTER_CR_NUMERIC, 'is.mlehastertng', NUM_CAST_INT, true], // mlehastertng
|
||||
79 => [FILTER_CR_NUMERIC, 'is.resirtng', NUM_CAST_INT, true], // resirtng
|
||||
84 => [FILTER_CR_NUMERIC, 'is.mlecritstrkrtng', NUM_CAST_INT, true], // mlecritstrkrtng
|
||||
94 => [FILTER_CR_NUMERIC, 'is.splpen', NUM_CAST_INT, true], // splpen
|
||||
95 => [FILTER_CR_NUMERIC, 'is.mlehitrtng', NUM_CAST_INT, true], // mlehitrtng
|
||||
96 => [FILTER_CR_NUMERIC, 'is.critstrkrtng', NUM_CAST_INT, true], // critstrkrtng
|
||||
97 => [FILTER_CR_NUMERIC, 'is.feratkpwr', NUM_CAST_INT, true], // feratkpwr
|
||||
101 => [FILTER_CR_NUMERIC, 'is.rgdhastertng', NUM_CAST_INT, true], // rgdhastertng
|
||||
102 => [FILTER_CR_NUMERIC, 'is.splhastertng', NUM_CAST_INT, true], // splhastertng
|
||||
103 => [FILTER_CR_NUMERIC, 'is.hastertng', NUM_CAST_INT, true], // hastertng
|
||||
114 => [FILTER_CR_NUMERIC, 'is.armorpenrtng', NUM_CAST_INT, true], // armorpenrtng
|
||||
115 => [FILTER_CR_NUMERIC, 'is.health', NUM_CAST_INT, true], // health
|
||||
116 => [FILTER_CR_NUMERIC, 'is.mana', NUM_CAST_INT, true], // mana
|
||||
117 => [FILTER_CR_NUMERIC, 'is.exprtng', NUM_CAST_INT, true], // exprtng
|
||||
119 => [FILTER_CR_NUMERIC, 'is.hitrtng', NUM_CAST_INT, true], // hitrtng
|
||||
123 => [FILTER_CR_NUMERIC, 'is.splpwr', NUM_CAST_INT, true] // splpwr
|
||||
protected array $genericFilter = array(
|
||||
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
3 => [parent::CR_ENUM, 'skillLine' ], // requiresprof
|
||||
4 => [parent::CR_NUMERIC, 'skillLevel', NUM_CAST_INT ], // reqskillrank
|
||||
5 => [parent::CR_BOOLEAN, 'conditionId' ], // hascondition
|
||||
10 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
11 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
12 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
20 => [parent::CR_NUMERIC, 'is.str', NUM_CAST_INT, true], // str
|
||||
21 => [parent::CR_NUMERIC, 'is.agi', NUM_CAST_INT, true], // agi
|
||||
22 => [parent::CR_NUMERIC, 'is.sta', NUM_CAST_INT, true], // sta
|
||||
23 => [parent::CR_NUMERIC, 'is.int', NUM_CAST_INT, true], // int
|
||||
24 => [parent::CR_NUMERIC, 'is.spi', NUM_CAST_INT, true], // spi
|
||||
25 => [parent::CR_NUMERIC, 'is.arcres', NUM_CAST_INT, true], // arcres
|
||||
26 => [parent::CR_NUMERIC, 'is.firres', NUM_CAST_INT, true], // firres
|
||||
27 => [parent::CR_NUMERIC, 'is.natres', NUM_CAST_INT, true], // natres
|
||||
28 => [parent::CR_NUMERIC, 'is.frores', NUM_CAST_INT, true], // frores
|
||||
29 => [parent::CR_NUMERIC, 'is.shares', NUM_CAST_INT, true], // shares
|
||||
30 => [parent::CR_NUMERIC, 'is.holres', NUM_CAST_INT, true], // holres
|
||||
32 => [parent::CR_NUMERIC, 'is.dps', NUM_CAST_FLOAT, true], // dps
|
||||
34 => [parent::CR_NUMERIC, 'is.dmg', NUM_CAST_FLOAT, true], // dmg
|
||||
37 => [parent::CR_NUMERIC, 'is.mleatkpwr', NUM_CAST_INT, true], // mleatkpwr
|
||||
38 => [parent::CR_NUMERIC, 'is.rgdatkpwr', NUM_CAST_INT, true], // rgdatkpwr
|
||||
39 => [parent::CR_NUMERIC, 'is.rgdhitrtng', NUM_CAST_INT, true], // rgdhitrtng
|
||||
40 => [parent::CR_NUMERIC, 'is.rgdcritstrkrtng', NUM_CAST_INT, true], // rgdcritstrkrtng
|
||||
41 => [parent::CR_NUMERIC, 'is.armor', NUM_CAST_INT, true], // armor
|
||||
42 => [parent::CR_NUMERIC, 'is.defrtng', NUM_CAST_INT, true], // defrtng
|
||||
43 => [parent::CR_NUMERIC, 'is.block', NUM_CAST_INT, true], // block
|
||||
44 => [parent::CR_NUMERIC, 'is.blockrtng', NUM_CAST_INT, true], // blockrtng
|
||||
45 => [parent::CR_NUMERIC, 'is.dodgertng', NUM_CAST_INT, true], // dodgertng
|
||||
46 => [parent::CR_NUMERIC, 'is.parryrtng', NUM_CAST_INT, true], // parryrtng
|
||||
48 => [parent::CR_NUMERIC, 'is.splhitrtng', NUM_CAST_INT, true], // splhitrtng
|
||||
49 => [parent::CR_NUMERIC, 'is.splcritstrkrtng', NUM_CAST_INT, true], // splcritstrkrtng
|
||||
50 => [parent::CR_NUMERIC, 'is.splheal', NUM_CAST_INT, true], // splheal
|
||||
51 => [parent::CR_NUMERIC, 'is.spldmg', NUM_CAST_INT, true], // spldmg
|
||||
52 => [parent::CR_NUMERIC, 'is.arcsplpwr', NUM_CAST_INT, true], // arcsplpwr
|
||||
53 => [parent::CR_NUMERIC, 'is.firsplpwr', NUM_CAST_INT, true], // firsplpwr
|
||||
54 => [parent::CR_NUMERIC, 'is.frosplpwr', NUM_CAST_INT, true], // frosplpwr
|
||||
55 => [parent::CR_NUMERIC, 'is.holsplpwr', NUM_CAST_INT, true], // holsplpwr
|
||||
56 => [parent::CR_NUMERIC, 'is.natsplpwr', NUM_CAST_INT, true], // natsplpwr
|
||||
57 => [parent::CR_NUMERIC, 'is.shasplpwr', NUM_CAST_INT, true], // shasplpwr
|
||||
60 => [parent::CR_NUMERIC, 'is.healthrgn', NUM_CAST_INT, true], // healthrgn
|
||||
61 => [parent::CR_NUMERIC, 'is.manargn', NUM_CAST_INT, true], // manargn
|
||||
77 => [parent::CR_NUMERIC, 'is.atkpwr', NUM_CAST_INT, true], // atkpwr
|
||||
78 => [parent::CR_NUMERIC, 'is.mlehastertng', NUM_CAST_INT, true], // mlehastertng
|
||||
79 => [parent::CR_NUMERIC, 'is.resirtng', NUM_CAST_INT, true], // resirtng
|
||||
84 => [parent::CR_NUMERIC, 'is.mlecritstrkrtng', NUM_CAST_INT, true], // mlecritstrkrtng
|
||||
94 => [parent::CR_NUMERIC, 'is.splpen', NUM_CAST_INT, true], // splpen
|
||||
95 => [parent::CR_NUMERIC, 'is.mlehitrtng', NUM_CAST_INT, true], // mlehitrtng
|
||||
96 => [parent::CR_NUMERIC, 'is.critstrkrtng', NUM_CAST_INT, true], // critstrkrtng
|
||||
97 => [parent::CR_NUMERIC, 'is.feratkpwr', NUM_CAST_INT, true], // feratkpwr
|
||||
101 => [parent::CR_NUMERIC, 'is.rgdhastertng', NUM_CAST_INT, true], // rgdhastertng
|
||||
102 => [parent::CR_NUMERIC, 'is.splhastertng', NUM_CAST_INT, true], // splhastertng
|
||||
103 => [parent::CR_NUMERIC, 'is.hastertng', NUM_CAST_INT, true], // hastertng
|
||||
114 => [parent::CR_NUMERIC, 'is.armorpenrtng', NUM_CAST_INT, true], // armorpenrtng
|
||||
115 => [parent::CR_NUMERIC, 'is.health', NUM_CAST_INT, true], // health
|
||||
116 => [parent::CR_NUMERIC, 'is.mana', NUM_CAST_INT, true], // mana
|
||||
117 => [parent::CR_NUMERIC, 'is.exprtng', NUM_CAST_INT, true], // exprtng
|
||||
119 => [parent::CR_NUMERIC, 'is.hitrtng', NUM_CAST_INT, true], // hitrtng
|
||||
123 => [parent::CR_NUMERIC, 'is.splpwr', NUM_CAST_INT, true] // splpwr
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_RANGE, [2, 123], true ], // criteria ids
|
||||
'crs' => [FILTER_V_RANGE, [1, 15], true ], // criteria operators
|
||||
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - only numerals
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'ty' => [FILTER_V_RANGE, [1, 8], true ] // types
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_RANGE, [2, 123], true ], // criteria ids
|
||||
'crs' => [parent::V_RANGE, [1, 15], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numerals
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'ty' => [parent::V_RANGE, [1, 8], true ] // types
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
//string
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
|
||||
$parts[] = $_;
|
||||
|
||||
// type
|
||||
if (isset($_v['ty']))
|
||||
if ($_v['ty'])
|
||||
$parts[] = ['OR', ['type1', $_v['ty']], ['type2', $_v['ty']], ['type3', $_v['ty']]];
|
||||
|
||||
return $parts;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -17,9 +19,9 @@ class FactionList extends BaseType
|
||||
'ft' => ['j' => '?_factiontemplate ft ON ft.factionId = f.id']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -21,7 +23,7 @@ class GameObjectList extends BaseType
|
||||
's' => ['j' => '?_spawns s ON s.type = 2 AND s.typeId = o.id']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
@@ -73,7 +75,7 @@ class GameObjectList extends BaseType
|
||||
{
|
||||
$data[$this->id] = array(
|
||||
'id' => $this->id,
|
||||
'name' => $this->getField('name', true),
|
||||
'name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW),
|
||||
'type' => $this->curTpl['typeCat'],
|
||||
'location' => $this->getSpawns(SPAWNINFO_ZONES)
|
||||
);
|
||||
@@ -95,7 +97,7 @@ class GameObjectList extends BaseType
|
||||
return array();
|
||||
|
||||
$x = '<table>';
|
||||
$x .= '<tr><td><b class="q">'.$this->getField('name', true).'</b></td></tr>';
|
||||
$x .= '<tr><td><b class="q">'.Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_HTML).'</b></td></tr>';
|
||||
if ($this->curTpl['typeCat'])
|
||||
if ($_ = Lang::gameObject('type', $this->curTpl['typeCat']))
|
||||
$x .= '<tr><td>'.$_.'</td></tr>';
|
||||
@@ -115,23 +117,24 @@ class GameObjectList extends BaseType
|
||||
$data = [];
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
$data[Type::OBJECT][$this->id] = ['name' => $this->getField('name', true)];
|
||||
$data[Type::OBJECT][$this->id] = ['name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW)];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getSourceData()
|
||||
public function getSourceData(int $id = 0) : array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
if ($id && $id != $this->id)
|
||||
continue;
|
||||
|
||||
$data[$this->id] = array(
|
||||
'n' => $this->getField('name', true),
|
||||
't' => Type::OBJECT,
|
||||
'ti' => $this->id
|
||||
// 'bd' => bossdrop
|
||||
// 'dd' => dungeondifficulty
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,118 +145,107 @@ class GameObjectList extends BaseType
|
||||
|
||||
class GameObjectListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = [];
|
||||
protected $enums = array(
|
||||
50 => array(
|
||||
null, 1, 2, 3, 4,
|
||||
663 => 663,
|
||||
883 => 883,
|
||||
FILTER_ENUM_ANY => true,
|
||||
FILTER_ENUM_NONE => false
|
||||
)
|
||||
protected string $type = 'objects';
|
||||
protected array $enums = array(
|
||||
1 => parent::ENUM_ZONE,
|
||||
16 => parent::ENUM_EVENT,
|
||||
50 => [1, 2, 3, 4, 663, 883]
|
||||
);
|
||||
|
||||
protected $genericFilter = array(
|
||||
1 => [FILTER_CR_ENUM, 's.areaId', null ], // foundin
|
||||
2 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [side]
|
||||
3 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [side]
|
||||
4 => [FILTER_CR_CALLBACK, 'cbOpenable', null, null], // openable [yn]
|
||||
5 => [FILTER_CR_NYI_PH, null, null ], // averagemoneycontained [op] [int] - GOs don't contain money, match against 0
|
||||
7 => [FILTER_CR_NUMERIC, 'reqSkill', NUM_CAST_INT ], // requiredskilllevel
|
||||
11 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
13 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
15 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT ], // id
|
||||
16 => [FILTER_CR_CALLBACK, 'cbRelEvent', null, null], // relatedevent (ignore removed by event)
|
||||
18 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
50 => [FILTER_CR_ENUM, 'spellFocusId', null, ], // SpellFocus
|
||||
protected array $genericFilter = array(
|
||||
1 => [parent::CR_ENUM, 's.areaId', false, true], // foundin
|
||||
2 => [parent::CR_CALLBACK, 'cbQuestRelation', 'startsQuests', 0x1 ], // startsquest [side]
|
||||
3 => [parent::CR_CALLBACK, 'cbQuestRelation', 'endsQuests', 0x2 ], // endsquest [side]
|
||||
4 => [parent::CR_CALLBACK, 'cbOpenable', null, null], // openable [yn]
|
||||
5 => [parent::CR_NYI_PH, null, 0 ], // averagemoneycontained [op] [int] - GOs don't contain money, match against 0
|
||||
7 => [parent::CR_NUMERIC, 'reqSkill', NUM_CAST_INT ], // requiredskilllevel
|
||||
11 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
13 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
15 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT ], // id
|
||||
16 => [parent::CR_CALLBACK, 'cbRelEvent', null, null], // relatedevent (ignore removed by event)
|
||||
18 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
50 => [parent::CR_ENUM, 'spellFocusId', true, true], // spellfocus
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_LIST, [[1, 5], 7, 11, 13, 15, 16, 18, 50], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - only numeric input values expected
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false] // match any / all filter
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_LIST, [[1, 5], 7, 11, 13, 15, 16, 18, 50], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 5000]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numeric input values expected
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false] // match any / all filter
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCR = $this->genericCriterion($cr))
|
||||
return $genCR;
|
||||
public array $extraOpts = [];
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = $this->fiData['v'];
|
||||
$_v = $this->values;
|
||||
|
||||
// name
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
|
||||
$parts[] = $_;
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbOpenable($cr)
|
||||
protected function cbOpenable(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if ($this->int2Bool($cr[1]))
|
||||
return $cr[1] ? ['OR', ['flags', 0x2, '&'], ['type', 3]] : ['AND', [['flags', 0x2, '&'], 0], ['type', 3, '!']];
|
||||
if ($this->int2Bool($crs))
|
||||
return $crs ? ['OR', ['flags', 0x2, '&'], ['type', 3]] : ['AND', [['flags', 0x2, '&'], 0], ['type', 3, '!']];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbQuestRelation($cr, $field, $value)
|
||||
protected function cbQuestRelation(int $cr, int $crs, string $crv, $field, $value) : ?array
|
||||
{
|
||||
switch ($cr[1])
|
||||
switch ($crs)
|
||||
{
|
||||
case 1: // any
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!']];
|
||||
case 2: // alliance only
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_HORDE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&']];
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_HORDE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&']];
|
||||
case 3: // horde only
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']];
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], [['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], 0], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']];
|
||||
case 4: // both
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', RACE_MASK_ALLIANCE, '&'], ['qt.reqRaceMask', RACE_MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
|
||||
return ['AND', ['qse.method', $value, '&'], ['qse.questId', null, '!'], ['OR', ['AND', ['qt.reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ['qt.reqRaceMask', ChrRace::MASK_HORDE, '&']], ['qt.reqRaceMask', 0]]];
|
||||
case 5: // none todo (low): broken, if entry starts and ends quests...
|
||||
$this->extraOpts['o']['h'][] = $field.' = 0';
|
||||
return [1];
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbRelEvent($cr)
|
||||
protected function cbRelEvent(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT))
|
||||
return false;;
|
||||
|
||||
if ($cr[1] == FILTER_ENUM_ANY)
|
||||
if ($crs == parent::ENUM_ANY)
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0');
|
||||
$goGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_gameobject WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
|
||||
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $goGuids];
|
||||
|
||||
return [0];
|
||||
}
|
||||
else if ($cr[1] == FILTER_ENUM_NONE)
|
||||
else if ($crs == parent::ENUM_NONE)
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId <> 0');
|
||||
$goGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_gameobject WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` <> 0'))
|
||||
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $goGuids, '!'];
|
||||
|
||||
return [0];
|
||||
}
|
||||
else if ($cr[1])
|
||||
else if (in_array($crs, $this->enums[$cr]))
|
||||
{
|
||||
$eventIds = DB::Aowow()->selectCol('SELECT id FROM ?_events WHERE holidayId = ?d', $cr[1]);
|
||||
$goGuids = DB::World()->selectCol('SELECT DISTINCT guid FROM game_event_gameobject WHERE eventEntry IN (?a)', $eventIds);
|
||||
if ($eventIds = DB::Aowow()->selectCol('SELECT `id` FROM ?_events WHERE `holidayId` = ?d', $crs))
|
||||
if ($goGuids = DB::World()->selectCol('SELECT DISTINCT `guid` FROM game_event_gameobject WHERE `eventEntry` IN (?a)', $eventIds))
|
||||
return ['s.guid', $goGuids];
|
||||
|
||||
return [0];
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -19,6 +21,7 @@ class GuideList extends BaseType
|
||||
public static $type = Type::GUIDE;
|
||||
public static $brickFile = 'guide';
|
||||
public static $dataTable = '?_guides';
|
||||
public static $contribute = CONTRIBUTE_CO;
|
||||
|
||||
private $article = [];
|
||||
private $jsGlobals = [];
|
||||
@@ -26,13 +29,13 @@ class GuideList extends BaseType
|
||||
protected $queryBase = 'SELECT g.*, g.id AS ARRAY_KEY FROM ?_guides g';
|
||||
protected $queryOpts = array(
|
||||
'g' => [['a', 'c'], 'g' => 'g.`id`'],
|
||||
'a' => ['j' => ['?_account a ON a.id = g.userId', true], 's' => ', IFNULL(a.displayName, "") AS author'],
|
||||
'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS `comments`']
|
||||
'a' => ['j' => ['?_account a ON a.`id` = g.`userId`', true], 's' => ', IFNULL(a.`username`, "") AS "author"'],
|
||||
'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS "comments"']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
@@ -69,7 +72,7 @@ class GuideList extends BaseType
|
||||
$this->article[$a['rev']] = $a['article'];
|
||||
if ($this->article[$a['rev']])
|
||||
{
|
||||
(new Markup($this->article[$a['rev']]))->parseGlobalsFromText($this->jsGlobals);
|
||||
Markup::parseTags($this->article[$a['rev']], $this->jsGlobals);
|
||||
return $this->article[$a['rev']];
|
||||
}
|
||||
else
|
||||
@@ -92,7 +95,7 @@ class GuideList extends BaseType
|
||||
'description' => $this->getField('description'),
|
||||
'sticky' => !!($this->getField('cuFlags') & CC_FLAG_STICKY),
|
||||
'nvotes' => $this->getField('nvotes'),
|
||||
'url' => '/?guide=' . ($this->getField('url') ?: $this->id),
|
||||
'url' => '?guide=' . ($this->getField('url') ?: $this->id),
|
||||
'status' => $this->getField('status'),
|
||||
'author' => $this->getField('author'),
|
||||
'authorroles' => $this->getField('roles'),
|
||||
@@ -104,7 +107,11 @@ class GuideList extends BaseType
|
||||
'when' => date(Util::$dateFormatInternal, $this->getField('date'))
|
||||
);
|
||||
|
||||
|
||||
if ($this->getField('category') == 1)
|
||||
{
|
||||
$data[$this->id]['classs'] = $this->getField('classId');
|
||||
$data[$this->id]['spec'] = $this->getField('specId');
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -139,23 +146,24 @@ class GuideList extends BaseType
|
||||
|
||||
if ($this->getField('classId') && $this->getField('category') == 1)
|
||||
{
|
||||
$c = $this->getField('classId');
|
||||
if ($c = $this->getField('classId'))
|
||||
{
|
||||
$n = Lang::game('cl', $c);
|
||||
$specStr .= ' – <span class="icontiny c'.$c.'" style="background-image: url('.Cfg::get('STATIC_URL').'/images/wow/icons/tiny/class_'.ChrClass::tryFrom($c)->json().'.gif)">%s</span>';
|
||||
|
||||
if (($s = $this->getField('specId')) > -1)
|
||||
{
|
||||
$i = Game::$specIconStrings[$c][$s];
|
||||
$n = Lang::game('classSpecs', $c, $s);
|
||||
}
|
||||
else
|
||||
{
|
||||
$i = 'class_'.Game::$classFileStrings[$c];
|
||||
$n = Lang::game('cl', $c);
|
||||
$n = '';
|
||||
$specStr .= '<span class="icontiny c'.$c.'" style="background-image: url('.Cfg::get('STATIC_URL').'/images/wow/icons/tiny/'.$i.'.gif)">'.Lang::game('classSpecs', $c, $s).'</span>';
|
||||
}
|
||||
|
||||
$specStr = ' – <span class="icontiny c'.$c.'" style="background-image: url('.STATIC_URL.'/images/wow/icons/tiny/'.$i.'.gif)">'.$n.'</span>';
|
||||
$specStr = sprintf($specStr, $n);
|
||||
}
|
||||
}
|
||||
|
||||
$tt = '<table><tr><td><div style="max-width: 320px"><b class="q">'.$this->getField('title').'</b><br>';
|
||||
$tt .= '<table width="100%"><tr><td>'.Lang::guide('guide').'</td><th>'.Lang::guide('byAuthor', [$this->getField('author')]).'</th></tr></table>';
|
||||
$tt .= '<table width="100%"><tr><td>'.Lang::game('guide').'</td><th>'.Lang::guide('byAuthor', [$this->getField('author')]).'</th></tr></table>';
|
||||
$tt .= '<table width="100%"><tr><td>'.Lang::guide('category', $this->getField('category')).$specStr.'</td><th>'.Lang::guide('patch').' 3.3.5</th></tr></table>';
|
||||
$tt .= '<div class="q" style="margin: 0.25em 0">'.$this->getField('description').'</div>';
|
||||
$tt .= '</div></td></tr></table>';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -8,6 +10,8 @@ class GuildList extends BaseType
|
||||
{
|
||||
use profilerHelper, listviewHelper;
|
||||
|
||||
public static $contribute = CONTRIBUTE_NONE;
|
||||
|
||||
public function getListviewData()
|
||||
{
|
||||
$this->getGuildScores();
|
||||
@@ -44,7 +48,7 @@ class GuildList extends BaseType
|
||||
if (!$guilds)
|
||||
return;
|
||||
|
||||
$stats = DB::Aowow()->select('SELECT guild AS ARRAY_KEY, id AS ARRAY_KEY2, level, gearscore, achievementpoints, IF(cuFlags & ?d, 0, 1) AS synced FROM ?_profiler_profiles WHERE guild IN (?a) ORDER BY gearscore DESC', PROFILER_CU_NEEDS_RESYNC, $guilds);
|
||||
$stats = DB::Aowow()->select('SELECT `guild` AS ARRAY_KEY, `id` AS ARRAY_KEY2, `level`, `gearscore`, `achievementpoints`, IF(`cuFlags` & ?d, 0, 1) AS "synced" FROM ?_profiler_profiles WHERE `guild` IN (?a) ORDER BY `gearscore` DESC', PROFILER_CU_NEEDS_RESYNC, $guilds);
|
||||
foreach ($this->iterate() as &$_curTpl)
|
||||
{
|
||||
$id = $_curTpl['id'];
|
||||
@@ -85,71 +89,41 @@ class GuildList extends BaseType
|
||||
|
||||
class GuildListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = [];
|
||||
protected $genericFilter = [];
|
||||
use TrProfilerFilter;
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [FILTER_V_LIST, [1, 2], false], // side
|
||||
'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server
|
||||
protected string $type = 'guilds';
|
||||
protected array $genericFilter = [];
|
||||
protected array $inputFields = array(
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE], false], // side
|
||||
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr) { }
|
||||
public array $extraOpts = [];
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = $this->fiData['v'];
|
||||
$_v = $this->values;
|
||||
|
||||
// region (rg), battlegroup (bg) and server (sv) are passed to GuildList as miscData and handled there
|
||||
|
||||
// name [str]
|
||||
if (!empty($_v['na']))
|
||||
if ($_ = $this->modularizeString(['g.name'], $_v['na'], !empty($_v['ex']) && $_v['ex'] == 'on'))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['g.name'], $_v['na'], $_v['ex'] == 'on'))
|
||||
$parts[] = $_;
|
||||
|
||||
// side [list]
|
||||
if (!empty($_v['si']))
|
||||
{
|
||||
if ($_v['si'] == 1)
|
||||
$parts[] = ['c.race', [1, 3, 4, 7, 11]];
|
||||
else if ($_v['si'] == 2)
|
||||
$parts[] = ['c.race', [2, 5, 6, 8, 10]];
|
||||
}
|
||||
if ($_v['si'] == SIDE_ALLIANCE)
|
||||
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
|
||||
else if ($_v['si'] == SIDE_HORDE)
|
||||
$parts[] = ['c.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbRegionCheck(&$v)
|
||||
{
|
||||
if (in_array($v, Util::$regions))
|
||||
{
|
||||
$this->parentCats[0] = $v; // directly redirect onto this region
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbServerCheck(&$v)
|
||||
{
|
||||
foreach (Profiler::getRealms() as $realm)
|
||||
if ($realm['name'] == $v)
|
||||
{
|
||||
$this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,12 +136,12 @@ class RemoteGuildList extends GuildList
|
||||
'c' => ['j' => 'characters c ON c.guid = gm.guid', 's' => ', BIT_OR(IF(c.race IN (1, 3, 4, 7, 11), 1, 2)) - 1 AS faction']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
// select DB by realm
|
||||
if (!$this->selectRealms($miscData))
|
||||
{
|
||||
trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING);
|
||||
trigger_error('RemoteGuildList::__construct - cannot access any realm.', E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,7 +159,7 @@ class RemoteGuildList extends GuildList
|
||||
foreach ($this->iterate() as $guid => &$curTpl)
|
||||
{
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
|
||||
$r = explode(':', $guid)[0];
|
||||
if (!empty($realms[$r]))
|
||||
@@ -196,7 +170,15 @@ class RemoteGuildList extends GuildList
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('character "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
trigger_error('guild #'.$guid.' belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// empty name
|
||||
if (!$curTpl['name'])
|
||||
{
|
||||
trigger_error('guild #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
@@ -208,11 +190,14 @@ class RemoteGuildList extends GuildList
|
||||
$distrib[$curTpl['realm']]++;
|
||||
}
|
||||
|
||||
$limit = CFG_SQL_LIMIT_DEFAULT;
|
||||
foreach ($conditions as $c)
|
||||
if (is_int($c))
|
||||
$limit = $c;
|
||||
|
||||
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
|
||||
if (!$limit) // int:0 means unlimited, so skip early
|
||||
return;
|
||||
|
||||
$total = array_sum($distrib);
|
||||
foreach ($distrib as &$d)
|
||||
$d = ceil($limit * $d / $total);
|
||||
@@ -246,7 +231,7 @@ class RemoteGuildList extends GuildList
|
||||
|
||||
// basic guild data
|
||||
foreach (Util::createSqlBatchInsert($data) as $ins)
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES '.$ins, array_keys(reset($data)));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_guild (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($data)));
|
||||
|
||||
// merge back local ids
|
||||
$localIds = DB::Aowow()->selectCol(
|
||||
@@ -266,15 +251,36 @@ class LocalGuildList extends GuildList
|
||||
{
|
||||
protected $queryBase = 'SELECT g.*, g.id AS ARRAY_KEY FROM ?_profiler_guild g';
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
// graft realm selection from miscData onto conditions
|
||||
if (isset($miscData['sv']))
|
||||
$realms = array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv']));
|
||||
|
||||
if (isset($miscData['rg']))
|
||||
$realms = array_filter($realms, fn($x) => $x['region'] == $miscData['rg']);
|
||||
|
||||
if (!$realms)
|
||||
{
|
||||
trigger_error('LocalGuildList::__construct - cannot access any realm.', E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($conditions)
|
||||
{
|
||||
array_unshift($conditions, 'AND');
|
||||
$conditions = ['AND', ['realm', array_keys($realms)], $conditions];
|
||||
}
|
||||
else
|
||||
$conditions = [['realm', array_keys($realms)]];
|
||||
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
foreach ($this->iterate() as $id => &$curTpl)
|
||||
{
|
||||
if ($curTpl['realm'] && !isset($realms[$curTpl['realm']]))
|
||||
@@ -287,7 +293,7 @@ class LocalGuildList extends GuildList
|
||||
}
|
||||
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -9,11 +11,11 @@ class IconList extends BaseType
|
||||
use listviewHelper;
|
||||
|
||||
public static $type = Type::ICON;
|
||||
public static $brickFile = 'icon';
|
||||
public static $brickFile = 'icongallery';
|
||||
public static $dataTable = '?_icons';
|
||||
public static $contribute = CONTRIBUTE_CO;
|
||||
|
||||
private $pseudoQry = 'SELECT iconId AS ARRAY_KEY, COUNT(*) FROM ?# WHERE iconId IN (?a) GROUP BY iconId';
|
||||
private $pseudoQry = 'SELECT `iconId` AS ARRAY_KEY, COUNT(*) FROM ?# WHERE `iconId` IN (?a) GROUP BY `iconId`';
|
||||
private $pseudoJoin = array(
|
||||
'nItems' => '?_items',
|
||||
'nSpells' => '?_spell',
|
||||
@@ -34,9 +36,9 @@ class IconList extends BaseType
|
||||
);
|
||||
*/
|
||||
|
||||
public function __construct($conditions)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if (!$this->getFoundIDs())
|
||||
return;
|
||||
@@ -53,7 +55,7 @@ class IconList extends BaseType
|
||||
// use if you JUST need the name
|
||||
public static function getName($id)
|
||||
{
|
||||
$n = DB::Aowow()->SelectRow('SELECT name FROM ?_icons WHERE id = ?d', $id );
|
||||
$n = DB::Aowow()->SelectRow('SELECT `name` FROM ?_icons WHERE `id` = ?d', $id );
|
||||
return Util::localizedString($n, 'name');
|
||||
}
|
||||
// end static use
|
||||
@@ -101,10 +103,8 @@ class IconList extends BaseType
|
||||
|
||||
class IconListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = null;
|
||||
|
||||
// cr => [type, field, misc, extraCol]
|
||||
private $criterion2field = array(
|
||||
private array $totalUses = [];
|
||||
private array $criterion2field = array(
|
||||
1 => '?_items', // items [num]
|
||||
2 => '?_spell', // spells [num]
|
||||
3 => '?_achievement', // achievements [num]
|
||||
@@ -118,35 +118,36 @@ class IconListFilter extends Filter
|
||||
11 => '', // classes [num]
|
||||
13 => '' // used [num]
|
||||
);
|
||||
private $totalUses = [];
|
||||
|
||||
protected $genericFilter = array(
|
||||
1 => [FILTER_CR_CALLBACK, 'cbUseAny' ], // items [num]
|
||||
2 => [FILTER_CR_CALLBACK, 'cbUseAny' ], // spells [num]
|
||||
3 => [FILTER_CR_CALLBACK, 'cbUseAny' ], // achievements [num]
|
||||
6 => [FILTER_CR_CALLBACK, 'cbUseAny' ], // currencies [num]
|
||||
9 => [FILTER_CR_CALLBACK, 'cbUseAny' ], // hunterpets [num]
|
||||
11 => [FILTER_CR_NYI_PH, null, null], // classes [num]
|
||||
13 => [FILTER_CR_CALLBACK, 'cbUseAll' ] // used [num]
|
||||
protected string $type = 'icons';
|
||||
protected array $genericFilter = array(
|
||||
1 => [parent::CR_CALLBACK, 'cbUseAny' ], // items [num]
|
||||
2 => [parent::CR_CALLBACK, 'cbUseAny' ], // spells [num]
|
||||
3 => [parent::CR_CALLBACK, 'cbUseAny' ], // achievements [num]
|
||||
6 => [parent::CR_CALLBACK, 'cbUseAny' ], // currencies [num]
|
||||
9 => [parent::CR_CALLBACK, 'cbUseAny' ], // hunterpets [num]
|
||||
11 => [parent::CR_NYI_PH, null, 0], // classes [num]
|
||||
13 => [parent::CR_CALLBACK, 'cbUseAll' ] // used [num]
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_LIST, [1, 2, 3, 6, 9, 11, 13], true ], // criteria ids
|
||||
'crs' => [FILTER_V_RANGE, [1, 6], true ], // criteria operators
|
||||
'crv' => [FILTER_V_RANGE, [0, 99999], true ], // criteria values - all criteria are numeric here
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false] // match any / all filter
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_LIST, [1, 2, 3, 6, 9, 11, 13], true ], // criteria ids
|
||||
'crs' => [parent::V_RANGE, [1, 6], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - all criteria are numeric here
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false] // match any / all filter
|
||||
);
|
||||
|
||||
private function _getCnd($op, $val, $tbl)
|
||||
public array $extraOpts = [];
|
||||
|
||||
private function _getCnd(string $op, int $val, string $tbl) : ?array
|
||||
{
|
||||
switch ($op)
|
||||
{
|
||||
case '>':
|
||||
case '>=':
|
||||
case '=':
|
||||
$ids = DB::Aowow()->selectCol('SELECT iconId AS ARRAY_KEY, COUNT(*) AS n FROM ?# GROUP BY iconId HAVING n '.$op.' '.$val, $tbl);
|
||||
$ids = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId` HAVING n '.$op.' '.$val, $tbl);
|
||||
return $ids ? ['id', array_keys($ids)] : [1];
|
||||
case '<=':
|
||||
if ($val)
|
||||
@@ -160,48 +161,39 @@ class IconListFilter extends Filter
|
||||
if ($val)
|
||||
$op = '=';
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
$ids = DB::Aowow()->selectCol('SELECT iconId AS ARRAY_KEY, COUNT(*) AS n FROM ?# GROUP BY iconId HAVING n '.$op.' '.$val, $tbl);
|
||||
$ids = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId` HAVING n '.$op.' '.$val, $tbl);
|
||||
return $ids ? ['id', array_keys($ids), '!'] : [1];
|
||||
}
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
//string
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name']))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name']))
|
||||
$parts[] = $_;
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbUseAny($cr, $value)
|
||||
protected function cbUseAny(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (Util::checkNumeric($cr[2], NUM_CAST_INT) && $this->int2Op($cr[1]))
|
||||
return $this->_getCnd($cr[1], $cr[2], $this->criterion2field[$cr[0]]);
|
||||
if (Util::checkNumeric($crv, NUM_CAST_INT) && $this->int2Op($crs))
|
||||
return $this->_getCnd($crs, $crv, $this->criterion2field[$cr]);
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbUseAll($cr)
|
||||
protected function cbUseAll(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
if (!$this->totalUses)
|
||||
{
|
||||
@@ -210,24 +202,24 @@ class IconListFilter extends Filter
|
||||
if (!$tbl)
|
||||
continue;
|
||||
|
||||
$res = DB::Aowow()->selectCol('SELECT iconId AS ARRAY_KEY, COUNT(*) AS n FROM ?# GROUP BY iconId', $tbl);
|
||||
$res = DB::Aowow()->selectCol('SELECT `iconId` AS ARRAY_KEY, COUNT(*) AS "n" FROM ?# GROUP BY `iconId`', $tbl);
|
||||
Util::arraySumByKey($this->totalUses, $res);
|
||||
}
|
||||
}
|
||||
|
||||
if ($cr[1] == '=')
|
||||
$cr[1] = '==';
|
||||
if ($crs == '=')
|
||||
$crs = '==';
|
||||
|
||||
$op = $cr[1];
|
||||
if ($cr[1] == '<=' && $cr[2])
|
||||
$op = $crs;
|
||||
if ($crs == '<=' && $crv)
|
||||
$op = '>';
|
||||
else if ($cr[1] == '<' && $cr[2])
|
||||
else if ($crs == '<' && $crv)
|
||||
$op = '>=';
|
||||
else if ($cr[1] == '!=' && $cr[2])
|
||||
else if ($crs == '!=' && $crv)
|
||||
$op = '==';
|
||||
$ids = array_filter($this->totalUses, function ($x) use ($op, $cr) { return eval('return '.$x.' '.$op.' '.$cr[2].';'); });
|
||||
$ids = array_filter($this->totalUses, fn($x) => eval('return '.$x.' '.$op.' '.$crv.';'));
|
||||
|
||||
if ($cr[1] != $op)
|
||||
if ($crs != $op)
|
||||
return $ids ? ['id', array_keys($ids), '!'] : [1];
|
||||
else
|
||||
return $ids ? ['id', array_keys($ids)] : ['id', array_keys($this->totalUses), '!'];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -18,27 +20,21 @@ class ItemsetList extends BaseType
|
||||
protected $queryBase = 'SELECT `set`.*, `set`.id AS ARRAY_KEY FROM ?_itemset `set`';
|
||||
protected $queryOpts = array(
|
||||
'set' => ['o' => 'maxlevel DESC'],
|
||||
'e' => ['j' => ['?_events e ON e.id = `set`.eventId', true], 's' => ', e.holidayId']
|
||||
'e' => ['j' => ['?_events e ON `e`.`id` = `set`.`eventId`', true], 's' => ', e.holidayId'],
|
||||
'src' => ['j' => ['?_source src ON `src`.`typeId` = `set`.`id` AND `src`.`type` = 4', true], 's' => ', src1, src2, src3, src4, src5, src6, src7, src8, src9, src10, src11, src12, src13, src14, src15, src16, src17, src18, src19, src20, src21, src22, src23, src24']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as &$_curTpl)
|
||||
{
|
||||
$_curTpl['classes'] = [];
|
||||
$_curTpl['pieces'] = [];
|
||||
for ($i = 1; $i < 12; $i++)
|
||||
{
|
||||
if ($_curTpl['classMask'] & (1 << ($i - 1)))
|
||||
{
|
||||
$this->classes[] = $i;
|
||||
$_curTpl['classes'][] = $i;
|
||||
}
|
||||
}
|
||||
$_curTpl['classes'] = ChrClass::fromMask($_curTpl['classMask']);
|
||||
$this->classes = array_merge($this->classes, $_curTpl['classes']);
|
||||
|
||||
$_curTpl['pieces'] = [];
|
||||
for ($i = 1; $i < 10; $i++)
|
||||
{
|
||||
if ($piece = $_curTpl['item'.$i])
|
||||
@@ -112,7 +108,7 @@ class ItemsetList extends BaseType
|
||||
if ($_ = $this->getField('contentGroup'))
|
||||
$x .= Lang::itemset('notes', $_).($this->getField('heroic') ? ' <i class="q2">('.Lang::item('heroic').')</i>' : '').'<br />';
|
||||
|
||||
if (!$nCl || !$this->getField('contentGroup'))
|
||||
if (!$nCl || !$this->getField('type'))
|
||||
$x.= Lang::itemset('types', $this->getField('type')).'<br />';
|
||||
|
||||
if ($bonuses = $this->getBonuses())
|
||||
@@ -171,91 +167,93 @@ class ItemsetList extends BaseType
|
||||
// missing filter: "Available to Players"
|
||||
class ItemsetListFilter extends Filter
|
||||
{
|
||||
// cr => [type, field, misc, extraCol]
|
||||
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
|
||||
2 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
3 => [FILTER_CR_NUMERIC, 'npieces', NUM_CAST_INT ], // pieces
|
||||
4 => [FILTER_CR_STRING, 'bonusText', STR_LOCALIZED ], // bonustext
|
||||
5 => [FILTER_CR_BOOLEAN, 'heroic', ], // heroic
|
||||
6 => [FILTER_CR_ENUM, 'e.holidayId', ], // relatedevent
|
||||
8 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
9 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
10 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
12 => [FILTER_CR_NYI_PH, null, 1 ] // available to players [yn] - ugh .. scan loot, quest and vendor templates and write to ?_itemset
|
||||
protected string $type = 'itemsets';
|
||||
protected array $enums = array(
|
||||
6 => parent::ENUM_EVENT
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_RANGE, [2, 12], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 424]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_REGEX, '/[\p{C};:%\\\\]/ui', true ], // criteria values - only printable chars, no delimiters
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / description - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'qu' => [FILTER_V_RANGE, [0, 7], true ], // quality
|
||||
'ty' => [FILTER_V_RANGE, [1, 12], true ], // set type
|
||||
'minle' => [FILTER_V_RANGE, [1, 999], false], // min item level
|
||||
'maxle' => [FILTER_V_RANGE, [1, 999], false], // max itemlevel
|
||||
'minrl' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // min required level
|
||||
'maxrl' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // max required level
|
||||
'cl' => [FILTER_V_LIST, [[1, 9], 11], false], // class
|
||||
'ta' => [FILTER_V_RANGE, [1, 30], false] // tag / content group
|
||||
protected array $genericFilter = array(
|
||||
2 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
3 => [parent::CR_NUMERIC, 'npieces', NUM_CAST_INT ], // pieces
|
||||
4 => [parent::CR_STRING, 'bonusText', STR_LOCALIZED ], // bonustext
|
||||
5 => [parent::CR_BOOLEAN, 'heroic' ], // heroic
|
||||
6 => [parent::CR_ENUM, 'e.holidayId', true, true], // relatedevent
|
||||
8 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
9 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
10 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
12 => [parent::CR_CALLBACK, 'cbAvaliable', ] // available to players [yn]
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCR = $this->genericCriterion($cr))
|
||||
return $genCR;
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_RANGE, [2, 12], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 424]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values - only printable chars, no delimiters
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / description - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'qu' => [parent::V_RANGE, [0, 7], true ], // quality
|
||||
'ty' => [parent::V_RANGE, [1, 12], true ], // set type
|
||||
'minle' => [parent::V_RANGE, [1, 999], false], // min item level
|
||||
'maxle' => [parent::V_RANGE, [1, 999], false], // max itemlevel
|
||||
'minrl' => [parent::V_RANGE, [1, MAX_LEVEL], false], // min required level
|
||||
'maxrl' => [parent::V_RANGE, [1, MAX_LEVEL], false], // max required level
|
||||
'cl' => [parent::V_LIST, [[1, 9], 11], false], // class
|
||||
'ta' => [parent::V_RANGE, [1, 30], false] // tag / content group
|
||||
);
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
// name [str]
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name_loc'.User::$localeId]))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]))
|
||||
$parts[] = $_;
|
||||
|
||||
// quality [enum]
|
||||
if (isset($_v['qu']))
|
||||
if ($_v['qu'])
|
||||
$parts[] = ['quality', $_v['qu']];
|
||||
|
||||
// type [enum]
|
||||
if (isset($_v['ty']))
|
||||
if ($_v['ty'])
|
||||
$parts[] = ['type', $_v['ty']];
|
||||
|
||||
// itemLevel min [int]
|
||||
if (isset($_v['minle']))
|
||||
if ($_v['minle'])
|
||||
$parts[] = ['minLevel', $_v['minle'], '>='];
|
||||
|
||||
// itemLevel max [int]
|
||||
if (isset($_v['maxle']))
|
||||
if ($_v['maxle'])
|
||||
$parts[] = ['maxLevel', $_v['maxle'], '<='];
|
||||
|
||||
// reqLevel min [int]
|
||||
if (isset($_v['minrl']))
|
||||
if ($_v['minrl'])
|
||||
$parts[] = ['reqLevel', $_v['minrl'], '>='];
|
||||
|
||||
// reqLevel max [int]
|
||||
if (isset($_v['maxrl']))
|
||||
if ($_v['maxrl'])
|
||||
$parts[] = ['reqLevel', $_v['maxrl'], '<='];
|
||||
|
||||
// class [enum]
|
||||
if (isset($_v['cl']))
|
||||
if ($_v['cl'])
|
||||
$parts[] = ['classMask', $this->list2Mask([$_v['cl']]), '&'];
|
||||
|
||||
// tag [enum]
|
||||
if (isset($_v['ta']))
|
||||
if ($_v['ta'])
|
||||
$parts[] = ['contentGroup', intVal($_v['ta'])];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbAvaliable(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
return match ($crs)
|
||||
{
|
||||
1 => ['src.typeId', null, '!'], // Yes
|
||||
2 => ['src.typeId', null], // No
|
||||
default => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -13,9 +15,9 @@ class MailList extends BaseType
|
||||
protected $queryBase = 'SELECT m.*, m.id AS ARRAY_KEY FROM ?_mails m';
|
||||
protected $queryOpts = [];
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -8,6 +10,8 @@ class ProfileList extends BaseType
|
||||
{
|
||||
use profilerHelper, listviewHelper;
|
||||
|
||||
public static $contribute = CONTRIBUTE_NONE;
|
||||
|
||||
public function getListviewData($addInfo = 0, array $reqCols = [])
|
||||
{
|
||||
$data = [];
|
||||
@@ -29,13 +33,13 @@ class ProfileList extends BaseType
|
||||
'classs' => $this->getField('class'),
|
||||
'gender' => $this->getField('gender'),
|
||||
'level' => $this->getField('level'),
|
||||
'faction' => (1 << ($this->getField('race') - 1)) & RACE_MASK_ALLIANCE ? 0 : 1,
|
||||
'faction' => ChrRace::tryFrom($this->getField('race'))?->isAlliance() ? 0 : 1,
|
||||
'talenttree1' => $this->getField('talenttree1'),
|
||||
'talenttree2' => $this->getField('talenttree2'),
|
||||
'talenttree3' => $this->getField('talenttree3'),
|
||||
'talentspec' => $this->getField('activespec') + 1, // 0 => 1; 1 => 2
|
||||
'achievementpoints' => $this->getField('achievementpoints'),
|
||||
'guild' => '$"'.str_replace ('"', '', $this->curTpl['guildname']).'"',// force this to be a string
|
||||
'guild' => $this->curTpl['guildname'] ? '$"'.str_replace ('"', '', $this->curTpl['guildname']).'"' : '', // force this to be a string
|
||||
'guildrank' => $this->getField('guildrank'),
|
||||
'realm' => Profiler::urlize($this->getField('realmName'), true),
|
||||
'realmname' => $this->getField('realmName'),
|
||||
@@ -235,286 +239,230 @@ class ProfileList extends BaseType
|
||||
|
||||
class ProfileListFilter extends Filter
|
||||
{
|
||||
public $useLocalList = false;
|
||||
public $extraOpts = [];
|
||||
use TrProfilerFilter;
|
||||
|
||||
private $realms = [];
|
||||
|
||||
protected $enums = array(
|
||||
-1 => array( // arena team sizes
|
||||
// by name by rating by contrib
|
||||
12 => 2, 13 => 2, 14 => 2,
|
||||
15 => 3, 16 => 3, 17 => 3,
|
||||
18 => 5, 19 => 5, 20 => 5
|
||||
)
|
||||
protected string $type = 'profiles';
|
||||
protected array $genericFilter = array(
|
||||
2 => [parent::CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num]
|
||||
3 => [parent::CR_CALLBACK, 'cbAchievs', null, null], // achievementpoints [num]
|
||||
5 => [parent::CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num]
|
||||
6 => [parent::CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num]
|
||||
7 => [parent::CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num]
|
||||
9 => [parent::CR_STRING, 'g.name' ], // guildname
|
||||
10 => [parent::CR_CALLBACK, 'cbHasGuildRank', null, null], // guildrank
|
||||
12 => [parent::CR_CALLBACK, 'cbTeamName', 2, null], // teamname2v2
|
||||
15 => [parent::CR_CALLBACK, 'cbTeamName', 3, null], // teamname3v3
|
||||
18 => [parent::CR_CALLBACK, 'cbTeamName', 5, null], // teamname5v5
|
||||
13 => [parent::CR_CALLBACK, 'cbTeamRating', 2, null], // teamrtng2v2
|
||||
16 => [parent::CR_CALLBACK, 'cbTeamRating', 3, null], // teamrtng3v3
|
||||
19 => [parent::CR_CALLBACK, 'cbTeamRating', 5, null], // teamrtng5v5
|
||||
14 => [parent::CR_NYI_PH, null, 0 /* 2 */ ], // teamcontrib2v2 [num]
|
||||
17 => [parent::CR_NYI_PH, null, 0 /* 3 */ ], // teamcontrib3v3 [num]
|
||||
20 => [parent::CR_NYI_PH, null, 0 /* 5 */ ], // teamcontrib5v5 [num]
|
||||
21 => [parent::CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str]
|
||||
23 => [parent::CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement
|
||||
25 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ALCHEMY, null], // alchemy [num]
|
||||
26 => [parent::CR_CALLBACK, 'cbProfession', SKILL_BLACKSMITHING, null], // blacksmithing [num]
|
||||
27 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ENCHANTING, null], // enchanting [num]
|
||||
28 => [parent::CR_CALLBACK, 'cbProfession', SKILL_ENGINEERING, null], // engineering [num]
|
||||
29 => [parent::CR_CALLBACK, 'cbProfession', SKILL_HERBALISM, null], // herbalism [num]
|
||||
30 => [parent::CR_CALLBACK, 'cbProfession', SKILL_INSCRIPTION, null], // inscription [num]
|
||||
31 => [parent::CR_CALLBACK, 'cbProfession', SKILL_JEWELCRAFTING, null], // jewelcrafting [num]
|
||||
32 => [parent::CR_CALLBACK, 'cbProfession', SKILL_LEATHERWORKING, null], // leatherworking [num]
|
||||
33 => [parent::CR_CALLBACK, 'cbProfession', SKILL_MINING, null], // mining [num]
|
||||
34 => [parent::CR_CALLBACK, 'cbProfession', SKILL_SKINNING, null], // skinning [num]
|
||||
35 => [parent::CR_CALLBACK, 'cbProfession', SKILL_TAILORING, null], // tailoring [num]
|
||||
36 => [parent::CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn]
|
||||
);
|
||||
|
||||
protected $genericFilter = array( // misc (bool): _NUMERIC => useFloat; _STRING => localized; _FLAG => match Value; _BOOLEAN => stringSet
|
||||
2 => [FILTER_CR_NUMERIC, 'gearscore', NUM_CAST_INT ], // gearscore [num]
|
||||
3 => [FILTER_CR_CALLBACK, 'cbAchievs', null, null], // achievementpoints [num]
|
||||
5 => [FILTER_CR_NUMERIC, 'talenttree1', NUM_CAST_INT ], // talenttree1 [num]
|
||||
6 => [FILTER_CR_NUMERIC, 'talenttree2', NUM_CAST_INT ], // talenttree2 [num]
|
||||
7 => [FILTER_CR_NUMERIC, 'talenttree3', NUM_CAST_INT ], // talenttree3 [num]
|
||||
9 => [FILTER_CR_STRING, 'g.name', ], // guildname
|
||||
10 => [FILTER_CR_CALLBACK, 'cbHasGuildRank', null, null], // guildrank
|
||||
12 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname2v2
|
||||
15 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname3v3
|
||||
18 => [FILTER_CR_CALLBACK, 'cbTeamName', null, null], // teamname5v5
|
||||
13 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng2v2
|
||||
16 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng3v3
|
||||
19 => [FILTER_CR_CALLBACK, 'cbTeamRating', null, null], // teamrtng5v5
|
||||
14 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib2v2 [num]
|
||||
17 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib3v3 [num]
|
||||
20 => [FILTER_CR_NYI_PH, 0 ], // teamcontrib5v5 [num]
|
||||
21 => [FILTER_CR_CALLBACK, 'cbWearsItems', null, null], // wearingitem [str]
|
||||
23 => [FILTER_CR_CALLBACK, 'cbCompletedAcv', null, null], // completedachievement
|
||||
25 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ALCHEMY, null], // alchemy [num]
|
||||
26 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_BLACKSMITHING, null], // blacksmithing [num]
|
||||
27 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ENCHANTING, null], // enchanting [num]
|
||||
28 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_ENGINEERING, null], // engineering [num]
|
||||
29 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_HERBALISM, null], // herbalism [num]
|
||||
30 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_INSCRIPTION, null], // inscription [num]
|
||||
31 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_JEWELCRAFTING, null], // jewelcrafting [num]
|
||||
32 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_LEATHERWORKING, null], // leatherworking [num]
|
||||
33 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_MINING, null], // mining [num]
|
||||
34 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_SKINNING, null], // skinning [num]
|
||||
35 => [FILTER_CR_CALLBACK, 'cbProfession', SKILL_TAILORING, null], // tailoring [num]
|
||||
36 => [FILTER_CR_CALLBACK, 'cbHasGuild', null, null] // hasguild [yn]
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_RANGE, [1, 36], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 5000]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_CRV, true ], // criteria values
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [parent::V_LIST, [SIDE_ALLIANCE, SIDE_HORDE], false], // side
|
||||
'ra' => [parent::V_LIST, [[1, 8], 10, 11], true ], // race
|
||||
'cl' => [parent::V_LIST, [[1, 9], 11], true ], // class
|
||||
'minle' => [parent::V_RANGE, [1, MAX_LEVEL], false], // min level
|
||||
'maxle' => [parent::V_RANGE, [1, MAX_LEVEL], false], // max level
|
||||
'rg' => [parent::V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [parent::V_CALLBACK, 'cbServerCheck', false], // server
|
||||
);
|
||||
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_RANGE, [1, 36], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 5000]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_REGEX, '/[\p{C}:;%\\\\]/ui', true ], // criteria values
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // only match exact
|
||||
'si' => [FILTER_V_LIST, [1, 2], false], // side
|
||||
'ra' => [FILTER_V_LIST, [[1, 8], 10, 11], true ], // race
|
||||
'cl' => [FILTER_V_LIST, [[1, 9], 11], true ], // class
|
||||
'minle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // min level
|
||||
'maxle' => [FILTER_V_RANGE, [1, MAX_LEVEL], false], // max level
|
||||
'rg' => [FILTER_V_CALLBACK, 'cbRegionCheck', false], // region
|
||||
'sv' => [FILTER_V_CALLBACK, 'cbServerCheck', false], // server
|
||||
);
|
||||
public bool $useLocalList = false;
|
||||
public array $extraOpts = [];
|
||||
|
||||
/* heads up!
|
||||
a couple of filters are too complex to be run against the characters database
|
||||
if they are selected, force useage of LocalProfileList
|
||||
*/
|
||||
|
||||
public function __construct($fromPOST = false, $opts = [])
|
||||
public function __construct(string|array $data, array $opts = [])
|
||||
{
|
||||
if (!empty($opts['realms']))
|
||||
$this->realms = $opts['realms'];
|
||||
else
|
||||
$this->realms = array_keys(Profiler::getRealms());
|
||||
parent::__construct($data, $opts);
|
||||
|
||||
parent::__construct($fromPOST, $opts);
|
||||
|
||||
if (!empty($this->fiData['c']['cr']))
|
||||
if (array_intersect($this->fiData['c']['cr'], [2, 5, 6, 7, 21]))
|
||||
if (!empty($this->criteria['cr']))
|
||||
if (array_intersect($this->criteria['cr'], [2, 5, 6, 7, 21]))
|
||||
$this->useLocalList = true;
|
||||
}
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCR = $this->genericCriterion($cr))
|
||||
return $genCR;
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = $this->fiData['v'];
|
||||
$_v = $this->values;
|
||||
|
||||
// region (rg), battlegroup (bg) and server (sv) are passed to ProflieList as miscData and handled there
|
||||
|
||||
// table key differs between remote and local :<
|
||||
$k = $this->useLocalList ? 'p' : 'c';
|
||||
|
||||
// name [str] - the table is case sensitive. Since i down't want to destroy indizes, lets alter the search terms
|
||||
if (!empty($_v['na']))
|
||||
// name [str] - the table is case sensitive. Since i don't want to destroy indizes, lets alter the search terms
|
||||
if ($_v['na'])
|
||||
{
|
||||
$lower = $this->modularizeString([$k.'.name'], Util::lower($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on', true);
|
||||
$proper = $this->modularizeString([$k.'.name'], Util::ucWords($_v['na']), !empty($_v['ex']) && $_v['ex'] == 'on', true);
|
||||
$lower = $this->tokenizeString([$k.'.name'], Util::lower($_v['na']), $_v['ex'] == 'on', true);
|
||||
$proper = $this->tokenizeString([$k.'.name'], Util::ucWords($_v['na']), $_v['ex'] == 'on', true);
|
||||
|
||||
$parts[] = ['OR', $lower, $proper];
|
||||
}
|
||||
|
||||
// side [list]
|
||||
if (!empty($_v['si']))
|
||||
{
|
||||
if ($_v['si'] == 1)
|
||||
$parts[] = [$k.'.race', [1, 3, 4, 7, 11]];
|
||||
else if ($_v['si'] == 2)
|
||||
$parts[] = [$k.'.race', [2, 5, 6, 8, 10]];
|
||||
}
|
||||
if ($_v['si'] == SIDE_ALLIANCE)
|
||||
$parts[] = [$k.'.race', ChrRace::fromMask(ChrRace::MASK_ALLIANCE)];
|
||||
else if ($_v['si'] == SIDE_HORDE)
|
||||
$parts[] = [$k.'.race', ChrRace::fromMask(ChrRace::MASK_HORDE)];
|
||||
|
||||
// race [list]
|
||||
if (!empty($_v['ra']))
|
||||
if ($_v['ra'])
|
||||
$parts[] = [$k.'.race', $_v['ra']];
|
||||
|
||||
// class [list]
|
||||
if (!empty($_v['cl']))
|
||||
if ($_v['cl'])
|
||||
$parts[] = [$k.'.class', $_v['cl']];
|
||||
|
||||
// min level [int]
|
||||
if (isset($_v['minle']))
|
||||
if ($_v['minle'])
|
||||
$parts[] = [$k.'.level', $_v['minle'], '>='];
|
||||
|
||||
// max level [int]
|
||||
if (isset($_v['maxle']))
|
||||
if ($_v['maxle'])
|
||||
$parts[] = [$k.'.level', $_v['maxle'], '<='];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbRegionCheck(&$v)
|
||||
protected function cbProfession(int $cr, int $crs, string $crv, $skillId) : ?array
|
||||
{
|
||||
if (in_array($v, Util::$regions))
|
||||
{
|
||||
$this->parentCats[0] = $v; // directly redirect onto this region
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbServerCheck(&$v)
|
||||
{
|
||||
foreach (Profiler::getRealms() as $realm)
|
||||
if ($realm['name'] == $v)
|
||||
{
|
||||
$this->parentCats[1] = Profiler::urlize($v);// directly redirect onto this server
|
||||
$v = ''; // remove from filter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbProfession($cr, $skillId)
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
$k = 'sk_'.Util::createHash(12);
|
||||
$col = 'skill'.$skillId;
|
||||
$col = 'skill-'.$skillId;
|
||||
|
||||
$this->formData['extraCols'][$skillId] = $col;
|
||||
$this->fiExtraCols[$skillId] = $col;
|
||||
|
||||
if ($this->useLocalList)
|
||||
{
|
||||
$this->extraOpts[$k] = array(
|
||||
'j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.Type::SKILL.' AND '.$k.'.typeId = '.$skillId.' AND '.$k.'.cur '.$cr[1].' '.$cr[2], true],
|
||||
's' => [', '.$k.'.cur AS '.$col]
|
||||
'j' => [sprintf('?_profiler_completion_skills %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`skillId` = %2$d AND `%1$s`.`value` %3$s %4$d', $k, $skillId, $crs, $crv), true],
|
||||
's' => [', '.$k.'.`value` AS "'.$col.'"']
|
||||
);
|
||||
return [$k.'.typeId', null, '!'];
|
||||
return [$k.'.skillId', null, '!'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->extraOpts[$k] = array(
|
||||
'j' => ['character_skills '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.skill = '.$skillId.' AND '.$k.'.value '.$cr[1].' '.$cr[2], true],
|
||||
's' => [', '.$k.'.value AS '.$col]
|
||||
'j' => [sprintf('character_skills %1$s ON `%1$s`.`guid` = c.`guid` AND `%1$s`.`skill` = %2$d AND `%1$s`.`value` %3$s %4$d', $k, $skillId, $crs, $crv), true],
|
||||
's' => [', '.$k.'.`value` AS "'.$col.'"']
|
||||
);
|
||||
return [$k.'.skill', null, '!'];
|
||||
}
|
||||
}
|
||||
|
||||
protected function cbCompletedAcv($cr)
|
||||
protected function cbCompletedAcv(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_achievement WHERE id = ?d', $cr[2]))
|
||||
return false;
|
||||
if (!Type::validateIds(Type::ACHIEVEMENT, $crv))
|
||||
return null;
|
||||
|
||||
$k = 'acv_'.Util::createHash(12);
|
||||
|
||||
if ($this->useLocalList)
|
||||
{
|
||||
$this->extraOpts[$k] = ['j' => ['?_profiler_completion '.$k.' ON '.$k.'.id = p.id AND '.$k.'.`type` = '.Type::ACHIEVEMENT.' AND '.$k.'.typeId = '.$cr[2], true]];
|
||||
return [$k.'.typeId', null, '!'];
|
||||
$this->extraOpts[$k] = ['j' => [sprintf('?_profiler_completion_achievements %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`achievementId` = %2$d', $k, $crv), true]];
|
||||
return [$k.'.achievementId', null, '!'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->extraOpts[$k] = ['j' => ['character_achievement '.$k.' ON '.$k.'.guid = c.guid AND '.$k.'.achievement = '.$cr[2], true]];
|
||||
$this->extraOpts[$k] = ['j' => [sprintf('character_achievement %1$s ON `%1$s`.`guid` = c.`guid` AND `%1$s`.`achievement` = %2$d', $k, $crv), true]];
|
||||
return [$k.'.achievement', null, '!'];
|
||||
}
|
||||
}
|
||||
|
||||
protected function cbWearsItems($cr)
|
||||
protected function cbWearsItems(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_items WHERE id = ?d', $cr[2]))
|
||||
return false;
|
||||
if (!Type::validateIds(Type::ITEM, $crv))
|
||||
return null;
|
||||
|
||||
$k = 'i_'.Util::createHash(12);
|
||||
|
||||
$this->extraOpts[$k] = ['j' => ['?_profiler_items '.$k.' ON '.$k.'.id = p.id AND '.$k.'.item = '.$cr[2], true]];
|
||||
$this->extraOpts[$k] = ['j' => [sprintf('?_profiler_items %1$s ON `%1$s`.`id` = p.`id` AND `%1$s`.`item` = %2$d', $k, $crv), true]];
|
||||
return [$k.'.item', null, '!'];
|
||||
}
|
||||
|
||||
protected function cbHasGuild($cr)
|
||||
protected function cbHasGuild(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
if ($this->useLocalList)
|
||||
return ['p.guild', null, $cr[1] ? '!' : null];
|
||||
return ['p.guild', null, $crs ? '!' : null];
|
||||
else
|
||||
return ['gm.guildId', null, $cr[1] ? '!' : null];
|
||||
return ['gm.guildId', null, $crs ? '!' : null];
|
||||
}
|
||||
|
||||
protected function cbHasGuildRank($cr)
|
||||
protected function cbHasGuildRank(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
if ($this->useLocalList)
|
||||
return ['p.guildrank', $cr[2], $cr[1]];
|
||||
return ['p.guildrank', $crv, $crs];
|
||||
else
|
||||
return ['gm.rank', $cr[2], $cr[1]];
|
||||
return ['gm.rank', $crv, $crs];
|
||||
}
|
||||
|
||||
protected function cbTeamName($cr)
|
||||
protected function cbTeamName(int $cr, int $crs, string $crv, $size) : ?array
|
||||
{
|
||||
if ($_ = $this->modularizeString(['at.name'], $cr[2]))
|
||||
return ['AND', ['at.type', $this->enums[-1][$cr[0]]], $_];
|
||||
if ($_ = $this->tokenizeString(['at.name'], $crv))
|
||||
return ['AND', ['at.type', $size], $_];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbTeamRating($cr)
|
||||
protected function cbTeamRating(int $cr, int $crs, string $crv, $size) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
return ['AND', ['at.type', $this->enums[-1][$cr[0]]], ['at.rating', $cr[2], $cr[1]]];
|
||||
return ['AND', ['at.type', $size], ['at.rating', $crv, $crs]];
|
||||
}
|
||||
|
||||
protected function cbAchievs($cr)
|
||||
protected function cbAchievs(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
if ($this->useLocalList)
|
||||
return ['p.achievementpoints', $cr[2], $cr[1]];
|
||||
return ['p.achievementpoints', $crv, $crs];
|
||||
else
|
||||
return ['cap.counter', $cr[2], $cr[1]];
|
||||
return ['cap.counter', $crv, $crs];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,12 +479,14 @@ class RemoteProfileList extends ProfileList
|
||||
'at' => [['atm'], 'j' => 'arena_team at ON atm.arenaTeamId = at.arenaTeamId', 's' => ', at.name AS arenateam, IF(at.captainGuid = c.guid, 1, 0) AS captain']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
private $rnItr = []; // rename iterator [name => nCharsWithThisName]
|
||||
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
// select DB by realm
|
||||
if (!$this->selectRealms($miscData))
|
||||
{
|
||||
trigger_error('no access to auth-db or table realmlist is empty', E_USER_WARNING);
|
||||
trigger_error('RemoteProfileList::__construct - cannot access any realm.', E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -550,18 +500,13 @@ class RemoteProfileList extends ProfileList
|
||||
$realms = Profiler::getRealms();
|
||||
$talentSpells = [];
|
||||
$talentLookup = [];
|
||||
$distrib = null;
|
||||
$limit = CFG_SQL_LIMIT_DEFAULT;
|
||||
|
||||
foreach ($conditions as $c)
|
||||
if (is_int($c))
|
||||
$limit = $c;
|
||||
$distrib = [];
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as $guid => &$curTpl)
|
||||
{
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
|
||||
// realm
|
||||
[$r, $g] = explode(':', $guid);
|
||||
@@ -573,7 +518,15 @@ class RemoteProfileList extends ProfileList
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error('character "'.$curTpl['name'].'" belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
trigger_error('char #'.$guid.' belongs to nonexistant realm #'.$r, E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// empty name
|
||||
if (!$curTpl['name'])
|
||||
{
|
||||
trigger_error('char #'.$guid.' on realm #'.$r.' has empty name.', E_USER_WARNING);
|
||||
unset($this->templates[$guid]);
|
||||
continue;
|
||||
}
|
||||
@@ -587,13 +540,10 @@ class RemoteProfileList extends ProfileList
|
||||
$curTpl['activespec'] = $curTpl['activeTalentGroup'];
|
||||
|
||||
// equalize distribution
|
||||
if ($limit != CFG_SQL_LIMIT_NONE)
|
||||
{
|
||||
if (empty($distrib[$curTpl['realm']]))
|
||||
$distrib[$curTpl['realm']] = 1;
|
||||
else
|
||||
$distrib[$curTpl['realm']]++;
|
||||
}
|
||||
|
||||
// char is pending rename
|
||||
if ($curTpl['at_login'] & 0x1)
|
||||
@@ -619,16 +569,21 @@ class RemoteProfileList extends ProfileList
|
||||
|
||||
$talentSpells = DB::Aowow()->select('SELECT spell AS ARRAY_KEY, tab, `rank` FROM ?_talents WHERE class IN (?a)', array_unique($talentSpells));
|
||||
|
||||
if ($distrib !== null)
|
||||
{
|
||||
foreach ($conditions as $c)
|
||||
if (is_int($c))
|
||||
$limit = $c;
|
||||
|
||||
$limit ??= Cfg::get('SQL_LIMIT_DEFAULT');
|
||||
if (!$limit) // int:0 means unlimited, so skip process
|
||||
$distrib = [];
|
||||
|
||||
$total = array_sum($distrib);
|
||||
foreach ($distrib as &$d)
|
||||
$d = ceil($limit * $d / $total);
|
||||
}
|
||||
|
||||
foreach ($this->iterate() as $guid => &$curTpl)
|
||||
{
|
||||
if ($distrib !== null)
|
||||
if ($distrib)
|
||||
{
|
||||
if ($limit <= 0 || $distrib[$curTpl['realm']] <= 0)
|
||||
{
|
||||
@@ -671,8 +626,11 @@ class RemoteProfileList extends ProfileList
|
||||
$baseData = $guildData = [];
|
||||
foreach ($this->iterate() as $guid => $__)
|
||||
{
|
||||
$realmId = $this->getField('realm');
|
||||
$guildGUID = $this->getField('guild');
|
||||
|
||||
$baseData[$guid] = array(
|
||||
'realm' => $this->getField('realm'),
|
||||
'realm' => $realmId,
|
||||
'realmGUID' => $this->getField('guid'),
|
||||
'name' => $this->getField('name'),
|
||||
'renameItr' => $this->getField('renameItr'),
|
||||
@@ -680,15 +638,15 @@ class RemoteProfileList extends ProfileList
|
||||
'class' => $this->getField('class'),
|
||||
'level' => $this->getField('level'),
|
||||
'gender' => $this->getField('gender'),
|
||||
'guild' => $this->getField('guild') ?: null,
|
||||
'guildrank' => $this->getField('guild') ? $this->getField('guildrank') : null,
|
||||
'guild' => $guildGUID ?: null,
|
||||
'guildrank' => $guildGUID ? $this->getField('guildrank') : null,
|
||||
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
|
||||
);
|
||||
|
||||
if ($this->getField('guild'))
|
||||
$guildData[] = array(
|
||||
'realm' => $this->getField('realm'),
|
||||
'realmGUID' => $this->getField('guild'),
|
||||
if ($guildGUID && empty($guildData[$realmId.'-'.$guildGUID]))
|
||||
$guildData[$realmId.'-'.$guildGUID] = array(
|
||||
'realm' => $realmId,
|
||||
'realmGUID' => $guildGUID,
|
||||
'name' => $this->getField('guildname'),
|
||||
'nameUrl' => Profiler::urlize($this->getField('guildname')),
|
||||
'cuFlags' => PROFILER_CU_NEEDS_RESYNC
|
||||
@@ -699,7 +657,7 @@ class RemoteProfileList extends ProfileList
|
||||
if ($guildData)
|
||||
{
|
||||
foreach (Util::createSqlBatchInsert($guildData) as $ins)
|
||||
DB::Aowow()->query('INSERT IGNORE INTO ?_profiler_guild (?#) VALUES '.$ins, array_keys(reset($guildData)));
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_guild (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE `id` = `id`', array_keys(reset($guildData)));
|
||||
|
||||
// merge back local ids
|
||||
$localGuilds = DB::Aowow()->selectCol('SELECT realm AS ARRAY_KEY, realmGUID AS ARRAY_KEY2, id FROM ?_profiler_guild WHERE realm IN (?a) AND realmGUID IN (?a)',
|
||||
@@ -714,8 +672,8 @@ class RemoteProfileList extends ProfileList
|
||||
// basic char data (enough for tooltips)
|
||||
if ($baseData)
|
||||
{
|
||||
foreach (Util::createSqlBatchInsert($baseData) as $ins)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES '.$ins.' ON DUPLICATE KEY UPDATE name = VALUES(name), renameItr = VALUES(renameItr)', array_keys(reset($baseData)));
|
||||
foreach ($baseData as $ins)
|
||||
DB::Aowow()->query('INSERT INTO ?_profiler_profiles (?#) VALUES (?a) ON DUPLICATE KEY UPDATE `name` = ?, `renameItr` = ?d', array_keys($ins), array_values($ins), $ins['name'], $ins['renameItr']);
|
||||
|
||||
// merge back local ids
|
||||
$localIds = DB::Aowow()->select(
|
||||
@@ -744,33 +702,45 @@ class LocalProfileList extends ProfileList
|
||||
'g' => ['j' => ['?_profiler_guild g ON g.id = p.guild', true], 's' => ', g.name AS guildname']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
// graft realm selection from miscData onto conditions
|
||||
$realmIds = [];
|
||||
if (isset($miscData['sv']))
|
||||
$realmIds = array_keys(array_filter($realms, fn($x) => Profiler::urlize($x['name']) == Profiler::urlize($miscData['sv'])));
|
||||
|
||||
if (isset($miscData['rg']))
|
||||
$realmIds = array_merge($realmIds, array_keys(array_filter($realms, fn($x) => $x['region'] == $miscData['rg'])));
|
||||
|
||||
if ($conditions && $realmIds)
|
||||
{
|
||||
array_unshift($conditions, 'AND');
|
||||
$conditions = ['AND', ['realm', $realmIds], $conditions];
|
||||
}
|
||||
else if ($realmIds)
|
||||
$conditions = [['realm', $realmIds]];
|
||||
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
if ($this->error)
|
||||
return;
|
||||
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
// post processing
|
||||
$acvPoints = DB::Aowow()->selectCol('SELECT pc.id AS ARRAY_KEY, SUM(a.points) FROM ?_profiler_completion pc LEFT JOIN ?_achievement a ON a.id = pc.typeId WHERE pc.`type` = ?d AND pc.id IN (?a) GROUP BY pc.id', Type::ACHIEVEMENT, $this->getFoundIDs());
|
||||
|
||||
foreach ($this->iterate() as $id => &$curTpl)
|
||||
{
|
||||
if ($curTpl['realm'] && !isset($realms[$curTpl['realm']]))
|
||||
if (!$curTpl['realm']) // custom profile w/o realminfo
|
||||
continue;
|
||||
|
||||
if (isset($realms[$curTpl['realm']]))
|
||||
if (!isset($realms[$curTpl['realm']]))
|
||||
{
|
||||
$curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
|
||||
$curTpl['region'] = $realms[$curTpl['realm']]['region'];
|
||||
unset($this->templates[$id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// battlegroup
|
||||
$curTpl['battlegroup'] = CFG_BATTLEGROUP;
|
||||
|
||||
$curTpl['achievementpoints'] = isset($acvPoints[$id]) ? $acvPoints[$id] : 0;
|
||||
$curTpl['realmName'] = $realms[$curTpl['realm']]['name'];
|
||||
$curTpl['region'] = $realms[$curTpl['realm']]['region'];
|
||||
$curTpl['battlegroup'] = Cfg::get('BATTLEGROUP');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -22,7 +24,7 @@ class QuestList extends BaseType
|
||||
'e' => ['j' => ['?_events e ON e.id = `q`.eventId', true], 's' => ', e.holidayId']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
@@ -148,12 +150,15 @@ class QuestList extends BaseType
|
||||
return in_array($this->getField('zoneOrSortBak'), [-22, -284, -366, -369, -370, -376, -374]) && !$this->isRepeatable();
|
||||
}
|
||||
|
||||
public function getSourceData()
|
||||
public function getSourceData(int $id = 0) : array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
if ($id && $id != $this->id)
|
||||
continue;
|
||||
|
||||
$data[$this->id] = array(
|
||||
"n" => $this->getField('name', true),
|
||||
"t" => Type::QUEST,
|
||||
@@ -172,7 +177,7 @@ class QuestList extends BaseType
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
if (!(Game::sideByRaceMask($this->curTpl['reqRaceMask']) & $side))
|
||||
if (!(ChrRace::sideFromMask($this->curTpl['reqRaceMask']) & $side))
|
||||
continue;
|
||||
|
||||
[$series, $first] = DB::Aowow()->SelectRow(
|
||||
@@ -208,8 +213,8 @@ class QuestList extends BaseType
|
||||
'id' => $this->id,
|
||||
'level' => $this->curTpl['level'],
|
||||
'reqlevel' => $this->curTpl['minLevel'],
|
||||
'name' => $this->getField('name', true),
|
||||
'side' => Game::sideByRaceMask($this->curTpl['reqRaceMask']),
|
||||
'name' => Lang::unescapeUISequences($this->getField('name', true), Lang::FMT_RAW),
|
||||
'side' => ChrRace::sideFromMask($this->curTpl['reqRaceMask']),
|
||||
'wflags' => 0x0,
|
||||
'xp' => $this->curTpl['rewardXP']
|
||||
);
|
||||
@@ -235,8 +240,8 @@ class QuestList extends BaseType
|
||||
if ($_ = $this->curTpl['reqClassMask'])
|
||||
$data[$this->id]['reqclass'] = $_;
|
||||
|
||||
if ($_ = ($this->curTpl['reqRaceMask'] & RACE_MASK_ALL))
|
||||
if ((($_ & RACE_MASK_ALLIANCE) != RACE_MASK_ALLIANCE) && (($_ & RACE_MASK_HORDE) != RACE_MASK_HORDE))
|
||||
if ($_ = ($this->curTpl['reqRaceMask'] & ChrRace::MASK_ALL))
|
||||
if ((($_ & ChrRace::MASK_ALLIANCE) != ChrRace::MASK_ALLIANCE) && (($_ & ChrRace::MASK_HORDE) != ChrRace::MASK_HORDE))
|
||||
$data[$this->id]['reqrace'] = $_;
|
||||
|
||||
if ($_ = $this->curTpl['rewardOrReqMoney'])
|
||||
@@ -313,7 +318,7 @@ class QuestList extends BaseType
|
||||
if (!$this->curTpl)
|
||||
return null;
|
||||
|
||||
$title = htmlentities($this->getField('name', true));
|
||||
$title = Lang::unescapeUISequences(Util::htmlEscape($this->getField('name', true)), Lang::FMT_HTML);
|
||||
$level = $this->curTpl['level'];
|
||||
if ($level < 0)
|
||||
$level = 0;
|
||||
@@ -348,7 +353,7 @@ class QuestList extends BaseType
|
||||
if ($ot)
|
||||
$name = $ot;
|
||||
else
|
||||
$name = $rng > 0 ? CreatureList::getName($rng) : GameObjectList::getName(-$rng);
|
||||
$name = $rng > 0 ? CreatureList::getName($rng) : Lang::unescapeUISequences(GameObjectList::getName(-$rng), Lang::FMT_HTML);
|
||||
|
||||
$xReq .= '<br /> - '.$name.($rngQty > 1 ? ' x '.$rngQty : null);
|
||||
}
|
||||
@@ -361,7 +366,7 @@ class QuestList extends BaseType
|
||||
if (!$ri || $riQty < 1)
|
||||
continue;
|
||||
|
||||
$xReq .= '<br /> - '.ItemList::getName($ri).($riQty > 1 ? ' x '.$riQty : null);
|
||||
$xReq .= '<br /> - '.Lang::unescapeUISequences(ItemList::getName($ri), Lang::FMT_HTML).($riQty > 1 ? ' x '.$riQty : null);
|
||||
}
|
||||
|
||||
if ($et = $this->getField('end', true))
|
||||
@@ -424,292 +429,291 @@ class QuestList extends BaseType
|
||||
|
||||
class QuestListFilter extends Filter
|
||||
{
|
||||
public $extraOpts = [];
|
||||
protected $enums = array( // massive enums could be put here, if you want to restrict inputs further to be valid IDs instead of just integers
|
||||
37 => [null, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, 11, true, false],
|
||||
38 => [null, 1, 2, 3, 4, 5, 6, 7, 8, null, 10, 11, true, false],
|
||||
);
|
||||
protected $genericFilter = array(
|
||||
1 => [FILTER_CR_CALLBACK, 'cbReputation', '>', null], // increasesrepwith
|
||||
2 => [FILTER_CR_NUMERIC, 'rewardXP', NUM_CAST_INT ], // experiencegained
|
||||
3 => [FILTER_CR_NUMERIC, 'rewardOrReqMoney', NUM_CAST_INT ], // moneyrewarded
|
||||
4 => [FILTER_CR_CALLBACK, 'cbSpellRewards', null, null], // spellrewarded [yn]
|
||||
5 => [FILTER_CR_FLAG, 'flags', QUEST_FLAG_SHARABLE ], // sharable
|
||||
6 => [FILTER_CR_NUMERIC, 'timeLimit', NUM_CAST_INT ], // timer
|
||||
7 => [FILTER_CR_NYI_PH, null, 1 ], // firstquestseries
|
||||
9 => [FILTER_CR_CALLBACK, 'cbEarnReputation', null, null], // objectiveearnrepwith [enum]
|
||||
10 => [FILTER_CR_CALLBACK, 'cbReputation', '<', null], // decreasesrepwith
|
||||
11 => [FILTER_CR_NUMERIC, 'suggestedPlayers', NUM_CAST_INT ], // suggestedplayers
|
||||
15 => [FILTER_CR_NYI_PH, null, 1 ], // lastquestseries
|
||||
16 => [FILTER_CR_NYI_PH, null, 1 ], // partseries
|
||||
18 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
19 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 0x1, null], // startsfrom [enum]
|
||||
21 => [FILTER_CR_CALLBACK, 'cbQuestRelation', 0x2, null], // endsat [enum]
|
||||
22 => [FILTER_CR_CALLBACK, 'cbItemRewards', null, null], // itemrewards [op] [int]
|
||||
23 => [FILTER_CR_CALLBACK, 'cbItemChoices', null, null], // itemchoices [op] [int]
|
||||
24 => [FILTER_CR_CALLBACK, 'cbLacksStartEnd', null, null], // lacksstartend [yn]
|
||||
25 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
27 => [FILTER_CR_FLAG, 'flags', QUEST_FLAG_DAILY ], // daily
|
||||
28 => [FILTER_CR_FLAG, 'flags', QUEST_FLAG_WEEKLY ], // weekly
|
||||
29 => [FILTER_CR_FLAG, 'flags', QUEST_FLAG_REPEATABLE ], // repeatable
|
||||
30 => [FILTER_CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
33 => [FILTER_CR_ENUM, 'e.holidayId' ], // relatedevent
|
||||
34 => [FILTER_CR_CALLBACK, 'cbAvailable', null, null], // availabletoplayers [yn]
|
||||
36 => [FILTER_CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
37 => [FILTER_CR_CALLBACK, 'cbClassSpec', null, null], // classspecific [enum]
|
||||
38 => [FILTER_CR_CALLBACK, 'cbRaceSpec', null, null], // racespecific [enum]
|
||||
42 => [FILTER_CR_STAFFFLAG, 'flags' ], // flags
|
||||
43 => [FILTER_CR_CALLBACK, 'cbCurrencyReward', null, null], // currencyrewarded [enum]
|
||||
44 => [FILTER_CR_CALLBACK, 'cbLoremaster', null, null], // countsforloremaster_stc [yn]
|
||||
45 => [FILTER_CR_BOOLEAN, 'rewardTitleId' ] // titlerewarded
|
||||
protected string $type = 'quests';
|
||||
protected array $enums = array(
|
||||
37 => parent::ENUM_CLASSS, // classspecific
|
||||
38 => parent::ENUM_RACE, // racespecific
|
||||
9 => parent::ENUM_FACTION, // objectiveearnrepwith
|
||||
33 => parent::ENUM_EVENT, // relatedevent
|
||||
43 => parent::ENUM_CURRENCY, // currencyrewarded
|
||||
1 => parent::ENUM_FACTION, // increasesrepwith
|
||||
10 => parent::ENUM_FACTION // decreasesrepwith
|
||||
);
|
||||
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'cr' => [FILTER_V_RANGE, [1, 45], true ], // criteria ids
|
||||
'crs' => [FILTER_V_LIST, [FILTER_ENUM_NONE, FILTER_ENUM_ANY, [0, 99999]], true ], // criteria operators
|
||||
'crv' => [FILTER_V_REGEX, '/\D/', true ], // criteria values - only numerals
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name / text - only printable chars, no delimiter
|
||||
'ex' => [FILTER_V_EQUAL, 'on', false], // also match subname
|
||||
'ma' => [FILTER_V_EQUAL, 1, false], // match any / all filter
|
||||
'minle' => [FILTER_V_RANGE, [1, 99], false], // min quest level
|
||||
'maxle' => [FILTER_V_RANGE, [1, 99], false], // max quest level
|
||||
'minrl' => [FILTER_V_RANGE, [1, 99], false], // min required level
|
||||
'maxrl' => [FILTER_V_RANGE, [1, 99], false], // max required level
|
||||
'si' => [FILTER_V_LIST, [-2, -1, 1, 2, 3], false], // siede
|
||||
'ty' => [FILTER_V_LIST, [0, 1, 21, 41, 62, [81, 85], 88, 89], true ] // type
|
||||
protected array $genericFilter = array(
|
||||
1 => [parent::CR_CALLBACK, 'cbReputation', '>', null], // increasesrepwith
|
||||
2 => [parent::CR_NUMERIC, 'rewardXP', NUM_CAST_INT ], // experiencegained
|
||||
3 => [parent::CR_NUMERIC, 'rewardOrReqMoney', NUM_CAST_INT ], // moneyrewarded
|
||||
4 => [parent::CR_CALLBACK, 'cbSpellRewards', null, null], // spellrewarded [yn]
|
||||
5 => [parent::CR_FLAG, 'flags', QUEST_FLAG_SHARABLE ], // sharable
|
||||
6 => [parent::CR_NUMERIC, 'timeLimit', NUM_CAST_INT ], // timer
|
||||
7 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_FIRST_SERIES ], // firstquestseries
|
||||
9 => [parent::CR_CALLBACK, 'cbEarnReputation', null, null], // objectiveearnrepwith [enum]
|
||||
10 => [parent::CR_CALLBACK, 'cbReputation', '<', null], // decreasesrepwith
|
||||
11 => [parent::CR_NUMERIC, 'suggestedPlayers', NUM_CAST_INT ], // suggestedplayers
|
||||
15 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_LAST_SERIES ], // lastquestseries
|
||||
16 => [parent::CR_FLAG, 'cuFlags', QUEST_CU_PART_OF_SERIES ], // partseries
|
||||
18 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_SCREENSHOT ], // hasscreenshots
|
||||
19 => [parent::CR_CALLBACK, 'cbQuestRelation', 0x1, null], // startsfrom [enum]
|
||||
21 => [parent::CR_CALLBACK, 'cbQuestRelation', 0x2, null], // endsat [enum]
|
||||
22 => [parent::CR_CALLBACK, 'cbItemRewards', null, null], // itemrewards [op] [int]
|
||||
23 => [parent::CR_CALLBACK, 'cbItemChoices', null, null], // itemchoices [op] [int]
|
||||
24 => [parent::CR_CALLBACK, 'cbLacksStartEnd', null, null], // lacksstartend [yn]
|
||||
25 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_COMMENT ], // hascomments
|
||||
27 => [parent::CR_FLAG, 'flags', QUEST_FLAG_DAILY ], // daily
|
||||
28 => [parent::CR_FLAG, 'flags', QUEST_FLAG_WEEKLY ], // weekly
|
||||
29 => [parent::CR_CALLBACK, 'cbRepeatable', null ], // repeatable
|
||||
30 => [parent::CR_NUMERIC, 'id', NUM_CAST_INT, true], // id
|
||||
33 => [parent::CR_ENUM, 'e.holidayId', true, true], // relatedevent
|
||||
34 => [parent::CR_CALLBACK, 'cbAvailable', null, null], // availabletoplayers [yn]
|
||||
36 => [parent::CR_FLAG, 'cuFlags', CUSTOM_HAS_VIDEO ], // hasvideos
|
||||
37 => [parent::CR_CALLBACK, 'cbClassSpec', null, null], // classspecific [enum]
|
||||
38 => [parent::CR_CALLBACK, 'cbRaceSpec', null, null], // racespecific [enum]
|
||||
42 => [parent::CR_STAFFFLAG, 'flags' ], // flags
|
||||
43 => [parent::CR_CALLBACK, 'cbCurrencyReward', null, null], // currencyrewarded [enum]
|
||||
44 => [parent::CR_CALLBACK, 'cbLoremaster', null, null], // countsforloremaster_stc [yn]
|
||||
45 => [parent::CR_BOOLEAN, 'rewardTitleId' ] // titlerewarded
|
||||
);
|
||||
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
if (in_array($cr[0], array_keys($this->genericFilter)))
|
||||
if ($genCr = $this->genericCriterion($cr))
|
||||
return $genCr;
|
||||
protected array $inputFields = array(
|
||||
'cr' => [parent::V_RANGE, [1, 45], true ], // criteria ids
|
||||
'crs' => [parent::V_LIST, [parent::ENUM_NONE, parent::ENUM_ANY, [0, 99999]], true ], // criteria operators
|
||||
'crv' => [parent::V_REGEX, parent::PATTERN_INT, true ], // criteria values - only numerals
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name / text - only printable chars, no delimiter
|
||||
'ex' => [parent::V_EQUAL, 'on', false], // also match subname
|
||||
'ma' => [parent::V_EQUAL, 1, false], // match any / all filter
|
||||
'minle' => [parent::V_RANGE, [1, 99], false], // min quest level
|
||||
'maxle' => [parent::V_RANGE, [1, 99], false], // max quest level
|
||||
'minrl' => [parent::V_RANGE, [1, 99], false], // min required level
|
||||
'maxrl' => [parent::V_RANGE, [1, 99], false], // max required level
|
||||
'si' => [parent::V_LIST, [-SIDE_HORDE, -SIDE_ALLIANCE, SIDE_ALLIANCE, SIDE_HORDE, SIDE_BOTH], false], // side
|
||||
'ty' => [parent::V_LIST, [0, 1, 21, 41, 62, [81, 85], 88, 89], true ] // type
|
||||
);
|
||||
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
public array $extraOpts = [];
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = $this->fiData['v'];
|
||||
$_v = $this->values;
|
||||
|
||||
// name
|
||||
if (isset($_v['na']))
|
||||
if ($_v['na'])
|
||||
{
|
||||
$_ = [];
|
||||
if (isset($_v['ex']) && $_v['ex'] == 'on')
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId, 'objectives_loc'.User::$localeId, 'details_loc'.User::$localeId]);
|
||||
if ($_v['ex'] == 'on')
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value, 'objectives_loc'.Lang::getLocale()->value, 'details_loc'.Lang::getLocale()->value]);
|
||||
else
|
||||
$_ = $this->modularizeString(['name_loc'.User::$localeId]);
|
||||
$_ = $this->tokenizeString(['name_loc'.Lang::getLocale()->value]);
|
||||
|
||||
if ($_)
|
||||
$parts[] = $_;
|
||||
}
|
||||
|
||||
// level min
|
||||
if (isset($_v['minle']))
|
||||
if ($_v['minle'])
|
||||
$parts[] = ['level', $_v['minle'], '>=']; // not considering quests that are always at player level (-1)
|
||||
|
||||
// level max
|
||||
if (isset($_v['maxle']))
|
||||
if ($_v['maxle'])
|
||||
$parts[] = ['level', $_v['maxle'], '<='];
|
||||
|
||||
// reqLevel min
|
||||
if (isset($_v['minrl']))
|
||||
if ($_v['minrl'])
|
||||
$parts[] = ['minLevel', $_v['minrl'], '>=']; // ignoring maxLevel
|
||||
|
||||
// reqLevel max
|
||||
if (isset($_v['maxrl']))
|
||||
if ($_v['maxrl'])
|
||||
$parts[] = ['minLevel', $_v['maxrl'], '<=']; // ignoring maxLevel
|
||||
|
||||
// side
|
||||
if (isset($_v['si']))
|
||||
if ($_v['si'])
|
||||
{
|
||||
$ex = [['reqRaceMask', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'];
|
||||
$notEx = ['OR', ['reqRaceMask', 0], [['reqRaceMask', RACE_MASK_ALL, '&'], RACE_MASK_ALL]];
|
||||
$excl = [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL, '!'];
|
||||
$incl = ['OR', ['reqRaceMask', 0], [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL]];
|
||||
|
||||
switch ($_v['si'])
|
||||
$parts[] = match ($_v['si'])
|
||||
{
|
||||
case 3:
|
||||
$parts[] = $notEx;
|
||||
break;
|
||||
case 2:
|
||||
$parts[] = ['OR', $notEx, ['reqRaceMask', RACE_MASK_HORDE, '&']];
|
||||
break;
|
||||
case -2:
|
||||
$parts[] = ['AND', $ex, ['reqRaceMask', RACE_MASK_HORDE, '&']];
|
||||
break;
|
||||
case 1:
|
||||
$parts[] = ['OR', $notEx, ['reqRaceMask', RACE_MASK_ALLIANCE, '&']];
|
||||
break;
|
||||
case -1:
|
||||
$parts[] = ['AND', $ex, ['reqRaceMask', RACE_MASK_ALLIANCE, '&']];
|
||||
break;
|
||||
}
|
||||
SIDE_BOTH => $incl,
|
||||
SIDE_HORDE => ['OR', $incl, ['reqRaceMask', ChrRace::MASK_HORDE, '&']],
|
||||
-SIDE_HORDE => ['AND', $excl, ['reqRaceMask', ChrRace::MASK_HORDE, '&']],
|
||||
SIDE_ALLIANCE => ['OR', $incl, ['reqRaceMask', ChrRace::MASK_ALLIANCE, '&']],
|
||||
-SIDE_ALLIANCE => ['AND', $excl, ['reqRaceMask', ChrRace::MASK_ALLIANCE, '&']]
|
||||
};
|
||||
}
|
||||
|
||||
// type [list]
|
||||
if (isset($_v['ty']))
|
||||
if ($_v['ty'] !== null)
|
||||
$parts[] = ['type', $_v['ty']];
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function cbReputation($cr, $sign)
|
||||
protected function cbReputation(int $cr, int $crs, string $crv, string $sign) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT) || $cr[1] <= 0)
|
||||
return false;
|
||||
if (!Util::checkNumeric($crs, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE id = ?d', $cr[1]))
|
||||
$this->formData['reputationCols'][] = [$cr[1], Util::localizedString($_, 'name')];
|
||||
if (!in_array($crs, $this->enums[$cr]))
|
||||
return null;
|
||||
|
||||
if ($_ = DB::Aowow()->selectRow('SELECT * FROM ?_factions WHERE `id` = ?d', $crs))
|
||||
$this->fiReputationCols[] = [$crs, Util::localizedString($_, 'name')];
|
||||
|
||||
return [
|
||||
'OR',
|
||||
['AND', ['rewardFactionId1', $cr[1]], ['rewardFactionValue1', 0, $sign]],
|
||||
['AND', ['rewardFactionId2', $cr[1]], ['rewardFactionValue2', 0, $sign]],
|
||||
['AND', ['rewardFactionId3', $cr[1]], ['rewardFactionValue3', 0, $sign]],
|
||||
['AND', ['rewardFactionId4', $cr[1]], ['rewardFactionValue4', 0, $sign]],
|
||||
['AND', ['rewardFactionId5', $cr[1]], ['rewardFactionValue5', 0, $sign]]
|
||||
['AND', ['rewardFactionId1', $crs], ['rewardFactionValue1', 0, $sign]],
|
||||
['AND', ['rewardFactionId2', $crs], ['rewardFactionValue2', 0, $sign]],
|
||||
['AND', ['rewardFactionId3', $crs], ['rewardFactionValue3', 0, $sign]],
|
||||
['AND', ['rewardFactionId4', $crs], ['rewardFactionValue4', 0, $sign]],
|
||||
['AND', ['rewardFactionId5', $crs], ['rewardFactionValue5', 0, $sign]]
|
||||
];
|
||||
}
|
||||
|
||||
protected function cbQuestRelation($cr, $flags)
|
||||
protected function cbQuestRelation(int $cr, int $crs, string $crv, $flags) : ?array
|
||||
{
|
||||
switch ($cr[1])
|
||||
return match ($crs)
|
||||
{
|
||||
case 1: // npc
|
||||
return ['AND', ['qse.type', Type::NPC], ['qse.method', $flags, '&']];
|
||||
case 2: // object
|
||||
return ['AND', ['qse.type', Type::OBJECT], ['qse.method', $flags, '&']];
|
||||
case 3: // item
|
||||
return ['AND', ['qse.type', Type::ITEM], ['qse.method', $flags, '&']];
|
||||
Type::NPC,
|
||||
Type::OBJECT,
|
||||
Type::ITEM => ['AND', ['qse.type', $crs], ['qse.method', $flags, '&']],
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function cbCurrencyReward($cr)
|
||||
protected function cbCurrencyReward(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT) || $cr[1] <= 0)
|
||||
return false;
|
||||
if (!Util::checkNumeric($crs, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if (!in_array($crs, $this->enums[$cr]))
|
||||
return null;
|
||||
|
||||
return [
|
||||
'OR',
|
||||
['rewardItemId1', $cr[1]], ['rewardItemId2', $cr[1]], ['rewardItemId3', $cr[1]], ['rewardItemId4', $cr[1]],
|
||||
['rewardChoiceItemId1', $cr[1]], ['rewardChoiceItemId2', $cr[1]], ['rewardChoiceItemId3', $cr[1]], ['rewardChoiceItemId4', $cr[1]], ['rewardChoiceItemId5', $cr[1]], ['rewardChoiceItemId6', $cr[1]]
|
||||
['rewardItemId1', $crs], ['rewardItemId2', $crs], ['rewardItemId3', $crs], ['rewardItemId4', $crs],
|
||||
['rewardChoiceItemId1', $crs], ['rewardChoiceItemId2', $crs], ['rewardChoiceItemId3', $crs], ['rewardChoiceItemId4', $crs], ['rewardChoiceItemId5', $crs], ['rewardChoiceItemId6', $crs]
|
||||
];
|
||||
}
|
||||
|
||||
protected function cbAvailable($cr)
|
||||
protected function cbAvailable(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
if ($cr[1])
|
||||
if ($crs)
|
||||
return [['cuFlags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0];
|
||||
else
|
||||
return ['cuFlags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'];
|
||||
}
|
||||
|
||||
protected function cbItemChoices($cr)
|
||||
protected function cbRepeatable(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
$this->extraOpts['q']['s'][] = ', (IF(rewardChoiceItemId1, 1, 0) + IF(rewardChoiceItemId2, 1, 0) + IF(rewardChoiceItemId3, 1, 0) + IF(rewardChoiceItemId4, 1, 0) + IF(rewardChoiceItemId5, 1, 0) + IF(rewardChoiceItemId6, 1, 0)) as numChoices';
|
||||
$this->extraOpts['q']['h'][] = 'numChoices '.$cr[1].' '.$cr[2];
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function cbItemRewards($cr)
|
||||
{
|
||||
if (!Util::checkNumeric($cr[2], NUM_CAST_INT) || !$this->int2Op($cr[1]))
|
||||
return false;
|
||||
|
||||
$this->extraOpts['q']['s'][] = ', (IF(rewardItemId1, 1, 0) + IF(rewardItemId2, 1, 0) + IF(rewardItemId3, 1, 0) + IF(rewardItemId4, 1, 0)) as numRewards';
|
||||
$this->extraOpts['q']['h'][] = 'numRewards '.$cr[1].' '.$cr[2];
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function cbLoremaster($cr)
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
|
||||
if ($cr[1])
|
||||
return ['AND', ['zoneOrSort', 0, '>'], [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE , '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY , '&'], 0]];
|
||||
if ($crs)
|
||||
return ['OR', ['flags', QUEST_FLAG_REPEATABLE, '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE, '&']];
|
||||
else
|
||||
return ['OR', ['zoneOrSort', 0, '<'], ['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE , '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY , '&']];;
|
||||
return ['AND', [['flags', QUEST_FLAG_REPEATABLE, '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE, '&'], 0]];
|
||||
}
|
||||
|
||||
protected function cbSpellRewards($cr)
|
||||
protected function cbItemChoices(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
if ($cr[1])
|
||||
return ['OR', ['sourceSpellId', 0, '>'], ['rewardSpell', 0, '>'], ['rsc.effect1Id', SpellList::$effects['teach']], ['rsc.effect2Id', SpellList::$effects['teach']], ['rsc.effect3Id', SpellList::$effects['teach']]];
|
||||
$this->extraOpts['q']['s'][] = ', (IF(`rewardChoiceItemId1`, 1, 0) + IF(`rewardChoiceItemId2`, 1, 0) + IF(`rewardChoiceItemId3`, 1, 0) + IF(`rewardChoiceItemId4`, 1, 0) + IF(`rewardChoiceItemId5`, 1, 0) + IF(`rewardChoiceItemId6`, 1, 0)) AS "numChoices"';
|
||||
$this->extraOpts['q']['h'][] = '`numChoices` '.$crs.' '.$crv;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function cbItemRewards(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($crv, NUM_CAST_INT) || !$this->int2Op($crs))
|
||||
return null;
|
||||
|
||||
$this->extraOpts['q']['s'][] = ', (IF(`rewardItemId1`, 1, 0) + IF(`rewardItemId2`, 1, 0) + IF(`rewardItemId3`, 1, 0) + IF(`rewardItemId4`, 1, 0)) AS "numRewards"';
|
||||
$this->extraOpts['q']['h'][] = '`numRewards` '.$crs.' '.$crv;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function cbLoremaster(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
if ($crs)
|
||||
return ['AND', ['zoneOrSort', 0, '>'], [['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], 0], [['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&'], 0]];
|
||||
else
|
||||
return ['OR', ['zoneOrSort', 0, '<'], ['flags', QUEST_FLAG_DAILY | QUEST_FLAG_WEEKLY | QUEST_FLAG_REPEATABLE, '&'], ['specialFlags', QUEST_FLAG_SPECIAL_REPEATABLE | QUEST_FLAG_SPECIAL_MONTHLY, '&']];
|
||||
}
|
||||
|
||||
protected function cbSpellRewards(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
if ($crs)
|
||||
return ['OR', ['sourceSpellId', 0, '>'], ['rewardSpell', 0, '>'], ['rsc.effect1Id', SpellList::EFFECTS_TEACH], ['rsc.effect2Id', SpellList::EFFECTS_TEACH], ['rsc.effect3Id', SpellList::EFFECTS_TEACH]];
|
||||
else
|
||||
return ['AND', ['sourceSpellId', 0], ['rewardSpell', 0], ['rewardSpellCast', 0]];
|
||||
}
|
||||
|
||||
protected function cbEarnReputation($cr)
|
||||
protected function cbEarnReputation(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!Util::checkNumeric($cr[1], NUM_REQ_INT))
|
||||
return false;
|
||||
if (!Util::checkNumeric($crs, NUM_CAST_INT))
|
||||
return null;
|
||||
|
||||
if ($cr[1] > 0)
|
||||
return ['OR', ['reqFactionId1', $cr[1]], ['reqFactionId2', $cr[1]]];
|
||||
else if ($cr[1] == FILTER_ENUM_ANY) // any
|
||||
if ($crs == parent::ENUM_ANY)
|
||||
return ['OR', ['reqFactionId1', 0, '>'], ['reqFactionId2', 0, '>']];
|
||||
else if ($cr[1] == FILTER_ENUM_NONE) // none
|
||||
else if ($crs == parent::ENUM_NONE)
|
||||
return ['AND', ['reqFactionId1', 0], ['reqFactionId2', 0]];
|
||||
else if (in_array($crs, $this->enums[$cr]))
|
||||
return ['OR', ['reqFactionId1', $crs], ['reqFactionId2', $crs]];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbClassSpec($cr)
|
||||
protected function cbClassSpec(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!isset($this->enums[$cr[0]][$cr[1]]))
|
||||
return false;
|
||||
if (!isset($this->enums[$cr][$crs]))
|
||||
return null;
|
||||
|
||||
$_ = $this->enums[$cr[0]][$cr[1]];
|
||||
$_ = $this->enums[$cr][$crs];
|
||||
if ($_ === true)
|
||||
return ['AND', ['reqClassMask', 0, '!'], [['reqClassMask', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!']];
|
||||
return ['AND', ['reqClassMask', 0, '!'], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL, '!']];
|
||||
else if ($_ === false)
|
||||
return ['OR', ['reqClassMask', 0], [['reqClassMask', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL]];
|
||||
return ['OR', ['reqClassMask', 0], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL]];
|
||||
else if (is_int($_))
|
||||
return ['AND', ['reqClassMask', (1 << ($_ - 1)), '&'], [['reqClassMask', CLASS_MASK_ALL, '&'], CLASS_MASK_ALL, '!']];
|
||||
return ['AND', ['reqClassMask', ChrClass::from($_)->toMask(), '&'], [['reqClassMask', ChrClass::MASK_ALL, '&'], ChrClass::MASK_ALL, '!']];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbRaceSpec($cr)
|
||||
protected function cbRaceSpec(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!isset($this->enums[$cr[0]][$cr[1]]))
|
||||
return false;
|
||||
if (!isset($this->enums[$cr][$crs]))
|
||||
return null;
|
||||
|
||||
$_ = $this->enums[$cr[0]][$cr[1]];
|
||||
$_ = $this->enums[$cr][$crs];
|
||||
if ($_ === true)
|
||||
return ['AND', ['reqRaceMask', 0, '!'], [['reqRaceMask', RACE_MASK_ALL, '&'], RACE_MASK_ALL, '!'], [['reqRaceMask', RACE_MASK_ALLIANCE, '&'], RACE_MASK_ALLIANCE, '!'], [['reqRaceMask', RACE_MASK_HORDE, '&'], RACE_MASK_HORDE, '!']];
|
||||
return ['AND', ['reqRaceMask', 0, '!'], [['reqRaceMask', ChrRace::MASK_ALL, '&'], ChrRace::MASK_ALL, '!'], [['reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ChrRace::MASK_ALLIANCE, '!'], [['reqRaceMask', ChrRace::MASK_HORDE, '&'], ChrRace::MASK_HORDE, '!']];
|
||||
else if ($_ === false)
|
||||
return ['OR', ['reqRaceMask', 0], ['reqRaceMask', RACE_MASK_ALL], ['reqRaceMask', RACE_MASK_ALLIANCE], ['reqRaceMask', RACE_MASK_HORDE]];
|
||||
return ['OR', ['reqRaceMask', 0], ['reqRaceMask', ChrRace::MASK_ALL], ['reqRaceMask', ChrRace::MASK_ALLIANCE], ['reqRaceMask', ChrRace::MASK_HORDE]];
|
||||
else if (is_int($_))
|
||||
return ['AND', ['reqRaceMask', (1 << ($_ - 1)), '&'], [['reqRaceMask', RACE_MASK_ALLIANCE, '&'], RACE_MASK_ALLIANCE, '!'], [['reqRaceMask', RACE_MASK_HORDE, '&'], RACE_MASK_HORDE, '!']];
|
||||
return ['AND', ['reqRaceMask', ChrRace::from($_)->toMask(), '&'], [['reqRaceMask', ChrRace::MASK_ALLIANCE, '&'], ChrRace::MASK_ALLIANCE, '!'], [['reqRaceMask', ChrRace::MASK_HORDE, '&'], ChrRace::MASK_HORDE, '!']];
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function cbLacksStartEnd($cr)
|
||||
protected function cbLacksStartEnd(int $cr, int $crs, string $crv) : ?array
|
||||
{
|
||||
if (!$this->int2Bool($cr[1]))
|
||||
return false;
|
||||
if (!$this->int2Bool($crs))
|
||||
return null;
|
||||
|
||||
$missing = DB::Aowow()->selectCol('SELECT questId, max(method) a, min(method) b FROM ?_quests_startend GROUP BY questId HAVING (a | b) <> 3');
|
||||
if ($cr[1])
|
||||
$missing = DB::Aowow()->selectCol('SELECT `questId`, BIT_OR(`method`) AS "se" FROM ?_quests_startend GROUP BY `questId` HAVING "se" <> 3');
|
||||
if ($crs)
|
||||
return ['id', $missing];
|
||||
else
|
||||
return ['id', $missing, '!'];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -16,9 +18,9 @@ class SkillList extends BaseType
|
||||
'ic' => ['j' => ['?_icons ic ON ic.id = sl.iconId', true], 's' => ', ic.name AS iconString'],
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as &$_curTpl)
|
||||
@@ -34,7 +36,7 @@ class SkillList extends BaseType
|
||||
}
|
||||
|
||||
if (!$_curTpl['iconId'])
|
||||
$_curTpl['iconString'] = 'inv_misc_questionmark';
|
||||
$_curTpl['iconString'] = DEFAULT_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -21,9 +23,9 @@ class SoundList extends BaseType
|
||||
SOUND_TYPE_MP3 => 'audio/mpeg'
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as $id => &$_curTpl)
|
||||
@@ -43,17 +45,17 @@ class SoundList extends BaseType
|
||||
|
||||
if ($this->fileBuffer)
|
||||
{
|
||||
$files = DB::Aowow()->select('SELECT id AS ARRAY_KEY, `id`, `file` AS title, `type`, `path` FROM ?_sounds_files sf WHERE id IN (?a)', array_keys($this->fileBuffer));
|
||||
$files = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `id`, `file` AS "title", CAST(`type` AS UNSIGNED) AS "type", `path` FROM ?_sounds_files sf WHERE `id` IN (?a)', array_keys($this->fileBuffer));
|
||||
foreach ($files as $id => $data)
|
||||
{
|
||||
// 3.3.5 bandaid - need fullpath to play via wow API, remove for cata and later
|
||||
$data['path'] = str_replace('\\', '\\\\', $data['path'] ? $data['path'] . '\\' . $data['title'] : $data['title']);
|
||||
// skipp file extension
|
||||
// skip file extension
|
||||
$data['title'] = substr($data['title'], 0, -4);
|
||||
// enum to string
|
||||
$data['type'] = self::$fileTypes[$data['type']];
|
||||
// get real url
|
||||
$data['url'] = STATIC_URL . '/wowsounds/' . $data['id'];
|
||||
$data['url'] = Cfg::get('STATIC_URL') . '/wowsounds/' . $data['id'];
|
||||
// v push v
|
||||
$this->fileBuffer[$id] = $data;
|
||||
}
|
||||
@@ -96,32 +98,24 @@ class SoundList extends BaseType
|
||||
|
||||
class SoundListFilter extends Filter
|
||||
{
|
||||
// fieldId => [checkType, checkValue[, fieldIsArray]]
|
||||
protected $inputFields = array(
|
||||
'na' => [FILTER_V_REGEX, '/[\p{C};%\\\\]/ui', false], // name - only printable chars, no delimiter
|
||||
'ty' => [FILTER_V_LIST, [[1, 4], 6, 9, 10, 12, 13, 14, 16, 17, [19, 23], [25, 31], 50, 52, 53], true ] // type
|
||||
protected string $type = 'sounds';
|
||||
protected array $inputFields = array(
|
||||
'na' => [parent::V_REGEX, parent::PATTERN_NAME, false], // name - only printable chars, no delimiter
|
||||
'ty' => [parent::V_LIST, [[1, 4], 6, 9, 10, 12, 13, 14, 16, 17, [19, 31], 50, 52, 53], true ] // type
|
||||
);
|
||||
|
||||
// we have no criteria for this one...
|
||||
protected function createSQLForCriterium(&$cr)
|
||||
{
|
||||
unset($cr);
|
||||
$this->error = true;
|
||||
return [1];
|
||||
}
|
||||
|
||||
protected function createSQLForValues()
|
||||
protected function createSQLForValues() : array
|
||||
{
|
||||
$parts = [];
|
||||
$_v = &$this->fiData['v'];
|
||||
$_v = &$this->values;
|
||||
|
||||
// name [str]
|
||||
if (isset($_v['na']))
|
||||
if ($_ = $this->modularizeString(['name']))
|
||||
if ($_v['na'])
|
||||
if ($_ = $this->tokenizeString(['name']))
|
||||
$parts[] = $_;
|
||||
|
||||
// type [list]
|
||||
if (isset($_v['ty']))
|
||||
if ($_v['ty'])
|
||||
$parts[] = ['cat', $_v['ty']];
|
||||
|
||||
return $parts;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -14,40 +16,41 @@ class TitleList extends BaseType
|
||||
|
||||
public $sources = [];
|
||||
|
||||
protected $queryBase = 'SELECT t.*, id AS ARRAY_KEY FROM ?_titles t';
|
||||
protected $queryBase = 'SELECT t.*, t.id AS ARRAY_KEY FROM ?_titles t';
|
||||
protected $queryOpts = array(
|
||||
't' => [['src']], // 11: Type::TITLE
|
||||
'src' => ['j' => ['?_source src ON type = 11 AND typeId = t.id', true], 's' => ', src13, moreType, moreTypeId']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// post processing
|
||||
foreach ($this->iterate() as $id => &$_curTpl)
|
||||
{
|
||||
// preparse sources - notice: under this system titles can't have more than one source (or two for achivements), which is enough for standard TC cases but may break custom cases
|
||||
if ($_curTpl['moreType'] == Type::ACHIEVEMENT)
|
||||
$this->sources[$this->id][12][] = $_curTpl['moreTypeId'];
|
||||
$this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['moreTypeId'];
|
||||
else if ($_curTpl['moreType'] == Type::QUEST)
|
||||
$this->sources[$this->id][4][] = $_curTpl['moreTypeId'];
|
||||
$this->sources[$this->id][SRC_QUEST][] = $_curTpl['moreTypeId'];
|
||||
else if ($_curTpl['src13'])
|
||||
$this->sources[$this->id][13][] = $_curTpl['src13'];
|
||||
$this->sources[$this->id][SRC_CUSTOM_STRING][] = $_curTpl['src13'];
|
||||
|
||||
// titles display up to two achievements at once
|
||||
if ($_curTpl['src12Ext'])
|
||||
$this->sources[$this->id][12][] = $_curTpl['src12Ext'];
|
||||
$this->sources[$this->id][SRC_ACHIEVEMENT][] = $_curTpl['src12Ext'];
|
||||
|
||||
unset($_curTpl['src12Ext']);
|
||||
unset($_curTpl['moreType']);
|
||||
unset($_curTpl['moreTypeId']);
|
||||
unset($_curTpl['src3']);
|
||||
|
||||
// shorthand for more generic access
|
||||
foreach (Util::$localeStrings as $i => $str)
|
||||
if ($str)
|
||||
$_curTpl['name_loc'.$i] = trim(str_replace('%s', '', $_curTpl['male_loc'.$i]));
|
||||
// shorthand for more generic access; required by CommunityContent to determine subject
|
||||
foreach (Locale::cases() as $loc)
|
||||
if ($loc->validate())
|
||||
$_curTpl['name'] = new LocString($_curTpl, 'male', fn($x) => trim(str_replace('%s', '', $x)));
|
||||
// $_curTpl['name_loc'.$loc->value] = trim(str_replace('%s', '', $_curTpl['male_loc'.$loc->value]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +96,9 @@ class TitleList extends BaseType
|
||||
private function createSource()
|
||||
{
|
||||
$sources = array(
|
||||
4 => [], // Quest
|
||||
12 => [], // Achievements
|
||||
13 => [] // simple text
|
||||
SRC_QUEST => [],
|
||||
SRC_ACHIEVEMENT => [],
|
||||
SRC_CUSTOM_STRING => []
|
||||
);
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
@@ -109,43 +112,43 @@ class TitleList extends BaseType
|
||||
}
|
||||
|
||||
// fill in the details
|
||||
if (!empty($sources[4]))
|
||||
$sources[4] = (new QuestList(array(['id', $sources[4]])))->getSourceData();
|
||||
if (!empty($sources[SRC_QUEST]))
|
||||
$sources[SRC_QUEST] = (new QuestList(array(['id', $sources[SRC_QUEST]])))->getSourceData();
|
||||
|
||||
if (!empty($sources[12]))
|
||||
$sources[12] = (new AchievementList(array(['id', $sources[12]])))->getSourceData();
|
||||
if (!empty($sources[SRC_ACHIEVEMENT]))
|
||||
$sources[SRC_ACHIEVEMENT] = (new AchievementList(array(['id', $sources[SRC_ACHIEVEMENT]])))->getSourceData();
|
||||
|
||||
foreach ($this->sources as $Id => $src)
|
||||
{
|
||||
$tmp = [];
|
||||
|
||||
// Quest-source
|
||||
if (isset($src[4]))
|
||||
if (isset($src[SRC_QUEST]))
|
||||
{
|
||||
foreach ($src[4] as $s)
|
||||
foreach ($src[SRC_QUEST] as $s)
|
||||
{
|
||||
if (isset($sources[4][$s]['s']))
|
||||
$this->faction2Side($sources[4][$s]['s']);
|
||||
if (isset($sources[SRC_QUEST][$s]['s']))
|
||||
$this->faction2Side($sources[SRC_QUEST][$s]['s']);
|
||||
|
||||
$tmp[4][] = $sources[4][$s];
|
||||
$tmp[SRC_QUEST][] = $sources[SRC_QUEST][$s];
|
||||
}
|
||||
}
|
||||
|
||||
// Achievement-source
|
||||
if (isset($src[12]))
|
||||
if (isset($src[SRC_ACHIEVEMENT]))
|
||||
{
|
||||
foreach ($src[12] as $s)
|
||||
foreach ($src[SRC_ACHIEVEMENT] as $s)
|
||||
{
|
||||
if (isset($sources[12][$s]['s']))
|
||||
$this->faction2Side($sources[12][$s]['s']);
|
||||
if (isset($sources[SRC_ACHIEVEMENT][$s]['s']))
|
||||
$this->faction2Side($sources[SRC_ACHIEVEMENT][$s]['s']);
|
||||
|
||||
$tmp[12][] = $sources[12][$s];
|
||||
$tmp[SRC_ACHIEVEMENT][] = $sources[SRC_ACHIEVEMENT][$s];
|
||||
}
|
||||
}
|
||||
|
||||
// other source (only one item possible, so no iteration needed)
|
||||
if (isset($src[13]))
|
||||
$tmp[13] = [Lang::game('pvpSources', $this->sources[$Id][13][0])];
|
||||
if (isset($src[SRC_CUSTOM_STRING]))
|
||||
$tmp[SRC_CUSTOM_STRING] = [Lang::game('pvpSources', $Id)];
|
||||
|
||||
$this->templates[$Id]['source'] = $tmp;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -9,13 +11,14 @@ class UserList extends BaseType
|
||||
public static $type = Type::USER;
|
||||
public static $brickFile = 'user';
|
||||
public static $dataTable = ''; // doesn't have community content
|
||||
public static $contribute = CONTRIBUTE_NONE;
|
||||
|
||||
public $sources = [];
|
||||
|
||||
protected $queryBase = 'SELECT *, a.id AS ARRAY_KEY FROM ?_account a';
|
||||
protected $queryOpts = array(
|
||||
'a' => [['r']],
|
||||
'r' => ['j' => ['?_account_reputation r ON r.userId = a.id', true], 's' => ', IFNULL(SUM(r.amount), 0) AS reputation', 'g' => 'a.id']
|
||||
'r' => ['j' => ['?_account_reputation r ON r.`userId` = a.`id`', true], 's' => ', IFNULL(SUM(r.`amount`), 0) AS "reputation"', 'g' => 'a.`id`']
|
||||
);
|
||||
|
||||
public function getListviewData() { }
|
||||
@@ -26,7 +29,7 @@ class UserList extends BaseType
|
||||
|
||||
foreach ($this->iterate() as $__)
|
||||
{
|
||||
$data[$this->curTpl['displayName']] = array(
|
||||
$data[$this->curTpl['username']] = array(
|
||||
'border' => 0, // border around avatar (rarityColors)
|
||||
'roles' => $this->curTpl['userGroups'],
|
||||
'joined' => date(Util::$dateFormatInternal, $this->curTpl['joinDate']),
|
||||
@@ -37,19 +40,19 @@ class UserList extends BaseType
|
||||
'reputation' => $this->curTpl['reputation']
|
||||
);
|
||||
|
||||
// custom titles (only ssen on user page..?)
|
||||
// custom titles (only seen on user page..?)
|
||||
if ($_ = $this->curTpl['title'])
|
||||
$data[$this->curTpl['displayName']]['title'] = $_;
|
||||
$data[$this->curTpl['username']]['title'] = $_;
|
||||
|
||||
if ($_ = $this->curTpl['avatar'])
|
||||
{
|
||||
$data[$this->curTpl['displayName']]['avatar'] = is_numeric($_) ? 2 : 1;
|
||||
$data[$this->curTpl['displayName']]['avatarmore'] = $_;
|
||||
$data[$this->curTpl['username']]['avatar'] = is_numeric($_) ? 2 : 1;
|
||||
$data[$this->curTpl['username']]['avatarmore'] = $_;
|
||||
}
|
||||
|
||||
// more optional data
|
||||
// sig: markdown formated string (only used in forum?)
|
||||
// border: seen as null|1|3 .. changes the border around the avatar (i suspect its meaning changed and got decupled from premium-status with the introduction of patron-status)
|
||||
// border: seen as null|1|3 .. changes the border around the avatar (i suspect its meaning changed and got decupled from premium-status with the introduction of patreon-status)
|
||||
}
|
||||
|
||||
return [Type::USER => $data];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -10,15 +12,15 @@ class WorldEventList extends BaseType
|
||||
public static $brickFile = 'event';
|
||||
public static $dataTable = '?_events';
|
||||
|
||||
protected $queryBase = 'SELECT e.*, h.*, e.description AS nameINT, e.id AS id, e.id AS ARRAY_KEY FROM ?_events e';
|
||||
protected $queryBase = 'SELECT e.holidayId, e.cuFlags, e.startTime, e.endTime, e.occurence, e.length, e.requires, e.description AS nameINT, e.id AS eventId, e.id AS ARRAY_KEY, h.* FROM ?_events e';
|
||||
protected $queryOpts = array(
|
||||
'e' => [['h']],
|
||||
'h' => ['j' => ['?_holidays h ON e.holidayId = h.id', true], 'o' => '-e.id ASC']
|
||||
);
|
||||
|
||||
public function __construct($conditions = [])
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions);
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
// unseting elements while we iterate over the array will cause the pointer to reset
|
||||
$replace = [];
|
||||
@@ -60,7 +62,7 @@ class WorldEventList extends BaseType
|
||||
foreach ($replace as $old => $data)
|
||||
{
|
||||
unset($this->templates[$old]);
|
||||
$this->templates[$data['id']] = $data;
|
||||
$this->templates[$data['eventId']] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -12,9 +14,9 @@ class ZoneList extends BaseType
|
||||
public static $brickFile = 'zone';
|
||||
public static $dataTable = '?_zones';
|
||||
|
||||
protected $queryBase = 'SELECT z.*, id AS ARRAY_KEY FROM ?_zones z';
|
||||
protected $queryBase = 'SELECT z.*, z.id AS ARRAY_KEY FROM ?_zones z';
|
||||
|
||||
public function __construct($conditions = [], $miscData = null)
|
||||
public function __construct(array $conditions = [], array $miscData = [])
|
||||
{
|
||||
parent::__construct($conditions, $miscData);
|
||||
|
||||
|
||||
@@ -1,146 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
class User
|
||||
{
|
||||
public static $id = 0;
|
||||
public static $displayName = '';
|
||||
public static $banStatus = 0x0; // see ACC_BAN_* defines
|
||||
public static $groups = 0x0;
|
||||
public static $perms = 0;
|
||||
public static $localeId = 0;
|
||||
public static $localeString = 'enus';
|
||||
public static $avatar = 'inv_misc_questionmark';
|
||||
public static $dailyVotes = 0;
|
||||
public static $ip = null;
|
||||
public static int $id = 0;
|
||||
public static string $username = '';
|
||||
public static int $banStatus = 0x0; // see ACC_BAN_* defines
|
||||
public static int $status = 0x0;
|
||||
public static int $groups = 0x0;
|
||||
public static int $perms = 0;
|
||||
public static ?string $email = null;
|
||||
public static int $dailyVotes = 0;
|
||||
public static bool $debug = false; // show ids in lists (used to be debug, is now user setting)
|
||||
public static ?string $ip = null;
|
||||
public static ?string $agent = null;
|
||||
public static Locale $preferedLoc;
|
||||
|
||||
private static $reputation = 0;
|
||||
private static $dataKey = '';
|
||||
private static $expires = false;
|
||||
private static $passHash = '';
|
||||
private static $excludeGroups = 1;
|
||||
private static $profiles = null;
|
||||
private static int $reputation = 0;
|
||||
private static string $dataKey = '';
|
||||
private static int $excludeGroups = 1;
|
||||
private static ?LocalProfileList $profiles = null;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::setIP();
|
||||
self::setLocale();
|
||||
|
||||
if (isset($_SESSION['locale']) && $_SESSION['locale'] instanceof Locale)
|
||||
self::$preferedLoc = $_SESSION['locale']->validate() ?? Locale::getFallback();
|
||||
else if (!empty($_SERVER["HTTP_ACCEPT_LANGUAGE"]) && ($loc = Locale::tryFromHttpAcceptLanguage($_SERVER["HTTP_ACCEPT_LANGUAGE"])))
|
||||
self::$preferedLoc = $loc;
|
||||
else
|
||||
self::$preferedLoc = Locale::getFallback();
|
||||
|
||||
// session have a dataKey to access the JScripts (yes, also the anons)
|
||||
if (empty($_SESSION['dataKey']))
|
||||
$_SESSION['dataKey'] = Util::createHash(); // just some random numbers for identifictaion purpose
|
||||
$_SESSION['dataKey'] = Util::createHash(); // just some random numbers for identification purpose
|
||||
|
||||
self::$dataKey = $_SESSION['dataKey'];
|
||||
self::$agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
if (!self::$ip)
|
||||
return false;
|
||||
|
||||
// check IP bans
|
||||
if ($ipBan = DB::Aowow()->selectRow('SELECT count, unbanDate FROM ?_account_bannedips WHERE ip = ? AND type = 0', self::$ip))
|
||||
if ($ipBan = DB::Aowow()->selectRow('SELECT `count`, IF(`unbanDate` > UNIX_TIMESTAMP(), 1, 0) AS "active" FROM ?_account_bannedips WHERE `ip` = ? AND `type` = 0', self::$ip))
|
||||
{
|
||||
if ($ipBan['count'] > CFG_ACC_FAILED_AUTH_COUNT && $ipBan['unbanDate'] > time())
|
||||
if ($ipBan['count'] > Cfg::get('ACC_FAILED_AUTH_COUNT') && $ipBan['active'])
|
||||
return false;
|
||||
else if ($ipBan['unbanDate'] <= time())
|
||||
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE ip = ?', self::$ip);
|
||||
else if (!$ipBan['active'])
|
||||
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE `ip` = ?', self::$ip);
|
||||
}
|
||||
|
||||
// try to restore session
|
||||
if (empty($_SESSION['user']))
|
||||
return false;
|
||||
|
||||
// timed out...
|
||||
if (!empty($_SESSION['timeout']) && $_SESSION['timeout'] <= time())
|
||||
return false;
|
||||
|
||||
$query = DB::Aowow()->SelectRow('
|
||||
SELECT a.id, a.passHash, a.displayName, a.locale, a.userGroups, a.userPerms, a.allowExpire, BIT_OR(ab.typeMask) AS bans, IFNULL(SUM(r.amount), 0) as reputation, a.avatar, a.dailyVotes, a.excludeGroups
|
||||
$session = DB::Aowow()->selectRow('SELECT `userId`, `expires` FROM ?_account_sessions WHERE `status` = ?d AND `sessionId` = ?', SESSION_ACTIVE, session_id());
|
||||
$userData = DB::Aowow()->selectRow(
|
||||
'SELECT a.`id`, a.`passHash`, a.`username`, a.`locale`, a.`userGroups`, a.`userPerms`, BIT_OR(ab.`typeMask`) AS "bans", IFNULL(SUM(r.`amount`), 0) AS "reputation", a.`dailyVotes`, a.`excludeGroups`, a.`status`, a.`statusTimer`, a.`email`
|
||||
FROM ?_account a
|
||||
LEFT JOIN ?_account_banned ab ON a.id = ab.userId AND ab.end > UNIX_TIMESTAMP()
|
||||
LEFT JOIN ?_account_reputation r ON a.id = r.userId
|
||||
WHERE a.id = ?d
|
||||
GROUP BY a.id',
|
||||
LEFT JOIN ?_account_banned ab ON a.`id` = ab.`userId` AND ab.`end` > UNIX_TIMESTAMP()
|
||||
LEFT JOIN ?_account_reputation r ON a.`id` = r.`userId`
|
||||
WHERE a.`id` = ?d
|
||||
GROUP BY a.`id`',
|
||||
$_SESSION['user']
|
||||
);
|
||||
|
||||
if (!$query)
|
||||
return false;
|
||||
|
||||
// password changed, terminate session
|
||||
if (AUTH_MODE_SELF && $query['passHash'] != $_SESSION['hash'])
|
||||
if (!$session || !$userData)
|
||||
{
|
||||
self::destroy();
|
||||
return false;
|
||||
}
|
||||
else if ($session['expires'] && $session['expires'] < time())
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `sessionId` = ?', time(), SESSION_EXPIRED, session_id());
|
||||
self::destroy();
|
||||
return false;
|
||||
}
|
||||
else if ($session['userId'] != $userData['id']) // what in the name of fuck..?
|
||||
{
|
||||
// Don't know why, don't know how .. doesn't matter, both parties are out.
|
||||
DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `userId` IN (?a) AND `status` = ?d', time(), SESSION_FORCED_LOGOUT, [$userData['id'], $session['userId']], SESSION_ACTIVE);
|
||||
trigger_error('User::init - tried to resume session "'.session_id().'" of user #'.$_SESSION['user'].' linked to session data for user #'.$session['userId'].' Kicked both!', E_USER_ERROR);
|
||||
self::destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$id = intval($query['id']);
|
||||
self::$displayName = $query['displayName'];
|
||||
self::$passHash = $query['passHash'];
|
||||
self::$expires = (bool)$query['allowExpire'];
|
||||
self::$reputation = $query['reputation'];
|
||||
self::$banStatus = $query['bans'];
|
||||
self::$groups = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userGroups']);
|
||||
self::$perms = $query['bans'] & (ACC_BAN_TEMP | ACC_BAN_PERM) ? 0 : intval($query['userPerms']);
|
||||
self::$dailyVotes = $query['dailyVotes'];
|
||||
self::$excludeGroups = $query['excludeGroups'];
|
||||
DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `expires` = IF(`expires`, ?d, 0) WHERE `sessionId` = ?', time(), time() + Cfg::get('SESSION_TIMEOUT_DELAY'), session_id());
|
||||
|
||||
$conditions = array(
|
||||
[['cuFlags', PROFILER_CU_DELETED, '&'], 0],
|
||||
['OR', ['user', self::$id], ['ap.accountId', self::$id]]
|
||||
);
|
||||
if ($loc = Locale::tryFrom($userData['locale']))
|
||||
self::$preferedLoc = $loc;
|
||||
|
||||
if (self::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
|
||||
array_shift($conditions);
|
||||
// reset expired account statuses
|
||||
if ($userData['statusTimer'] < time() && $userData['status'] > ACC_STATUS_NEW)
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account SET `status` = ?d, `statusTimer` = 0, `token` = "", `updateValue` = "" WHERE `id` = ?d', ACC_STATUS_OK, User::$id);
|
||||
$userData['status'] = ACC_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/*******************************/
|
||||
/* past here we are logged in */
|
||||
/*******************************/
|
||||
|
||||
self::$id = intVal($userData['id']);
|
||||
self::$username = $userData['username'];
|
||||
self::$reputation = $userData['reputation'];
|
||||
self::$banStatus = $userData['bans'];
|
||||
self::$groups = self::isBanned() ? 0 : intval($userData['userGroups']);
|
||||
self::$perms = self::isBanned() ? 0 : intval($userData['userPerms']);
|
||||
self::$dailyVotes = $userData['dailyVotes'];
|
||||
self::$excludeGroups = $userData['excludeGroups'];
|
||||
self::$status = $userData['status'];
|
||||
// self::$debug = $userData['debug']; // TBD
|
||||
self::$email = $userData['email'];
|
||||
|
||||
$conditions = [['OR', ['user', self::$id], ['ap.accountId', self::$id]]];
|
||||
if (!self::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU))
|
||||
$conditions[] = [['cuFlags', PROFILER_CU_DELETED, '&'], 0];
|
||||
|
||||
self::$profiles = (new LocalProfileList($conditions));
|
||||
|
||||
if ($query['avatar'])
|
||||
self::$avatar = $query['avatar'];
|
||||
|
||||
if (self::$localeId != $query['locale']) // reset, if changed
|
||||
self::setLocale(intVal($query['locale']));
|
||||
|
||||
// stuff, that updates on a daily basis goes here (if you keep you session alive indefinitly, the signin-handler doesn't do very much)
|
||||
// - conscutive visits
|
||||
// - consecutive visits
|
||||
// - votes per day
|
||||
// - reputation for daily visit
|
||||
if (self::$id)
|
||||
if (!self::isBanned())
|
||||
{
|
||||
$lastLogin = DB::Aowow()->selectCell('SELECT curLogin FROM ?_account WHERE id = ?d', self::$id);
|
||||
$lastLogin = DB::Aowow()->selectCell('SELECT `curLogin` FROM ?_account WHERE `id` = ?d', self::$id);
|
||||
// either the day changed or the last visit was >24h ago
|
||||
if (date('j', $lastLogin) != date('j') || (time() - $lastLogin) > 1 * DAY)
|
||||
{
|
||||
// daily votes (we need to reset this one)
|
||||
self::$dailyVotes = self::getMaxDailyVotes();
|
||||
|
||||
DB::Aowow()->query('
|
||||
UPDATE ?_account
|
||||
SET dailyVotes = ?d, prevLogin = curLogin, curLogin = UNIX_TIMESTAMP(), prevIP = curIP, curIP = ?
|
||||
WHERE id = ?d',
|
||||
DB::Aowow()->query(
|
||||
'UPDATE ?_account
|
||||
SET `dailyVotes` = ?d, `prevLogin` = `curLogin`, `curLogin` = UNIX_TIMESTAMP(), `prevIP` = `curIP`, `curIP` = ?
|
||||
WHERE `id` = ?d',
|
||||
self::$dailyVotes,
|
||||
self::$ip,
|
||||
self::$id
|
||||
);
|
||||
|
||||
// gain rep for daily visit
|
||||
if (!(self::$banStatus & (ACC_BAN_TEMP | ACC_BAN_PERM)) && !self::isInGroup(U_GROUP_PENDING))
|
||||
if (!(self::isBanned()) && !self::isInGroup(U_GROUP_PENDING))
|
||||
Util::gainSiteReputation(self::$id, SITEREP_ACTION_DAILYVISIT);
|
||||
|
||||
// increment consecutive visits (next day or first of new month and not more than 48h)
|
||||
// i bet my ass i forgott a corner case
|
||||
// i bet my ass i forgot a corner case
|
||||
if ((date('j', $lastLogin) + 1 == date('j') || (date('j') == 1 && date('n', $lastLogin) != date('n'))) && (time() - $lastLogin) < 2 * DAY)
|
||||
DB::Aowow()->query('UPDATE ?_account SET consecutiveVisits = consecutiveVisits + 1 WHERE id = ?d', self::$id);
|
||||
DB::Aowow()->query('UPDATE ?_account SET `consecutiveVisits` = `consecutiveVisits` + 1 WHERE `id` = ?d', self::$id);
|
||||
else
|
||||
DB::Aowow()->query('UPDATE ?_account SET consecutiveVisits = 0 WHERE id = ?d', self::$id);
|
||||
DB::Aowow()->query('UPDATE ?_account SET `consecutiveVisits` = 0 WHERE `id` = ?d', self::$id);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function setIP()
|
||||
private static function setIP() : void
|
||||
{
|
||||
$ipAddr = '';
|
||||
$method = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
|
||||
@@ -165,120 +190,103 @@ class User
|
||||
self::$ip = $ipAddr ?: null;
|
||||
}
|
||||
|
||||
/****************/
|
||||
/* set language */
|
||||
/****************/
|
||||
|
||||
// set and save
|
||||
public static function setLocale($set = -1)
|
||||
public static function save(bool $toDB = false)
|
||||
{
|
||||
$loc = LOCALE_EN;
|
||||
$_SESSION['user'] = self::$id;
|
||||
$_SESSION['locale'] = self::$preferedLoc;
|
||||
// $_SESSION['dataKey'] does not depend on user login status and is set in User::init()
|
||||
|
||||
// get
|
||||
if ($set != -1 && isset(Util::$localeStrings[$set]))
|
||||
$loc = $set;
|
||||
else if (isset($_SESSION['locale']) && isset(Util::$localeStrings[$_SESSION['locale']]))
|
||||
$loc = $_SESSION['locale'];
|
||||
else if (!empty($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
|
||||
if (self::isLoggedIn() && $toDB)
|
||||
DB::Aowow()->query('UPDATE ?_account SET `locale` = ? WHERE `id` = ?', self::$preferedLoc->value, self::$id);
|
||||
}
|
||||
|
||||
public static function destroy()
|
||||
{
|
||||
$loc = strtolower(substr($_SERVER["HTTP_ACCEPT_LANGUAGE"], 0, 2));
|
||||
switch ($loc) {
|
||||
case 'fr': $loc = LOCALE_FR; break;
|
||||
case 'de': $loc = LOCALE_DE; break;
|
||||
case 'zh': $loc = LOCALE_CN; break; // may cause issues in future with zh-tw
|
||||
case 'es': $loc = LOCALE_ES; break;
|
||||
case 'ru': $loc = LOCALE_RU; break;
|
||||
default: $loc = LOCALE_EN;
|
||||
}
|
||||
session_regenerate_id(true); // session itself is not destroyed; status changed => regenerate id
|
||||
session_unset();
|
||||
|
||||
$_SESSION['locale'] = self::$preferedLoc; // keep locale
|
||||
$_SESSION['dataKey'] = self::$dataKey; // keep dataKey
|
||||
|
||||
self::$id = 0;
|
||||
self::$username = '';
|
||||
self::$perms = 0;
|
||||
self::$groups = U_GROUP_NONE;
|
||||
}
|
||||
|
||||
// check; pick first viable if failed
|
||||
if (CFG_LOCALES && !(CFG_LOCALES & (1 << $loc)))
|
||||
{
|
||||
foreach (Util::$localeStrings as $idx => $__)
|
||||
{
|
||||
if (CFG_LOCALES & (1 << $idx))
|
||||
{
|
||||
$loc = $idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set
|
||||
if (self::$id)
|
||||
DB::Aowow()->query('UPDATE ?_account SET locale = ? WHERE id = ?', $loc, self::$id);
|
||||
|
||||
self::useLocale($loc);
|
||||
}
|
||||
|
||||
// only use once
|
||||
public static function useLocale($use)
|
||||
{
|
||||
self::$localeId = isset(Util::$localeStrings[$use]) ? $use : LOCALE_EN;
|
||||
self::$localeString = self::localeString(self::$localeId);
|
||||
}
|
||||
|
||||
private static function localeString($loc = -1)
|
||||
{
|
||||
if (!isset(Util::$localeStrings[$loc]))
|
||||
$loc = 0;
|
||||
|
||||
return Util::$localeStrings[$loc];
|
||||
}
|
||||
|
||||
/*******************/
|
||||
/* auth mechanisms */
|
||||
/*******************/
|
||||
|
||||
public static function Auth($name, $pass)
|
||||
public static function authenticate(string $login, string $password) : int
|
||||
{
|
||||
$user = 0;
|
||||
$hash = '';
|
||||
$userId = 0;
|
||||
|
||||
switch (CFG_ACC_AUTH_MODE)
|
||||
$result = match (Cfg::get('ACC_AUTH_MODE'))
|
||||
{
|
||||
case AUTH_MODE_SELF:
|
||||
AUTH_MODE_SELF => self::authSelf($login, $password, $userId),
|
||||
AUTH_MODE_REALM => self::authRealm($login, $password, $userId),
|
||||
AUTH_MODE_EXTERNAL => self::authExtern($login, $password, $userId),
|
||||
default => AUTH_INTERNAL_ERR
|
||||
};
|
||||
|
||||
// also banned? its a feature block, not login block..
|
||||
if ($result == AUTH_OK || $result == AUTH_BANNED)
|
||||
{
|
||||
session_unset();
|
||||
$_SESSION['user'] = $userId;
|
||||
self::$id = $userId;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function authSelf(string $nameOrEmail, string $password, int &$userId) : int
|
||||
{
|
||||
if (!self::$ip)
|
||||
return AUTH_INTERNAL_ERR;
|
||||
|
||||
// handle login try limitation
|
||||
$ip = DB::Aowow()->selectRow('SELECT ip, count, unbanDate FROM ?_account_bannedips WHERE type = 0 AND ip = ?', self::$ip);
|
||||
if (!$ip || $ip['unbanDate'] < time()) // no entry exists or time expired; set count to 1
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 0, 1, UNIX_TIMESTAMP() + ?d)', self::$ip, CFG_ACC_FAILED_AUTH_BLOCK);
|
||||
$ipBan = DB::Aowow()->selectRow('SELECT `ip`, `count`, IF(`unbanDate` > UNIX_TIMESTAMP(), 1, 0) AS "active" FROM ?_account_bannedips WHERE `type` = 0 AND `ip` = ?', self::$ip);
|
||||
if (!$ipBan || !$ipBan['active']) // no entry exists or time expired; set count to 1
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (`ip`, `type`, `count`, `unbanDate`) VALUES (?, 0, 1, UNIX_TIMESTAMP() + ?d)', self::$ip, Cfg::get('ACC_FAILED_AUTH_BLOCK'));
|
||||
else // entry already exists; increment count
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ?', CFG_ACC_FAILED_AUTH_BLOCK, self::$ip);
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ?', Cfg::get('ACC_FAILED_AUTH_BLOCK'), self::$ip);
|
||||
|
||||
if ($ip && $ip['count'] >= CFG_ACC_FAILED_AUTH_COUNT && $ip['unbanDate'] >= time())
|
||||
if ($ipBan && $ipBan['count'] >= Cfg::get('ACC_FAILED_AUTH_COUNT') && $ipBan['active'])
|
||||
return AUTH_IPBANNED;
|
||||
|
||||
$query = DB::Aowow()->SelectRow('
|
||||
SELECT a.id, a.passHash, BIT_OR(ab.typeMask) AS bans, a.status
|
||||
$email = filter_var($nameOrEmail, FILTER_VALIDATE_EMAIL);
|
||||
|
||||
$query = DB::Aowow()->SelectRow(
|
||||
'SELECT a.`id`, a.`passHash`, BIT_OR(ab.`typeMask`) AS "bans", a.`status`
|
||||
FROM ?_account a
|
||||
LEFT JOIN ?_account_banned ab ON a.id = ab.userId AND ab.end > UNIX_TIMESTAMP()
|
||||
WHERE a.user = ?
|
||||
GROUP BY a.id',
|
||||
$name
|
||||
LEFT JOIN ?_account_banned ab ON a.`id` = ab.`userId` AND ab.`end` > UNIX_TIMESTAMP()
|
||||
WHERE { a.`email` = ? } { a.`login` = ? }
|
||||
GROUP BY a.`id`',
|
||||
$email ?: DBSIMPLE_SKIP,
|
||||
!$email ? $nameOrEmail : DBSIMPLE_SKIP
|
||||
);
|
||||
|
||||
if (!$query)
|
||||
return AUTH_WRONGUSER;
|
||||
|
||||
self::$passHash = $query['passHash'];
|
||||
if (!self::verifyCrypt($pass))
|
||||
if (!self::verifyCrypt($password, $query['passHash']))
|
||||
return AUTH_WRONGPASS;
|
||||
|
||||
// successfull auth; clear bans for this IP
|
||||
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE type = 0 AND ip = ?', self::$ip);
|
||||
DB::Aowow()->query('DELETE FROM ?_account_bannedips WHERE `type` = 0 AND `ip` = ?', self::$ip);
|
||||
|
||||
if ($query['bans'] & (ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
return AUTH_BANNED;
|
||||
|
||||
$user = $query['id'];
|
||||
$hash = $query['passHash'];
|
||||
break;
|
||||
$userId = $query['id'];
|
||||
|
||||
return AUTH_OK;
|
||||
}
|
||||
case AUTH_MODE_REALM:
|
||||
|
||||
private static function authRealm(string $name, string $password, int &$userId) : int
|
||||
{
|
||||
if (!DB::isConnectable(DB_AUTH))
|
||||
return AUTH_INTERNAL_ERR;
|
||||
@@ -287,74 +295,70 @@ class User
|
||||
if (!$wow)
|
||||
return AUTH_WRONGUSER;
|
||||
|
||||
if (!self::verifySRP6($name, $pass, $wow['salt'], $wow['verifier']))
|
||||
if (!self::verifySRP6($name, $password, $wow['salt'], $wow['verifier']))
|
||||
return AUTH_WRONGPASS;
|
||||
|
||||
if ($wow['hasBan'])
|
||||
return AUTH_BANNED;
|
||||
|
||||
if ($_ = self::checkOrCreateInDB($wow['id'], $name))
|
||||
$user = $_;
|
||||
$userId = $_;
|
||||
else
|
||||
return AUTH_INTERNAL_ERR;
|
||||
|
||||
break;
|
||||
}
|
||||
case AUTH_MODE_EXTERNAL:
|
||||
{
|
||||
if (!file_exists('config/extAuth.php'))
|
||||
return AUTH_INTERNAL_ERR;
|
||||
|
||||
require 'config/extAuth.php';
|
||||
|
||||
$extGroup = -1;
|
||||
$result = extAuth($name, $pass, $extId, $extGroup);
|
||||
|
||||
if ($result == AUTH_OK && $extId)
|
||||
{
|
||||
if ($_ = self::checkOrCreateInDB($extId, $name, $extGroup))
|
||||
$user = $_;
|
||||
else
|
||||
return AUTH_INTERNAL_ERR;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
default:
|
||||
return AUTH_INTERNAL_ERR;
|
||||
}
|
||||
|
||||
// kickstart session
|
||||
session_unset();
|
||||
$_SESSION['user'] = $user;
|
||||
$_SESSION['hash'] = $hash;
|
||||
|
||||
return AUTH_OK;
|
||||
}
|
||||
|
||||
// create a linked account for our settings if nessecary
|
||||
private static function checkOrCreateInDB($extId, $name, $userGroup = -1)
|
||||
private static function authExtern(string $nameOrEmail, string $password, int &$userId) : int
|
||||
{
|
||||
if (!intVal($extId))
|
||||
return 0;
|
||||
if (!file_exists('config/extAuth.php'))
|
||||
{
|
||||
trigger_error('User::authExtern - AUTH_MODE_EXTERNAL is selected but config/extAuth.php does not exist!', E_USER_ERROR);
|
||||
return AUTH_INTERNAL_ERR;
|
||||
}
|
||||
|
||||
$userGroup = intVal($userGroup);
|
||||
require 'config/extAuth.php';
|
||||
|
||||
if ($_ = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE extId = ?d', $extId))
|
||||
if (!function_exists('\extAuth'))
|
||||
{
|
||||
trigger_error('User::authExtern - AUTH_MODE_EXTERNAL is selected but function extAuth() is not defined!', E_USER_ERROR);
|
||||
return AUTH_INTERNAL_ERR;
|
||||
}
|
||||
|
||||
$extGroup = -1;
|
||||
$extId = 0;
|
||||
$result = \extAuth($nameOrEmail, $password, $extId, $extGroup);
|
||||
|
||||
// assert we don't have an email passed back from extAuth
|
||||
if (filter_var($nameOrEmail, FILTER_VALIDATE_EMAIL))
|
||||
return AUTH_WRONGUSER;
|
||||
|
||||
if ($result == AUTH_OK && $extId)
|
||||
{
|
||||
if ($_ = self::checkOrCreateInDB($extId, $nameOrEmail, $extGroup))
|
||||
$userId = $_;
|
||||
else
|
||||
return AUTH_INTERNAL_ERR;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// create a linked account for our settings if necessary
|
||||
private static function checkOrCreateInDB(int $extId, string $name, int $userGroup = -1) : int
|
||||
{
|
||||
if ($_ = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE `extId` = ?d', $extId))
|
||||
{
|
||||
if ($userGroup >= U_GROUP_NONE)
|
||||
DB::Aowow()->query('UPDATE ?_account SET userGroups = ?d WHERE extId = ?d', $userGroup, $extId);
|
||||
DB::Aowow()->query('UPDATE ?_account SET `userGroups` = ?d WHERE `extId` = ?d', $userGroup, $extId);
|
||||
return $_;
|
||||
}
|
||||
|
||||
$newId = DB::Aowow()->query('INSERT IGNORE INTO ?_account (extId, user, displayName, joinDate, prevIP, prevLogin, locale, status, userGroups) VALUES (?d, ?, ?, UNIX_TIMESTAMP(), ?, UNIX_TIMESTAMP(), ?d, ?d, ?d)',
|
||||
$newId = DB::Aowow()->query('INSERT IGNORE INTO ?_account (`extId`, `passHash`, `username`, `joinDate`, `prevIP`, `prevLogin`, `locale`, `status`, `userGroups`) VALUES (?d, "", ?, UNIX_TIMESTAMP(), ?, UNIX_TIMESTAMP(), ?d, ?d, ?d)',
|
||||
$extId,
|
||||
$name,
|
||||
Util::ucFirst($name),
|
||||
isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : '',
|
||||
User::$localeId,
|
||||
$_SERVER["REMOTE_ADDR"] ?? '',
|
||||
self::$preferedLoc->value,
|
||||
ACC_STATUS_OK,
|
||||
$userGroup >= U_GROUP_NONE ? $userGroup : U_GROUP_NONE
|
||||
);
|
||||
@@ -362,10 +366,10 @@ class User
|
||||
if ($newId)
|
||||
Util::gainSiteReputation($newId, SITEREP_ACTION_REGISTER);
|
||||
|
||||
return $newId;
|
||||
return $newId ?: 0;
|
||||
}
|
||||
|
||||
private static function createSalt()
|
||||
private static function createSalt() : string
|
||||
{
|
||||
$algo = '$2a';
|
||||
$strength = '$09';
|
||||
@@ -375,18 +379,18 @@ class User
|
||||
}
|
||||
|
||||
// crypt used by aowow
|
||||
public static function hashCrypt($pass)
|
||||
public static function hashCrypt(string $pass) : string
|
||||
{
|
||||
return crypt($pass, self::createSalt());
|
||||
}
|
||||
|
||||
public static function verifyCrypt($pass, $hash = '')
|
||||
public static function verifyCrypt(string $pass, string $hash) : bool
|
||||
{
|
||||
$_ = $hash ?: self::$passHash;
|
||||
return $_ === crypt($pass, $_);
|
||||
return $hash === crypt($pass, $hash);
|
||||
}
|
||||
|
||||
private static function verifySRP6($user, $pass, $salt, $verifier)
|
||||
// SRP6 used by TC
|
||||
private static function verifySRP6(string $user, string $pass, string $salt, string $verifier) : bool
|
||||
{
|
||||
$g = gmp_init(7);
|
||||
$N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16);
|
||||
@@ -399,19 +403,19 @@ class User
|
||||
return ($verifier === str_pad(gmp_export($v, 1, GMP_LSW_FIRST), 32, chr(0), STR_PAD_RIGHT));
|
||||
}
|
||||
|
||||
public static function isValidName($name, &$errCode = 0)
|
||||
public static function isValidName(string $name, int &$errCode = 0) : bool
|
||||
{
|
||||
$errCode = 0;
|
||||
|
||||
// different auth modes require different usernames
|
||||
$min = 0; // external case
|
||||
$max = 0;
|
||||
if (CFG_ACC_AUTH_MODE == AUTH_MODE_SELF)
|
||||
if (Cfg::get('ACC_AUTH_MODE') == AUTH_MODE_SELF)
|
||||
{
|
||||
$min = 4;
|
||||
$max = 16;
|
||||
}
|
||||
else if (CFG_ACC_AUTH_MODE == AUTH_MODE_REALM)
|
||||
else if (Cfg::get('ACC_AUTH_MODE') == AUTH_MODE_REALM)
|
||||
{
|
||||
$min = 3;
|
||||
$max = 32;
|
||||
@@ -425,12 +429,12 @@ class User
|
||||
return $errCode == 0;
|
||||
}
|
||||
|
||||
public static function isValidPass($pass, &$errCode = 0)
|
||||
public static function isValidPass(string $pass, ?int &$errCode = 0) : bool
|
||||
{
|
||||
$errCode = 0;
|
||||
|
||||
// only enforce for own passwords
|
||||
if (mb_strlen($pass) < 6 && CFG_ACC_AUTH_MODE == AUTH_MODE_SELF)
|
||||
if (mb_strlen($pass) < 6 && Cfg::get('ACC_AUTH_MODE') == AUTH_MODE_SELF)
|
||||
$errCode = 1;
|
||||
// else if (preg_match('/[^\w\d!"#\$%]/', $pass)) // such things exist..? :o
|
||||
// $errCode = 2;
|
||||
@@ -438,146 +442,153 @@ class User
|
||||
return $errCode == 0;
|
||||
}
|
||||
|
||||
public static function save()
|
||||
{
|
||||
$_SESSION['user'] = self::$id;
|
||||
$_SESSION['hash'] = self::$passHash;
|
||||
$_SESSION['locale'] = self::$localeId;
|
||||
$_SESSION['timeout'] = self::$expires ? time() + CFG_SESSION_TIMEOUT_DELAY : 0;
|
||||
// $_SESSION['dataKey'] does not depend on user login status and is set in User::init()
|
||||
}
|
||||
|
||||
public static function destroy()
|
||||
{
|
||||
session_regenerate_id(true); // session itself is not destroyed; status changed => regenerate id
|
||||
session_unset();
|
||||
|
||||
$_SESSION['locale'] = self::$localeId; // keep locale
|
||||
$_SESSION['dataKey'] = self::$dataKey; // keep dataKey
|
||||
|
||||
self::$id = 0;
|
||||
self::$displayName = '';
|
||||
self::$perms = 0;
|
||||
self::$groups = U_GROUP_NONE;
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* access management */
|
||||
/*********************/
|
||||
|
||||
public static function isInGroup($group)
|
||||
public static function isInGroup(int $group) : bool
|
||||
{
|
||||
return (self::$groups & $group) != 0;
|
||||
return $group == U_GROUP_NONE || (self::$groups & $group) != U_GROUP_NONE;
|
||||
}
|
||||
|
||||
public static function canComment()
|
||||
public static function canComment() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_COMMENT))
|
||||
return false;
|
||||
|
||||
return self::$perms || self::$reputation >= CFG_REP_REQ_COMMENT;
|
||||
return self::$perms || self::$reputation >= Cfg::get('REP_REQ_COMMENT');
|
||||
}
|
||||
|
||||
public static function canReply()
|
||||
public static function canReply() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_COMMENT))
|
||||
return false;
|
||||
|
||||
return self::$perms || self::$reputation >= CFG_REP_REQ_REPLY;
|
||||
return self::$perms || self::$reputation >= Cfg::get('REP_REQ_REPLY');
|
||||
}
|
||||
|
||||
public static function canUpvote()
|
||||
public static function canUpvote() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_COMMENT | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_COMMENT))
|
||||
return false;
|
||||
|
||||
return self::$perms || (self::$reputation >= CFG_REP_REQ_UPVOTE && self::$dailyVotes > 0);
|
||||
return self::$perms || (self::$reputation >= Cfg::get('REP_REQ_UPVOTE') && self::$dailyVotes > 0);
|
||||
}
|
||||
|
||||
public static function canDownvote()
|
||||
public static function canDownvote() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_RATE | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_RATE))
|
||||
return false;
|
||||
|
||||
return self::$perms || (self::$reputation >= CFG_REP_REQ_DOWNVOTE && self::$dailyVotes > 0);
|
||||
return self::$perms || (self::$reputation >= Cfg::get('REP_REQ_DOWNVOTE') && self::$dailyVotes > 0);
|
||||
}
|
||||
|
||||
public static function canSupervote()
|
||||
public static function canSupervote() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_RATE | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_RATE) || self::isInGroup(U_GROUP_PENDING))
|
||||
return false;
|
||||
|
||||
return self::$reputation >= CFG_REP_REQ_SUPERVOTE;
|
||||
return self::$reputation >= Cfg::get('REP_REQ_SUPERVOTE');
|
||||
}
|
||||
|
||||
public static function canUploadScreenshot()
|
||||
public static function canUploadScreenshot() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_SCREENSHOT | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_SCREENSHOT) || self::isInGroup(U_GROUP_PENDING))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function canWriteGuide()
|
||||
public static function canWriteGuide() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_GUIDE | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_GUIDE) || self::isInGroup(U_GROUP_PENDING))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function canSuggestVideo()
|
||||
public static function canSuggestVideo() : bool
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_VIDEO | ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_VIDEO) || self::isInGroup(U_GROUP_PENDING))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isPremium()
|
||||
public static function isPremium() : bool
|
||||
{
|
||||
return self::isInGroup(U_GROUP_PREMIUM) || self::$reputation >= CFG_REP_REQ_PREMIUM;
|
||||
return self::isInGroup(U_GROUP_PREMIUM) || self::$reputation >= Cfg::get('REP_REQ_PREMIUM');
|
||||
}
|
||||
|
||||
public static function isLoggedIn() : bool
|
||||
{
|
||||
return self::$id > 0; // more checks? maybe check pending email verification here? (self::isInGroup(U_GROUP_PENDING))
|
||||
}
|
||||
|
||||
public static function isBanned(int $addBanMask = 0x0) : bool
|
||||
{
|
||||
return self::$banStatus & (ACC_BAN_TEMP | ACC_BAN_PERM | $addBanMask);
|
||||
}
|
||||
|
||||
public static function isRecovering() : bool
|
||||
{
|
||||
return self::$status == ACC_STATUS_RECOVER_USER || self::$status == ACC_STATUS_RECOVER_PASS;
|
||||
}
|
||||
|
||||
|
||||
/**************/
|
||||
/* js-related */
|
||||
/**************/
|
||||
|
||||
public static function decrementDailyVotes()
|
||||
public static function decrementDailyVotes() : void
|
||||
{
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_RATE))
|
||||
return;
|
||||
|
||||
self::$dailyVotes--;
|
||||
DB::Aowow()->query('UPDATE ?_account SET dailyVotes = ?d WHERE id = ?d', self::$dailyVotes, self::$id);
|
||||
DB::Aowow()->query('UPDATE ?_account SET `dailyVotes` = ?d WHERE `id` = ?d', self::$dailyVotes, self::$id);
|
||||
}
|
||||
|
||||
public static function getCurDailyVotes()
|
||||
public static function getCurrentDailyVotes() : int
|
||||
{
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_RATE) || self::$dailyVotes < 0)
|
||||
return 0;
|
||||
|
||||
return self::$dailyVotes;
|
||||
}
|
||||
|
||||
public static function getMaxDailyVotes()
|
||||
public static function getMaxDailyVotes() : int
|
||||
{
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_PERM | ACC_BAN_TEMP))
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_RATE))
|
||||
return 0;
|
||||
|
||||
return CFG_USER_MAX_VOTES + (self::$reputation >= CFG_REP_REQ_VOTEMORE_BASE ? 1 + intVal((self::$reputation - CFG_REP_REQ_VOTEMORE_BASE) / CFG_REP_REQ_VOTEMORE_ADD) : 0);
|
||||
$threshold = Cfg::get('REP_REQ_VOTEMORE_BASE');
|
||||
$extra = Cfg::get('REP_REQ_VOTEMORE_ADD');
|
||||
$base = Cfg::get('USER_MAX_VOTES');
|
||||
|
||||
return $base + max(0, intVal((self::$reputation - $threshold + $extra) / $extra));
|
||||
}
|
||||
|
||||
public static function getReputation()
|
||||
public static function getReputation() : int
|
||||
{
|
||||
if (!self::isLoggedIn() || self::$reputation < 0)
|
||||
return 0;
|
||||
|
||||
return self::$reputation;
|
||||
}
|
||||
|
||||
public static function getUserGlobals()
|
||||
public static function getUserGlobal() : array
|
||||
{
|
||||
$gUser = array(
|
||||
'id' => self::$id,
|
||||
'name' => self::$displayName,
|
||||
'name' => self::$username,
|
||||
'roles' => self::$groups,
|
||||
'permissions' => self::$perms,
|
||||
'cookies' => []
|
||||
);
|
||||
|
||||
if (!self::$id || self::$banStatus & (ACC_BAN_TEMP | ACC_BAN_PERM))
|
||||
if (!self::isLoggedIn() || self::isBanned())
|
||||
return $gUser;
|
||||
|
||||
$gUser['commentban'] = !self::canComment();
|
||||
@@ -585,11 +596,29 @@ class User
|
||||
$gUser['canDownvote'] = self::canDownvote();
|
||||
$gUser['canPostReplies'] = self::canReply();
|
||||
$gUser['superCommentVotes'] = self::canSupervote();
|
||||
$gUser['downvoteRep'] = CFG_REP_REQ_DOWNVOTE;
|
||||
$gUser['upvoteRep'] = CFG_REP_REQ_UPVOTE;
|
||||
$gUser['downvoteRep'] = Cfg::get('REP_REQ_DOWNVOTE');
|
||||
$gUser['upvoteRep'] = Cfg::get('REP_REQ_UPVOTE');
|
||||
$gUser['characters'] = self::getCharacters();
|
||||
$gUser['excludegroups'] = self::$excludeGroups;
|
||||
$gUser['settings'] = (new StdClass); // profiler requires this to be set; has property premiumborder (NYI)
|
||||
|
||||
if (Cfg::get('DEBUG') && User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN | U_GROUP_TESTER))
|
||||
$gUser['debug'] = true; // csv id-list output option on listviews
|
||||
|
||||
if (self::getPremiumBorder())
|
||||
$gUser['settings'] = ['premiumborder' => 1];
|
||||
else
|
||||
$gUser['settings'] = (new \StdClass); // existence is checked in Profiler.js before g_user.excludegroups is applied
|
||||
|
||||
if (self::isPremium())
|
||||
$gUser['premium'] = 1;
|
||||
|
||||
if (self::getPremiumBorder())
|
||||
$gUser['settings'] = ['premiumborder' => 1];
|
||||
else
|
||||
$gUser['settings'] = (new \StdClass); // existence is checked in Profiler.js before g_user.excludegroups is applied
|
||||
|
||||
if (self::isPremium())
|
||||
$gUser['premium'] = 1;
|
||||
|
||||
if ($_ = self::getProfilerExclusions())
|
||||
$gUser = array_merge($gUser, $_);
|
||||
@@ -609,34 +638,41 @@ class User
|
||||
return $gUser;
|
||||
}
|
||||
|
||||
public static function getWeightScales()
|
||||
public static function getWeightScales() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$res = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, name FROM ?_account_weightscales WHERE userId = ?d', self::$id);
|
||||
if (!self::isLoggedIn() || self::isBanned())
|
||||
return $result;
|
||||
|
||||
$res = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `name` FROM ?_account_weightscales WHERE `userId` = ?d', self::$id);
|
||||
if (!$res)
|
||||
return $result;
|
||||
|
||||
$weights = DB::Aowow()->selectCol('SELECT id AS ARRAY_KEY, `field` AS ARRAY_KEY2, val FROM ?_account_weightscale_data WHERE id IN (?a)', array_keys($res));
|
||||
$weights = DB::Aowow()->selectCol('SELECT `id` AS ARRAY_KEY, `field` AS ARRAY_KEY2, `val` FROM ?_account_weightscale_data WHERE `id` IN (?a)', array_keys($res));
|
||||
foreach ($weights as $id => $data)
|
||||
$result[] = array_merge(['name' => $res[$id], 'id' => $id], $data);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getProfilerExclusions()
|
||||
public static function getProfilerExclusions() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (!self::isLoggedIn() || self::isBanned())
|
||||
return $result;
|
||||
|
||||
$modes = [1 => 'excludes', 2 => 'includes'];
|
||||
foreach ($modes as $mode => $field)
|
||||
if ($ex = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, typeId AS ARRAY_KEY2, typeId FROM ?_account_excludes WHERE mode = ?d AND userId = ?d', $mode, self::$id))
|
||||
if ($ex = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `typeId` FROM ?_account_excludes WHERE `mode` = ?d AND `userId` = ?d', $mode, self::$id))
|
||||
foreach ($ex as $type => $ids)
|
||||
$result[$field][$type] = array_values($ids);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getCharacters()
|
||||
public static function getCharacters() : array
|
||||
{
|
||||
if (!self::$profiles)
|
||||
return [];
|
||||
@@ -644,7 +680,7 @@ class User
|
||||
return self::$profiles->getJSGlobals(PROFILEINFO_CHARACTER);
|
||||
}
|
||||
|
||||
public static function getProfiles()
|
||||
public static function getProfiles() : array
|
||||
{
|
||||
if (!self::$profiles)
|
||||
return [];
|
||||
@@ -652,33 +688,53 @@ class User
|
||||
return self::$profiles->getJSGlobals(PROFILEINFO_PROFILE);
|
||||
}
|
||||
|
||||
public static function getGuides()
|
||||
public static function getPinnedCharacter() : array
|
||||
{
|
||||
if (!self::$profiles)
|
||||
return [];
|
||||
|
||||
$realms = Profiler::getRealms();
|
||||
|
||||
foreach (self::$profiles->iterate() as $id => $_)
|
||||
if (self::$profiles->getField('cuFlags') & PROFILER_CU_PINNED)
|
||||
if (isset($realms[self::$profiles->getField('realm')]))
|
||||
return [
|
||||
$id,
|
||||
self::$profiles->getField('name'),
|
||||
self::$profiles->getField('region') . '.' . Profiler::urlize($realms[self::$profiles->getField('realm')]['name'], true) . '.' . Profiler::urlize(self::$profiles->getField('name'), true, true)
|
||||
];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getGuides() : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (!self::isLoggedIn() || self::isBanned(ACC_BAN_GUIDE))
|
||||
return $result;
|
||||
|
||||
if ($guides = DB::Aowow()->select('SELECT `id`, `title`, `url` FROM ?_guides WHERE `userId` = ?d AND `status` <> ?d', self::$id, GUIDE_STATUS_ARCHIVED))
|
||||
{
|
||||
// fix url
|
||||
array_walk($guides, fn(&$x) => $x['url'] = '/?guide='.($x['url'] ?? $x['id']));
|
||||
array_walk($guides, fn(&$x) => $x['url'] = '?guide='.($x['url'] ?: $x['id']));
|
||||
$result = $guides;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getCookies()
|
||||
public static function getCookies() : array
|
||||
{
|
||||
$data = [];
|
||||
if (!self::isLoggedIn())
|
||||
return [];
|
||||
|
||||
if (self::$id)
|
||||
$data = DB::Aowow()->selectCol('SELECT name AS ARRAY_KEY, data FROM ?_account_cookies WHERE userId = ?d', self::$id);
|
||||
|
||||
return $data;
|
||||
return DB::Aowow()->selectCol('SELECT `name` AS ARRAY_KEY, `data` FROM ?_account_cookies WHERE `userId` = ?d', self::$id);
|
||||
}
|
||||
|
||||
public static function getFavorites()
|
||||
public static function getFavorites() : array
|
||||
{
|
||||
if (!self::$id)
|
||||
if (!self::isLoggedIn() || self::isBanned())
|
||||
return [];
|
||||
|
||||
$res = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, `typeId` AS ARRAY_KEY2, `typeId` FROM ?_account_favorites WHERE `userId` = ?d', self::$id);
|
||||
@@ -702,6 +758,12 @@ class User
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// not sure what to set .. user selected?
|
||||
public static function getPremiumBorder() : bool
|
||||
{
|
||||
return self::isInGroup(U_GROUP_PREMIUM);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
24
index.php
24
index.php
@@ -1,16 +1,13 @@
|
||||
<?php
|
||||
|
||||
require 'includes/shared.php';
|
||||
namespace Aowow;
|
||||
|
||||
require 'includes/kernel.php';
|
||||
|
||||
if (CLI)
|
||||
die("this script must not be run from CLI.\nto setup aowow use 'php aowow'\n");
|
||||
|
||||
|
||||
// maybe add additional setup checks?
|
||||
if (!DB::isConnectable(DB_AOWOW) || !DB::isConnectable(DB_WORLD))
|
||||
(new GenericPage($pageCall))->maintenance();
|
||||
|
||||
|
||||
$altClass = '';
|
||||
switch ($pageCall)
|
||||
{
|
||||
@@ -94,13 +91,16 @@ switch ($pageCall)
|
||||
case 'edit': // guide editor: targeted by QQ fileuploader, detail-page article editor
|
||||
case 'get-description': // guide editor: shorten fulltext into description
|
||||
case 'filter': // pre-evaluate filter POST-data; sanitize and forward as GET-data
|
||||
case 'go-to-reply': // find page the reply is on and forward
|
||||
if ($pageCall == 'go-to-reply')
|
||||
$altClass = 'go-to-comment';
|
||||
case 'go-to-comment': // find page the comment is on and forward
|
||||
case 'locale': // subdomain-workaround, change the language
|
||||
$cleanName = str_replace(['-', '_'], '', ucFirst($altClass ?: $pageCall));
|
||||
try // can it be handled as ajax?
|
||||
{
|
||||
$out = '';
|
||||
$class = 'Ajax'.$cleanName;
|
||||
$class = __NAMESPACE__.'\\'.'Ajax'.$cleanName;
|
||||
$ajax = new $class(explode('.', $pageParam));
|
||||
|
||||
if ($ajax->handle($out))
|
||||
@@ -116,17 +116,17 @@ switch ($pageCall)
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new Exception('not handled as ajax');
|
||||
throw new \Exception('not handled as ajax');
|
||||
}
|
||||
catch (Exception $e) // no, apparently not..
|
||||
catch (\Exception $e) // no, apparently not..
|
||||
{
|
||||
$class = $cleanName.'Page';
|
||||
$class = __NAMESPACE__.'\\'.$cleanName.'Page';
|
||||
$classInstance = new $class($pageCall, $pageParam);
|
||||
|
||||
if (is_callable([$classInstance, 'display']))
|
||||
$classInstance->display();
|
||||
else if (isset($_GET['power']))
|
||||
die('$WowheadPower.register(0, '.User::$localeId.', {})');
|
||||
die('$WowheadPower.register(0, '.Lang::getLocale()->value.', {})');
|
||||
else // in conjunction with a proper rewriteRule in .htaccess...
|
||||
(new GenericPage($pageCall))->error();
|
||||
}
|
||||
@@ -158,7 +158,7 @@ switch ($pageCall)
|
||||
break;
|
||||
default: // unk parameter given -> ErrorPage
|
||||
if (isset($_GET['power']))
|
||||
die('$WowheadPower.register(0, '.User::$localeId.', {})');
|
||||
die('$WowheadPower.register(0, '.Lang::getLocale()->value.', {})');
|
||||
else // in conjunction with a proper rewriteRule in .htaccess...
|
||||
(new GenericPage($pageCall))->error();
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
class Lang
|
||||
{
|
||||
private static $timeUnits;
|
||||
private static $lang;
|
||||
private static $main;
|
||||
private static $account;
|
||||
private static $user;
|
||||
@@ -40,21 +46,25 @@ class Lang
|
||||
private static $emote;
|
||||
private static $enchantment;
|
||||
|
||||
private static $locales = array(
|
||||
LOCALE_EN => 'English',
|
||||
LOCALE_FR => 'Français',
|
||||
LOCALE_DE => 'Deutsch',
|
||||
LOCALE_CN => '简体中文',
|
||||
LOCALE_ES => 'Español',
|
||||
LOCALE_RU => 'Русский'
|
||||
);
|
||||
private static ?Locale $locale = null;
|
||||
|
||||
public static function load($loc)
|
||||
public const FMT_RAW = 0;
|
||||
public const FMT_HTML = 1;
|
||||
public const FMT_MARKUP = 2;
|
||||
|
||||
public const CONCAT_NONE = 0;
|
||||
public const CONCAT_AND = 1;
|
||||
public const CONCAT_OR = 2;
|
||||
|
||||
public static function load(Locale $loc) : void
|
||||
{
|
||||
if (!file_exists('localization/locale_'.$loc.'.php'))
|
||||
die('File for localization '.strToUpper($loc).' not found.');
|
||||
if (self::$locale == $loc)
|
||||
return;
|
||||
|
||||
if (!file_exists('localization/locale_'.$loc->json().'.php'))
|
||||
die('File for locale '.$loc->name.' not found.');
|
||||
else
|
||||
require 'localization/locale_'.$loc.'.php';
|
||||
require 'localization/locale_'.$loc->json().'.php';
|
||||
|
||||
foreach ($lang as $k => $v)
|
||||
self::$$k = $v;
|
||||
@@ -63,72 +73,79 @@ class Lang
|
||||
self::$item['cat'][2] = [self::$item['cat'][2], self::$spell['weaponSubClass']];
|
||||
self::$item['cat'][2][1][14] .= ' ('.self::$item['cat'][2][0].')';
|
||||
self::$main['moreTitles']['privilege'] = self::$privileges['_privileges'];
|
||||
|
||||
self::$locale = $loc;
|
||||
}
|
||||
|
||||
public static function __callStatic($prop, $args)
|
||||
public static function getLocale() : Locale
|
||||
{
|
||||
if (!isset(self::$$prop))
|
||||
{
|
||||
$dbt = debug_backtrace()[0];
|
||||
$file = explode(DIRECTORY_SEPARATOR, $dbt['file']);
|
||||
trigger_error('Lang - tried to use undefined property Lang::$'.$prop.', called in '.array_pop($file).':'.$dbt['line'], E_USER_WARNING);
|
||||
return null;
|
||||
return self::$locale;
|
||||
}
|
||||
|
||||
public static function __callStatic(string $prop, ?array $args = []) : string|array|null
|
||||
{
|
||||
$vspfArgs = [];
|
||||
|
||||
$var = self::$$prop;
|
||||
foreach ($args as $arg)
|
||||
foreach ($args as $i => $arg)
|
||||
{
|
||||
if (is_array($arg))
|
||||
{
|
||||
$vspfArgs = $arg;
|
||||
if (!is_array($arg))
|
||||
continue;
|
||||
|
||||
$vspfArgs = $arg;
|
||||
unset($args[$i]);
|
||||
}
|
||||
else if (!isset($var[$arg]))
|
||||
{
|
||||
|
||||
if (($x = self::exist($prop, ...$args)) !== null)
|
||||
return self::vspf($x, $vspfArgs);
|
||||
|
||||
$dbt = debug_backtrace()[0];
|
||||
$file = explode(DIRECTORY_SEPARATOR, $dbt['file']);
|
||||
trigger_error('Lang - undefined property Lang::$'.$prop.'[\''.implode('\'][\'', $args).'\'], called in '.array_pop($file).':'.$dbt['line'], E_USER_WARNING);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$var = $var[$arg];
|
||||
public static function exist(string $prop, string ...$args) : string|array|null
|
||||
{
|
||||
if (!isset(self::$$prop))
|
||||
return null;
|
||||
|
||||
$ref = self::$$prop;
|
||||
foreach ($args as $a)
|
||||
{
|
||||
if (!isset($ref[$a]))
|
||||
return null;
|
||||
|
||||
$ref = $ref[$a];
|
||||
}
|
||||
|
||||
// meh :x
|
||||
if ($var === null && $prop == 'spell' && count($args) == 1)
|
||||
{
|
||||
if ($args[0] == 'effects')
|
||||
$var = self::$$prop['unkEffect'];
|
||||
else if ($args[0] == 'auras')
|
||||
$var = self::$$prop['unkAura'];
|
||||
return $ref;
|
||||
}
|
||||
|
||||
return self::vspf($var, $vspfArgs);
|
||||
}
|
||||
public static function concat(array $args, int $concat = self::CONCAT_AND, ?callable $callback = null) : string
|
||||
{
|
||||
$buff = '';
|
||||
$callback ??= fn($x) => $x;
|
||||
|
||||
public static function concat($args, $useAnd = true, $callback = null)
|
||||
reset($args);
|
||||
|
||||
if (count($args) < 2)
|
||||
return $callback(current($args), key($args));
|
||||
|
||||
do
|
||||
{
|
||||
$b = '';
|
||||
$i = 0;
|
||||
$n = count($args);
|
||||
foreach ($args as $k => $arg)
|
||||
{
|
||||
if (is_callable($callback))
|
||||
$b .= $callback($arg, $k);
|
||||
$item = $callback(current($args), key($args));
|
||||
$arg = next($args);
|
||||
|
||||
if ($arg !== false || $concat == self::CONCAT_NONE)
|
||||
$buff .= ', '.$item;
|
||||
else if ($concat == self::CONCAT_AND)
|
||||
$buff .= self::main('and').' '.$item;
|
||||
else
|
||||
$b .= $arg;
|
||||
|
||||
if ($n > 1 && $i < ($n - 2))
|
||||
$b .= ', ';
|
||||
else if ($n > 1 && $i == $n - 2)
|
||||
$b .= Lang::main($useAnd ? 'and' : 'or');
|
||||
|
||||
$i++;
|
||||
$buff .= self::main('or').' '.$item;
|
||||
}
|
||||
while ($arg !== false);
|
||||
|
||||
return $b;
|
||||
return substr($buff, 2);
|
||||
}
|
||||
|
||||
// truncate string after X chars. If X is inside a word truncate behind it.
|
||||
@@ -140,8 +157,9 @@ class Lang
|
||||
// limit whitespaces to one at a time
|
||||
$text = preg_replace('/\s+/', ' ', trim($text));
|
||||
|
||||
if ($len > 0 && mb_strlen($text) > $len)
|
||||
{
|
||||
if ($len <= 0 || mb_strlen($text) <= $len)
|
||||
return $text;
|
||||
|
||||
$n = 0;
|
||||
$b = [];
|
||||
$parts = explode(' ', $text);
|
||||
@@ -152,14 +170,11 @@ class Lang
|
||||
$b[] = $_;
|
||||
}
|
||||
|
||||
$text = implode(' ', $b).'…';
|
||||
}
|
||||
|
||||
return $text;
|
||||
return implode(' ', $b).'…';
|
||||
}
|
||||
|
||||
// add line breaks to string after X chars. If X is inside a word break behind it.
|
||||
public static function breakTextClean(string $text, int $len = 30, bool $asHTML = true) : string
|
||||
public static function breakTextClean(string $text, int $len = 30, int $fmt = self::FMT_HTML) : string
|
||||
{
|
||||
// remove line breaks
|
||||
$text = strtr($text, ["\n" => ' ', "\r" => ' ']);
|
||||
@@ -167,13 +182,13 @@ class Lang
|
||||
// limit whitespaces to one at a time
|
||||
$text = preg_replace('/\s+/', ' ', trim($text));
|
||||
|
||||
if ($len <= 0 || mb_strlen($text) <= $len)
|
||||
return $text;
|
||||
|
||||
$row = [];
|
||||
if ($len > 0 && mb_strlen($text) > $len)
|
||||
{
|
||||
$i = 0;
|
||||
$n = 0;
|
||||
$parts = explode(' ', $text);
|
||||
foreach ($parts as $p)
|
||||
foreach (explode(' ', $text) as $p)
|
||||
{
|
||||
$row[$i][] = $p;
|
||||
$n += (mb_strlen($p) + 1);
|
||||
@@ -186,55 +201,62 @@ class Lang
|
||||
}
|
||||
foreach ($row as &$r)
|
||||
$r = implode(' ', $r);
|
||||
|
||||
$separator = match ($fmt)
|
||||
{
|
||||
self::FMT_HTML => '<br />',
|
||||
self::FMT_MARKUP => '[br]',
|
||||
self::FMT_RAW => "\n",
|
||||
default => "\n"
|
||||
};
|
||||
|
||||
return implode($separator, $row);
|
||||
}
|
||||
|
||||
return implode($asHTML ? '<br />' : '[br]', $row);
|
||||
}
|
||||
|
||||
public static function sort($prop, $group, $method = SORT_NATURAL)
|
||||
public static function sort(string $prop, string $group, int $method = SORT_NATURAL) : void
|
||||
{
|
||||
|
||||
if (!isset(self::$$prop))
|
||||
{
|
||||
trigger_error('Lang::sort - tried to use undefined property Lang::$'.$prop, E_USER_WARNING);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
$var = &self::$$prop;
|
||||
if (!isset($var[$group]))
|
||||
{
|
||||
trigger_error('Lang::sort - tried to use undefined property Lang::$'.$prop.'[\''.$group.'\']', E_USER_WARNING);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
asort($var[$group], $method);
|
||||
}
|
||||
|
||||
// todo: expand
|
||||
public static function getInfoBoxForFlags($flags)
|
||||
public static function getInfoBoxForFlags(int $cuFlags) : array
|
||||
{
|
||||
$tmp = [];
|
||||
|
||||
if ($flags & CUSTOM_DISABLED)
|
||||
if ($cuFlags & CUSTOM_DISABLED)
|
||||
$tmp[] = '[tooltip name=disabledHint]'.Util::jsEscape(self::main('disabledHint')).'[/tooltip][span class=tip tooltip=disabledHint]'.Util::jsEscape(self::main('disabled')).'[/span]';
|
||||
|
||||
if ($flags & CUSTOM_SERVERSIDE)
|
||||
if ($cuFlags & CUSTOM_SERVERSIDE)
|
||||
$tmp[] = '[tooltip name=serversideHint]'.Util::jsEscape(self::main('serversideHint')).'[/tooltip][span class=tip tooltip=serversideHint]'.Util::jsEscape(self::main('serverside')).'[/span]';
|
||||
|
||||
if ($flags & CUSTOM_UNAVAILABLE)
|
||||
if ($cuFlags & CUSTOM_UNAVAILABLE)
|
||||
$tmp[] = self::main('unavailable');
|
||||
|
||||
if ($flags & CUSTOM_EXCLUDE_FOR_LISTVIEW && User::isInGroup(U_GROUP_STAFF))
|
||||
if ($cuFlags & CUSTOM_EXCLUDE_FOR_LISTVIEW && User::isInGroup(U_GROUP_STAFF))
|
||||
$tmp[] = '[tooltip name=excludedHint]This entry is excluded from lists and is not searchable.[/tooltip][span tooltip=excludedHint class="tip q10"]Hidden[/span]';
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
public static function getLocks(int $lockId, ?array &$ids = [], bool $interactive = false, bool $asHTML = false) : array
|
||||
public static function getLocks(int $lockId, ?array &$ids = [], bool $interactive = false, int $fmt = self::FMT_HTML) : array
|
||||
{
|
||||
$locks = [];
|
||||
$ids = [];
|
||||
$lock = DB::Aowow()->selectRow('SELECT * FROM ?_lock WHERE id = ?d', $lockId);
|
||||
$lock = DB::Aowow()->selectRow('SELECT * FROM ?_lock WHERE `id` = ?d', $lockId);
|
||||
if (!$lock)
|
||||
return $locks;
|
||||
|
||||
@@ -250,13 +272,16 @@ class Lang
|
||||
if (!$name)
|
||||
continue;
|
||||
|
||||
if ($interactive && $asHTML)
|
||||
$name = '<a class="q1" href="?item='.$prop.'">'.$name.'</a>';
|
||||
else if ($interactive && !$asHTML)
|
||||
if ($fmt == self::FMT_HTML)
|
||||
$name = $interactive ? '<a class="q1" href="?item='.$prop.'">'.$name.'</a>' : '<span class="q1">'.$name.'</span>';
|
||||
else if ($interactive && $fmt == self::FMT_MARKUP)
|
||||
{
|
||||
$name = '[item='.$prop.']';
|
||||
$ids[Type::ITEM][] = $prop;
|
||||
}
|
||||
else
|
||||
$name = $prop;
|
||||
|
||||
}
|
||||
else if ($lock['type'.$i] == LOCK_TYPE_SKILL)
|
||||
{
|
||||
@@ -274,13 +299,15 @@ class Lang
|
||||
20 => SKILL_INSCRIPTION
|
||||
);
|
||||
|
||||
if ($interactive && $asHTML)
|
||||
$name = '<a href="?skill='.$skills[$prop].'">'.$name.'</a>';
|
||||
else if ($interactive && !$asHTML)
|
||||
if ($fmt == self::FMT_HTML)
|
||||
$name = $interactive ? '<a href="?skill='.$skills[$prop].'">'.$name.'</a>' : '<span class="q1">'.$name.'</span>';
|
||||
else if ($interactive && $fmt == self::FMT_MARKUP)
|
||||
{
|
||||
$name = '[skill='.$skills[$prop].']';
|
||||
$ids[Type::SKILL][] = $skills[$prop];
|
||||
}
|
||||
else
|
||||
$name = $skills[$prop];
|
||||
|
||||
if ($rank > 0)
|
||||
$name .= ' ('.$rank.')';
|
||||
@@ -288,13 +315,14 @@ class Lang
|
||||
// Lockpicking
|
||||
else if ($prop == 4)
|
||||
{
|
||||
if ($interactive && $asHTML)
|
||||
$name = '<a href="?spell=1842">'.$name.'</a>';
|
||||
else if ($interactive && !$asHTML)
|
||||
if ($fmt == self::FMT_HTML)
|
||||
$name = $interactive ? '<a href="?spell=1842">'.$name.'</a>' : '<span class="q1">'.$name.'</span>';
|
||||
else if ($interactive && $fmt == self::FMT_MARKUP)
|
||||
{
|
||||
$name = '[spell=1842]';
|
||||
$ids[Type::SPELL][] = 1842;
|
||||
}
|
||||
// else $name = $name
|
||||
}
|
||||
// exclude unusual stuff
|
||||
else if (User::isInGroup(U_GROUP_STAFF))
|
||||
@@ -314,14 +342,12 @@ class Lang
|
||||
return $locks;
|
||||
}
|
||||
|
||||
public static function getReputationLevelForPoints($pts)
|
||||
public static function getReputationLevelForPoints(int $pts) : string
|
||||
{
|
||||
$_ = Game::getReputationLevelForPoints($pts);
|
||||
|
||||
return self::game('rep', $_);
|
||||
return self::game('rep', Game::getReputationLevelForPoints($pts));
|
||||
}
|
||||
|
||||
public static function getRequiredItems($class, $mask, $short = true)
|
||||
public static function getRequiredItems(int $class, int $mask, bool $short = true) : string
|
||||
{
|
||||
if (!in_array($class, [ITEM_CLASS_MISC, ITEM_CLASS_ARMOR, ITEM_CLASS_WEAPON]))
|
||||
return '';
|
||||
@@ -363,7 +389,7 @@ class Lang
|
||||
return implode(', ', $tmp);
|
||||
}
|
||||
|
||||
public static function getStances($stanceMask)
|
||||
public static function getStances(int $stanceMask) : string
|
||||
{
|
||||
$stanceMask &= 0xFF37F6FF; // clamp to available stances/forms..
|
||||
|
||||
@@ -383,12 +409,18 @@ class Lang
|
||||
return implode(', ', $tmp);
|
||||
}
|
||||
|
||||
public static function getMagicSchools($schoolMask)
|
||||
public static function getMagicSchools(int $schoolMask, bool $short = false) : string
|
||||
{
|
||||
$schoolMask &= SPELL_ALL_SCHOOLS; // clamp to available schools..
|
||||
$tmp = [];
|
||||
$i = 0;
|
||||
|
||||
if ($short && $schoolMask == SPELL_ALL_SCHOOLS)
|
||||
return self::main('all');
|
||||
|
||||
if ($short && $schoolMask == SPELL_MAGIC_SCHOOLS)
|
||||
return self::main('all').' ('.self::game('dt', 1).')';
|
||||
|
||||
while ($schoolMask)
|
||||
{
|
||||
if ($schoolMask & (1 << $i))
|
||||
@@ -402,93 +434,82 @@ class Lang
|
||||
return implode(', ', $tmp);
|
||||
}
|
||||
|
||||
public static function getClassString(int $classMask, array &$ids = [], bool $asHTML = true) : string
|
||||
public static function getClassString(int $classMask, array &$ids = [], int $fmt = self::FMT_HTML) : string
|
||||
{
|
||||
$classMask &= CLASS_MASK_ALL; // clamp to available classes..
|
||||
$classMask &= ChrClass::MASK_ALL; // clamp to available classes..
|
||||
|
||||
if ($classMask == CLASS_MASK_ALL) // available to all classes
|
||||
return false;
|
||||
if (!$classMask || $classMask == ChrClass::MASK_ALL)// available to all classes
|
||||
return '';
|
||||
|
||||
[$base, $br] = match ($fmt)
|
||||
{
|
||||
self::FMT_HTML => ['<a href="?class=%1$d" class="c%1$d">%2$s</a>', ''],
|
||||
self::FMT_MARKUP => ['[class=%1$d]', '[br]'],
|
||||
self::FMT_RAW => ['%2$s', ''],
|
||||
default => ['%2$s', '']
|
||||
};
|
||||
|
||||
$tmp = [];
|
||||
$i = 1;
|
||||
$base = $asHTML ? '<a href="?class=%d" class="c%1$d">%2$s</a>' : '[class=%d]';
|
||||
$br = $asHTML ? '' : '[br]';
|
||||
|
||||
while ($classMask)
|
||||
{
|
||||
if ($classMask & (1 << ($i - 1)))
|
||||
{
|
||||
$tmp[$i] = (!fMod(count($tmp) + 1, 3) ? $br : null).sprintf($base, $i, self::game('cl', $i));
|
||||
$classMask &= ~(1 << ($i - 1));
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
foreach (ChrClass::fromMask($classMask) as $c)
|
||||
$tmp[$c] = (!fMod(count($tmp) + 1, 3) ? $br : null).sprintf($base, $c, self::game('cl', $c));
|
||||
|
||||
$ids = array_keys($tmp);
|
||||
|
||||
return implode(', ', $tmp);
|
||||
}
|
||||
|
||||
public static function getRaceString(int $raceMask, array &$ids = [], bool $asHTML = true) : string
|
||||
public static function getRaceString(int $raceMask, array &$ids = [], int $fmt = self::FMT_HTML) : string
|
||||
{
|
||||
$raceMask &= RACE_MASK_ALL; // clamp to available races..
|
||||
$raceMask &= ChrRace::MASK_ALL; // clamp to available races..
|
||||
|
||||
if ($raceMask == RACE_MASK_ALL) // available to all races (we don't display 'both factions')
|
||||
return false;
|
||||
if (!$raceMask || $raceMask == ChrRace::MASK_ALL) // available to all races (we don't display 'both factions')
|
||||
return '';
|
||||
|
||||
if (!$raceMask)
|
||||
return false;
|
||||
|
||||
$tmp = [];
|
||||
$i = 1;
|
||||
$base = $asHTML ? '<a href="?race=%d" class="q1">%s</a>' : '[race=%d]';
|
||||
$br = $asHTML ? '' : '[br]';
|
||||
|
||||
if ($raceMask == RACE_MASK_HORDE)
|
||||
if ($raceMask == ChrRace::MASK_HORDE)
|
||||
return self::game('ra', -2);
|
||||
|
||||
if ($raceMask == RACE_MASK_ALLIANCE)
|
||||
if ($raceMask == ChrRace::MASK_ALLIANCE)
|
||||
return self::game('ra', -1);
|
||||
|
||||
while ($raceMask)
|
||||
[$base, $br] = match ($fmt)
|
||||
{
|
||||
if ($raceMask & (1 << ($i - 1)))
|
||||
{
|
||||
$tmp[$i] = (!fMod(count($tmp) + 1, 3) ? $br : null).sprintf($base, $i, self::game('ra', $i));
|
||||
$raceMask &= ~(1 << ($i - 1));
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
self::FMT_HTML => ['<a href="?race=%1$d" class="q1">%2$s</a>', ''],
|
||||
self::FMT_MARKUP => ['[race=%1$d]', '[br]'],
|
||||
self::FMT_RAW => ['%2$s', ''],
|
||||
default => ['%2$s', '']
|
||||
};
|
||||
|
||||
$tmp = [];
|
||||
foreach (ChrRace::fromMask($raceMask) as $r)
|
||||
$tmp[$r] = (!fMod(count($tmp) + 1, 3) ? $br : '').sprintf($base, $r, self::game('ra', $r));
|
||||
|
||||
$ids = array_keys($tmp);
|
||||
|
||||
return implode(', ', $tmp);
|
||||
}
|
||||
|
||||
public static function formatSkillBreakpoints(array $bp, bool $html = false) : string
|
||||
public static function formatSkillBreakpoints(array $bp, int $fmt = self::FMT_MARKUP) : string
|
||||
{
|
||||
$tmp = Lang::game('difficulty').Lang::main('colon');
|
||||
$tmp = self::game('difficulty').self::main('colon');
|
||||
|
||||
$base = match ($fmt)
|
||||
{
|
||||
self::FMT_HTML => '<span class="r%1$d">%2$s</span> ',
|
||||
self::FMT_MARKUP => '[color=r%1$d]%2$s[/color] ',
|
||||
self::FMT_RAW => '%2$s ',
|
||||
default => '%2$s '
|
||||
};
|
||||
|
||||
for ($i = 0; $i < 4; $i++)
|
||||
if (!empty($bp[$i]))
|
||||
$tmp .= $html ? '<span class="r'.($i + 1).'">'.$bp[$i].'</span> ' : '[color=r'.($i + 1).']'.$bp[$i].'[/color] ';
|
||||
$tmp .= sprintf($base, $i + 1, $bp[$i]);
|
||||
|
||||
return trim($tmp);
|
||||
}
|
||||
|
||||
public static function nf($number, $decimals = 0, $no1k = false)
|
||||
public static function nf(float $number, int $decimals = 0, bool $no1k = false) : string
|
||||
{
|
||||
// [decimal, thousand]
|
||||
$seps = array(
|
||||
LOCALE_EN => [',', '.'],
|
||||
LOCALE_FR => [' ', ','],
|
||||
LOCALE_DE => ['.', ','],
|
||||
LOCALE_CN => [',', '.'],
|
||||
LOCALE_ES => ['.', ','],
|
||||
LOCALE_RU => [' ', ',']
|
||||
);
|
||||
|
||||
return number_format($number, $decimals, $seps[User::$localeId][1], $no1k ? '' : $seps[User::$localeId][0]);
|
||||
return number_format($number, $decimals, self::main('nfSeparators', 1), $no1k ? '' : self::main('nfSeparators', 0));
|
||||
}
|
||||
|
||||
public static function typeName(int $type) : string
|
||||
@@ -496,98 +517,286 @@ class Lang
|
||||
return Util::ucFirst(self::game(Type::getFileString($type)));
|
||||
}
|
||||
|
||||
public static function formatTime(int $msec, string $prop = 'game', string $src = 'timeAbbrev', bool $concat = false) : string
|
||||
{
|
||||
if ($msec < 0)
|
||||
$msec = 0;
|
||||
|
||||
private static function vspf($var, $args)
|
||||
$time = Util::parseTime($msec); // [$ms, $s, $m, $h, $d]
|
||||
$mult = [0, 1000, 60, 60, 24];
|
||||
$total = 0;
|
||||
$ref = [];
|
||||
$result = [];
|
||||
|
||||
if (is_array(self::$$prop[$src]))
|
||||
$ref = &self::$$prop[$src];
|
||||
else
|
||||
{
|
||||
trigger_error('Lang::formatTime - tried to access undefined property Lang::$'.$prop, E_USER_WARNING);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$msec)
|
||||
return self::vspf($ref[0], [0]);
|
||||
|
||||
if ($concat)
|
||||
{
|
||||
for ($i = 4; $i > 0; $i--)
|
||||
{
|
||||
$total += $time[$i];
|
||||
if (isset($ref[$i]) && ($total || ($i == 1 && !$result)))
|
||||
{
|
||||
$result[] = self::vspf($ref[$i], [$total]);
|
||||
$total = 0;
|
||||
}
|
||||
else
|
||||
$total *= $mult[$i];
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
for ($i = 4; $i > 0; $i--)
|
||||
{
|
||||
$total += $time[$i];
|
||||
if (isset($ref[$i]) && ($total || $i == 1))
|
||||
return self::vspf($ref[$i], [$total + ($time[$i-1] ?? 0) / $mult[$i]]);
|
||||
else
|
||||
$total *= $mult[$i];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function vspf(null|array|string $var, array $args = []) : null|array|string
|
||||
{
|
||||
if (is_array($var))
|
||||
{
|
||||
foreach ($var as &$v)
|
||||
$v == self::vspf($v, $args);
|
||||
$v = self::vspf($v, $args);
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
if (!$var) // may be null or empty. Handled differently depending on context
|
||||
return $var;
|
||||
|
||||
$var = Cfg::applyToString($var);
|
||||
|
||||
if ($args)
|
||||
$var = vsprintf($var, $args);
|
||||
|
||||
// line break
|
||||
// |n
|
||||
$var = str_replace('|n', '<br />', $var);
|
||||
|
||||
// color
|
||||
// |c<aarrggbb><word>|r
|
||||
$var = preg_replace('/\|cff([a-f0-9]{6})(.+?)\|r/i', '<span style="color: #$1;">$2</span>', $var);
|
||||
|
||||
// icon
|
||||
// |T<imgPath>:0:0:0:-1|t - not used, skip if found
|
||||
$var = preg_replace('/\|T[^\|]+\|t/', '', $var);
|
||||
|
||||
// hyperlink
|
||||
// |H<hyperlinkStruct>|h<name>|h - not used, truncate structure if found
|
||||
$var = preg_replace('/\|H[^\|]+\|h([^\|]+)\|h/', '$1', $var);
|
||||
|
||||
// french preposition : de
|
||||
// |2 <word>
|
||||
$var = preg_replace_callback('/\|2\s(\w)/i', function ($m) {
|
||||
if (in_array(strtolower($m[1]), ['a', 'e', 'h', 'i', 'o', 'u']))
|
||||
return "d'".$m[1];
|
||||
else
|
||||
return 'de '.$m[1];
|
||||
}, $var);
|
||||
|
||||
// russian word cunjugation thingy
|
||||
// |3-<number>(<word>)
|
||||
$var = preg_replace_callback('/\|3-(\d)\(([^\)]+)\)/i', function ($m) {
|
||||
switch ($m[0])
|
||||
{
|
||||
case 1: // seen cases
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
default: // passthrough .. unk case
|
||||
return $m[1];
|
||||
return self::unescapeUISequences($var);
|
||||
}
|
||||
|
||||
}, $var);
|
||||
/* Quoted from WoWWiki - UI Escape Sequences (https://wowwiki-archive.fandom.com/wiki/UI_escape_sequences)
|
||||
* number |1singular;plural;
|
||||
Will choose a word depending on whether the digit preceding it is 0/1 or not (i.e. 1,11,21 return the first string, as will 0,10,40). Note that unlike |4 singular and plural forms are separated by semi-colon.
|
||||
|
||||
// numeric switch
|
||||
// <number> |4<singular>:<plural>[:<plural2>];
|
||||
$var = preg_replace_callback('/([\d\.\,]+)([^\d]*)\|4([^:]*):([^;]*);/i', function ($m) {
|
||||
$plurals = explode(':', $m[4]);
|
||||
$result = '';
|
||||
* |2text
|
||||
Before vowels outputs d' (with apostrophe) and removes any leading spaces from text, otherwise outputs de (with trailing space)
|
||||
|
||||
if (count($plurals) == 2) // special case: ruRU
|
||||
* |3-formid(text)
|
||||
Displays text declined to the specified form (index ranges from 1 to GetNumDeclensionSets()).
|
||||
|
||||
* number |4singular:plural; -or- number |4singular:plural1:plural2;
|
||||
Will choose a form based on the number preceding it. More than two forms (separated by colons) may be required by locale 8 (ruRU).
|
||||
**/
|
||||
|
||||
public static function unescapeUISequences(string $var, int $fmt = -1) : string
|
||||
{
|
||||
switch (substr($m[1], -1)) // check last digit of number
|
||||
if (strpos($var, '|') === false)
|
||||
return $var;
|
||||
|
||||
// line break |n
|
||||
$var = preg_replace_callback('/\|n/i', function ($m) use ($fmt)
|
||||
{
|
||||
case 1:
|
||||
// but not 11 (teen number)
|
||||
if (!in_array($m[1], [11]))
|
||||
switch ($fmt)
|
||||
{
|
||||
$result = $m[3];
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
// but not 12, 13, 14 (teen number) [11 is passthrough]
|
||||
if (!in_array($m[1], [11, 12, 13, 14]))
|
||||
{
|
||||
$result = $plurals[0];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case -1: // default Lang::vspf case
|
||||
case self::FMT_HTML:
|
||||
return '<br />';
|
||||
case self::FMT_MARKUP:
|
||||
return '[br]';
|
||||
case self::FMT_RAW:
|
||||
default:
|
||||
$result = $plurals[1];
|
||||
return '';
|
||||
}
|
||||
}
|
||||
else
|
||||
$result = ($m[1] == 1 ? $m[3] : $plurals[0]);
|
||||
}, $var);
|
||||
|
||||
return $m[1].$m[2].$result;
|
||||
// color |c<aarrggbb><word>|r
|
||||
$var = preg_replace_callback('/\|c([[:xdigit:]]{2})([[:xdigit:]]{6})(.+?)\|r/is', function ($m) use ($fmt)
|
||||
{
|
||||
[$_, $a, $rgb, $text] = $m;
|
||||
|
||||
switch ($fmt)
|
||||
{
|
||||
case -1: // default Lang::vspf case
|
||||
case self::FMT_HTML:
|
||||
return sprintf('<span style="color: #%1$s%2$s;">%3$s</span>', $rgb, $a, $text);
|
||||
case self::FMT_MARKUP:
|
||||
return sprintf('[span color=#%1$s]%3$s[/span]', $rgb, $a, $text); // doesn't support alpha
|
||||
case self::FMT_RAW:
|
||||
default:
|
||||
return $text;
|
||||
}
|
||||
}, $var);
|
||||
|
||||
// icon |T<imgPath+File.blp>:0:0:0:-1|t
|
||||
$var = preg_replace_callback('/\|T([\w]+\\\)*([^\.:]+)(?:\.[bB][lL][pP])?:([^\|]+)\|t/', function ($m) use ($fmt)
|
||||
{
|
||||
/* iconParam - size1, size2, xoffset, yoffset
|
||||
size1 == 0; size2 omitted: Width = Height = TextHeight (always square!)
|
||||
size1 > 0; size2 omitted: Width = Height = size1 (always square!)
|
||||
size1 == 0; size2 == 0 : Width = Height = TextHeight (always square!)
|
||||
size1 > 0; size2 == 0 : Width = TextHeight; Height = size1 (size1 is height!!!)
|
||||
size1 == 0; size2 > 0 : Width = size2 * TextHeight; Height = TextHeight (size2 is an aspect ratio and defines width!!!)
|
||||
size1 > 0; size2 > 0 : Width = size1; Height = size2
|
||||
*/
|
||||
|
||||
[$_, $iconPath, $iconName, $iconParam] = $m;
|
||||
|
||||
switch ($fmt)
|
||||
{
|
||||
case self::FMT_HTML:
|
||||
return '<span class="icontiny" style="background-image: url('.Cfg::get('STATIC_URL').'/images/wow/icons/tiny/'.Util::lower($iconName).'.gif)">';
|
||||
case self::FMT_MARKUP:
|
||||
return '[icon name='.Util::lower($iconName).']';
|
||||
case self::FMT_RAW:
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}, $var);
|
||||
|
||||
// hyperlink |H<hyperlinkStruct>|h<name>|h
|
||||
$var = preg_replace_callback('/\|H([^:]+):([^\|]+)\|h([^\|]+)\|h/i', function ($m) use ($fmt)
|
||||
{
|
||||
/* type Params
|
||||
|Hchannel channelName, channelname == CHANNEL ? channelNr : null
|
||||
|Hachievement AchievementID, PlayerGUID, isComplete, Month, Day, Year, criteriaMask1, criteriaMask2, criteriaMask3, criteriaMask4 - 32bit masks of Achievement_criteria.dbc/UIOrder only for achievements that display a todo list
|
||||
|Hquest QuestID, QuestLevel
|
||||
|Hitem itemId enchantId gemId1 gemId2 gemId3 gemId4 suffixId uniqueId linkLevel
|
||||
|Henchant SpellID (from craftwindow)
|
||||
|Htalent TalentID, TalentRank
|
||||
|Hspell SpellID, PlayerLevel?
|
||||
|Htrade SpellID, curSkill, maxSkill, PlayerGUID, base64_encode(known recipes bitmask)
|
||||
|Hplayer Name
|
||||
|Hunit GUID ? - combatlog
|
||||
|Hicon ? "source"|"dest" - combatlog
|
||||
|Haction ? - combatlog
|
||||
*/
|
||||
|
||||
[$_, $linkType, $linkVars, $text] = $m;
|
||||
|
||||
$linkVars = explode(':', $linkVars);
|
||||
|
||||
$spfVars = ['', $linkVars[0], $text];
|
||||
|
||||
switch ($linkType)
|
||||
{
|
||||
case 'trade':
|
||||
case 'enchant':
|
||||
$linkType = 'spell';
|
||||
case 'achievement': // markdown COULD implement completed status
|
||||
case 'quest':
|
||||
case 'item': // markdown COULD implement enchantments/gems
|
||||
case 'spell':
|
||||
$spfVars[0] = $linkType;
|
||||
break;
|
||||
case 'talent':
|
||||
if ($spell = DB::Aowow()->selectCell('SELECT `spell` FROM ?_talents WHERE `id` = ?d AND `rank` = ?d', $linkVars[0], $linkVars[1]))
|
||||
{
|
||||
$spfVars[0] = 'spell';
|
||||
$spfVars[1] = $spell;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
switch ($fmt)
|
||||
{
|
||||
case self::FMT_HTML:
|
||||
return sprintf('<a href="?%s=%d">%s</a>', $spfVars);
|
||||
case self::FMT_MARKUP:
|
||||
return sprintf('[%s=%d]', $spfVars);
|
||||
case self::FMT_RAW:
|
||||
default:
|
||||
return sprintf('(%s #%d) %s', $spfVars);
|
||||
}
|
||||
}, $var);
|
||||
|
||||
// |1 - digit singular/plural <number> |1<singular;<plural>;
|
||||
$var = preg_replace_callback('/(\d+)\s*\|1([^;]+);([^;]+);/is', function ($m)
|
||||
{
|
||||
[$_, $num, $singular, $plural] = $m;
|
||||
|
||||
switch ($num[-1])
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
return $num . ' ' . $singular;
|
||||
default:
|
||||
return $num . ' ' . $plural;
|
||||
}
|
||||
}, $var);
|
||||
|
||||
// |2 - frFR preposition: de |2 <word>
|
||||
$var = preg_replace_callback('/\|2\s?(\w)/i', function ($m)
|
||||
{
|
||||
[$_, $word] = $m;
|
||||
|
||||
switch (strtolower($word[1]))
|
||||
{
|
||||
case 'h':
|
||||
if (self::$locale != Locale::FR)
|
||||
return 'de ' . $word;
|
||||
case 'a':
|
||||
case 'e':
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'u':
|
||||
return "d'" . $word;
|
||||
default:
|
||||
return 'de ' . $word;
|
||||
}
|
||||
}, $var);
|
||||
|
||||
// |3 - ruRU declinations |3-<caseIdx>(<word>)
|
||||
$var = preg_replace_callback('/\|3-(\d)\(([^\)]+)\)/iu', function ($m)
|
||||
{
|
||||
[$_, $caseIdx, $word] = $m;
|
||||
|
||||
if ($caseIdx > 11 || $caseIdx < 1) // max caseIdx seen in DeclinedWordCases.dbc
|
||||
return $word;
|
||||
|
||||
if (preg_match('/\P{Cyrillic}/iu', $word)) // not in cyrillic script
|
||||
return $word;
|
||||
|
||||
if ($declWord = DB::Aowow()->selectCell('SELECT dwc.word FROM ?_declinedwordcases dwc JOIN ?_declinedword dc ON dwc.wordId = dc.id WHERE dwc.caseIdx = ?d AND dc.word = ?', $caseIdx, $word))
|
||||
return $declWord;
|
||||
|
||||
return $word;
|
||||
}, $var);
|
||||
|
||||
// |4 - numeric switch <number> |4<singular>:<plural>[:<plural2>];
|
||||
$var = preg_replace_callback('/([\d\.\,]+)([^\d]*)\|4([^:]*):([^:;]+)(?::([^;]+))?;/is', function ($m)
|
||||
{
|
||||
[$_, $num, $pad, $singular, $plural1, $plural2] = array_pad($m, 6, null);
|
||||
|
||||
if (self::$locale != Locale::RU || !$plural2)
|
||||
return $num . $pad . ($num == 1 ? $singular : $plural1);
|
||||
|
||||
// singular - ends in 1, but not teen number
|
||||
if ($num[-1] == 1 && $num != 11)
|
||||
return $num . $pad . $singular;
|
||||
|
||||
// genitive singular - ends in 2, 3, 4, but not teen number
|
||||
if (($num[-1] == 2 && $num != 12) || ($num[-1] == 3 && $num != 13) || ($num[-1] == 4 && $num != 14))
|
||||
return $num . $pad . $plural1;
|
||||
|
||||
// genitive plural - everything else
|
||||
return $num . $pad . $plural2;
|
||||
}, $var);
|
||||
|
||||
return $var;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
@@ -7,9 +9,19 @@ if (!defined('AOWOW_REVISION'))
|
||||
// exclude & weightscales are handled as Ajax
|
||||
class AccountPage extends GenericPage
|
||||
{
|
||||
protected $text = '';
|
||||
protected $head = '';
|
||||
protected $token = '';
|
||||
protected $infobox = [];
|
||||
protected $resetPass = false;
|
||||
protected $forceTabs = false;
|
||||
|
||||
protected $tpl = 'acc-dashboard';
|
||||
protected $js = [[JS_FILE, 'user.js'], [JS_FILE, 'profile.js']];
|
||||
protected $css = [[CSS_FILE, 'Profiler.css']];
|
||||
protected $scripts = array(
|
||||
[SC_JS_FILE, 'js/user.js'],
|
||||
[SC_JS_FILE, 'js/profile.js'],
|
||||
[SC_CSS_FILE, 'css/Profiler.css']
|
||||
);
|
||||
protected $mode = CACHE_TYPE_NONE;
|
||||
protected $category = null;
|
||||
protected $validCats = array(
|
||||
@@ -34,10 +46,10 @@ class AccountPage extends GenericPage
|
||||
|
||||
protected $_post = array(
|
||||
'username' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'password' => ['filter' => FILTER_UNSAFE_RAW],
|
||||
'c_password' => ['filter' => FILTER_UNSAFE_RAW],
|
||||
'token' => ['filter' => FILTER_UNSAFE_RAW],
|
||||
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => 'AccountPage::rememberCallback'],
|
||||
'password' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'],
|
||||
'c_password' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'],
|
||||
'token' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
|
||||
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AccountPage::rememberCallback'],
|
||||
'email' => ['filter' => FILTER_SANITIZE_EMAIL]
|
||||
);
|
||||
|
||||
@@ -51,10 +63,10 @@ class AccountPage extends GenericPage
|
||||
if ($pageParam)
|
||||
{
|
||||
// requires auth && not authed
|
||||
if ($this->validCats[$pageParam][0] && !User::$id)
|
||||
if ($this->validCats[$pageParam][0] && !User::isLoggedIn())
|
||||
$this->forwardToSignIn('account='.$pageParam);
|
||||
// doesn't require auth && authed
|
||||
else if (!$this->validCats[$pageParam][0] && User::$id)
|
||||
else if (!$this->validCats[$pageParam][0] && User::isLoggedIn())
|
||||
header('Location: ?account', true, 302); // goto dashboard
|
||||
}
|
||||
}
|
||||
@@ -75,10 +87,10 @@ class AccountPage extends GenericPage
|
||||
switch ($this->category[0])
|
||||
{
|
||||
case 'forgotpassword':
|
||||
if (CFG_ACC_AUTH_MODE != AUTH_MODE_SELF)
|
||||
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
|
||||
{
|
||||
if (CFG_ACC_EXT_RECOVER_URL)
|
||||
header('Location: '.CFG_ACC_EXT_RECOVER_URL, true, 302);
|
||||
if (Cfg::get('ACC_EXT_RECOVER_URL'))
|
||||
header('Location: '.Cfg::get('ACC_EXT_RECOVER_URL'), true, 302);
|
||||
else
|
||||
$this->error();
|
||||
}
|
||||
@@ -92,10 +104,10 @@ class AccountPage extends GenericPage
|
||||
$this->head = sprintf(Lang::account('recoverPass'), $nStep);
|
||||
break;
|
||||
case 'forgotusername':
|
||||
if (CFG_ACC_AUTH_MODE != AUTH_MODE_SELF)
|
||||
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
|
||||
{
|
||||
if (CFG_ACC_EXT_RECOVER_URL)
|
||||
header('Location: '.CFG_ACC_EXT_RECOVER_URL, true, 302);
|
||||
if (Cfg::get('ACC_EXT_RECOVER_URL'))
|
||||
header('Location: '.Cfg::get('ACC_EXT_RECOVER_URL'), true, 302);
|
||||
else
|
||||
$this->error();
|
||||
}
|
||||
@@ -125,23 +137,20 @@ class AccountPage extends GenericPage
|
||||
if ($err = $this->doSignIn())
|
||||
$this->error = $err;
|
||||
else
|
||||
{
|
||||
session_regenerate_id(true); // user status changed => regenerate id
|
||||
header('Location: '.$this->getNext(true), true, 302);
|
||||
}
|
||||
}
|
||||
else if ($this->_get['token'] && ($_ = DB::Aowow()->selectCell('SELECT user FROM ?_account WHERE status IN (?a) AND token = ? AND statusTimer > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token'])))
|
||||
else if ($this->_get['token'] && ($_ = DB::Aowow()->selectCell('SELECT `username` FROM ?_account WHERE `status` IN (?a) AND `token` = ? AND `statusTimer` > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token'])))
|
||||
$this->user = $_;
|
||||
|
||||
break;
|
||||
case 'signup':
|
||||
if (!CFG_ACC_ALLOW_REGISTER)
|
||||
if (!Cfg::get('ACC_ALLOW_REGISTER'))
|
||||
$this->error();
|
||||
|
||||
if (CFG_ACC_AUTH_MODE != AUTH_MODE_SELF)
|
||||
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
|
||||
{
|
||||
if (CFG_ACC_EXT_CREATE_URL)
|
||||
header('Location: '.CFG_ACC_EXT_CREATE_URL, true, 302);
|
||||
if (Cfg::get('ACC_EXT_CREATE_URL'))
|
||||
header('Location: '.Cfg::get('ACC_EXT_CREATE_URL'), true, 302);
|
||||
else
|
||||
$this->error();
|
||||
}
|
||||
@@ -162,7 +171,7 @@ class AccountPage extends GenericPage
|
||||
{
|
||||
$nStep = 2;
|
||||
DB::Aowow()->query('UPDATE ?_account SET status = ?d, statusTimer = 0, token = 0, userGroups = ?d WHERE token = ?', ACC_STATUS_OK, U_GROUP_NONE, $this->_get['token']);
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, ?d + 1, UNIX_TIMESTAMP() + ?d)', User::$ip, CFG_ACC_FAILED_AUTH_COUNT, CFG_ACC_FAILED_AUTH_BLOCK);
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, ?d + 1, UNIX_TIMESTAMP() + ?d)', User::$ip, Cfg::get('ACC_FAILED_AUTH_COUNT'), Cfg::get('ACC_FAILED_AUTH_BLOCK'));
|
||||
|
||||
$this->text = sprintf(Lang::account('accActivated'), $this->_get['token']);
|
||||
}
|
||||
@@ -172,6 +181,8 @@ class AccountPage extends GenericPage
|
||||
$this->head = sprintf(Lang::account('register'), $nStep);
|
||||
break;
|
||||
case 'signout':
|
||||
DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `sessionId` = ?', time(), SESSION_LOGOUT, session_id());
|
||||
|
||||
User::destroy();
|
||||
default:
|
||||
header('Location: '.$this->getNext(true), true, 302);
|
||||
@@ -188,11 +199,11 @@ class AccountPage extends GenericPage
|
||||
|
||||
private function createDashboard()
|
||||
{
|
||||
if (!User::$id)
|
||||
if (!User::isLoggedIn())
|
||||
$this->forwardToSignIn('account');
|
||||
|
||||
$user = DB::Aowow()->selectRow('SELECT * FROM ?_account WHERE id = ?d', User::$id);
|
||||
$bans = DB::Aowow()->select('SELECT ab.*, a.displayName, ab.id AS ARRAY_KEY FROM ?_account_banned ab LEFT JOIN ?_account a ON a.id = ab.staffId WHERE ab.userId = ?d', User::$id);
|
||||
$user = DB::Aowow()->selectRow('SELECT * FROM ?_account WHERE `id` = ?d', User::$id);
|
||||
$bans = DB::Aowow()->select('SELECT ab.*, a.`username`, ab.`id` AS ARRAY_KEY FROM ?_account_banned ab LEFT JOIN ?_account a ON a.`id` = ab.`staffId` WHERE ab.`userId` = ?d', User::$id);
|
||||
|
||||
/***********/
|
||||
/* Infobox */
|
||||
@@ -224,7 +235,7 @@ class AccountPage extends GenericPage
|
||||
continue;
|
||||
|
||||
$this->banned = array(
|
||||
'by' => [$b['staffId'], $b['displayName']],
|
||||
'by' => [$b['staffId'], $b['username']],
|
||||
'end' => $b['end'],
|
||||
'reason' => $b['reason']
|
||||
);
|
||||
@@ -248,7 +259,7 @@ class AccountPage extends GenericPage
|
||||
}
|
||||
|
||||
// comments
|
||||
if ($_ = CommunityContent::getCommentPreviews(['user' => User::$id, 'replies' => false]))
|
||||
if ($_ = CommunityContent::getCommentPreviews(['user' => User::$id, 'comments' => true]))
|
||||
{
|
||||
// needs foundCount for params
|
||||
// _totalCount: 377,
|
||||
@@ -346,22 +357,27 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
|
||||
if (!User::isValidPass($this->_post['password']))
|
||||
return Lang::account('wrongPass');
|
||||
|
||||
switch (User::Auth($this->_post['username'], $this->_post['password']))
|
||||
switch (User::authenticate($this->_post['username'], $this->_post['password']))
|
||||
{
|
||||
case AUTH_OK:
|
||||
if (!User::$ip)
|
||||
return Lang::main('intError');
|
||||
|
||||
// reset account status, update expiration
|
||||
DB::Aowow()->query('UPDATE ?_account SET prevIP = IF(curIp = ?, prevIP, curIP), curIP = IF(curIp = ?, curIP, ?), allowExpire = ?d, status = IF(status = ?d, status, 0), statusTimer = IF(status = ?d, statusTimer, 0), token = IF(status = ?d, token, "") WHERE user = ?',
|
||||
DB::Aowow()->query('UPDATE ?_account SET `prevIP` = IF(`curIp` = ?, `prevIP`, `curIP`), `curIP` = IF(`curIp` = ?, `curIP`, ?), `status` = IF(`status` = ?d, `status`, 0), `statusTimer` = IF(`status` = ?d, `statusTimer`, 0), `token` = IF(`status` = ?d, `token`, "") WHERE LOWER(`username`) = LOWER(?)',
|
||||
User::$ip, User::$ip, User::$ip,
|
||||
$this->_post['remember_me'] != 'yes',
|
||||
ACC_STATUS_NEW, ACC_STATUS_NEW, ACC_STATUS_NEW,
|
||||
$this->_post['username']
|
||||
);
|
||||
|
||||
if (User::init())
|
||||
User::save(); // overwrites the current user
|
||||
session_regenerate_id(true); // user status changed => regenerate id
|
||||
|
||||
// create new session entry
|
||||
DB::Aowow()->query('INSERT INTO ?_account_sessions (`userId`, `sessionId`, `created`, `expires`, `touched`, `deviceInfo`, `ip`, `status`) VALUES (?d, ?, ?d, ?d, ?d, ?, ?, ?d)',
|
||||
User::$id, session_id(), time(), $this->_post['remember_me'] ? 0 : time() + Cfg::get('SESSION_TIMEOUT_DELAY'), time(), User::$agent, User::$ip, SESSION_ACTIVE);
|
||||
|
||||
if (User::init()) // reinitialize the user
|
||||
User::save();
|
||||
|
||||
return;
|
||||
case AUTH_BANNED:
|
||||
@@ -376,7 +392,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
|
||||
return Lang::account('wrongPass');
|
||||
case AUTH_IPBANNED:
|
||||
User::destroy();
|
||||
return sprintf(Lang::account('loginExceeded'), Util::formatTime(CFG_ACC_FAILED_AUTH_BLOCK * 1000));
|
||||
return sprintf(Lang::account('loginExceeded'), Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000));
|
||||
case AUTH_INTERNAL_ERR:
|
||||
User::destroy();
|
||||
return Lang::main('intError');
|
||||
@@ -407,56 +423,55 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
|
||||
return Lang::main('intError');
|
||||
|
||||
// limit account creation
|
||||
$ip = DB::Aowow()->selectRow('SELECT ip, count, unbanDate FROM ?_account_bannedips WHERE type = 1 AND ip = ?', User::$ip);
|
||||
if ($ip && $ip['count'] >= CFG_ACC_FAILED_AUTH_COUNT && $ip['unbanDate'] >= time())
|
||||
$ip = DB::Aowow()->selectRow('SELECT `ip`, `count`, `unbanDate` FROM ?_account_bannedips WHERE `type` = 1 AND `ip` = ?', User::$ip);
|
||||
if ($ip && $ip['count'] >= Cfg::get('ACC_FAILED_AUTH_COUNT') && $ip['unbanDate'] >= time())
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ? AND type = 1', CFG_ACC_FAILED_AUTH_BLOCK, User::$ip);
|
||||
return sprintf(Lang::account('signupExceeded'), Util::formatTime(CFG_ACC_FAILED_AUTH_BLOCK * 1000));
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ? AND `type` = 1', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip);
|
||||
return sprintf(Lang::account('signupExceeded'), Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000));
|
||||
}
|
||||
|
||||
// username taken
|
||||
if ($_ = DB::Aowow()->SelectCell('SELECT user FROM ?_account WHERE (user = ? OR email = ?) AND (status <> ?d OR (status = ?d AND statusTimer > UNIX_TIMESTAMP()))', $this->_post['username'], $this->_post['email'], ACC_STATUS_NEW, ACC_STATUS_NEW))
|
||||
if ($_ = DB::Aowow()->SelectCell('SELECT `username` FROM ?_account WHERE (`username` = ? OR `email` = ?) AND (`status` <> ?d OR (`status` = ?d AND `statusTimer` > UNIX_TIMESTAMP()))', $this->_post['username'], $this->_post['email'], ACC_STATUS_NEW, ACC_STATUS_NEW))
|
||||
return $_ == $this->_post['username'] ? Lang::account('nameInUse') : Lang::account('mailInUse');
|
||||
|
||||
// create..
|
||||
$token = Util::createHash();
|
||||
$ok = DB::Aowow()->query('REPLACE INTO ?_account (user, passHash, displayName, email, joindate, curIP, allowExpire, locale, userGroups, status, statusTimer, token) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?d, ?d, ?d, ?d, UNIX_TIMESTAMP() + ?d, ?)',
|
||||
$ok = DB::Aowow()->query('REPLACE INTO ?_account (`login`, `passHash`, `username`, `email`, `joindate`, `curIP`, `locale`, `userGroups`, `status`, `statusTimer`, `token`) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?d, ?d, ?d, ?d, UNIX_TIMESTAMP() + ?d, ?)',
|
||||
$this->_post['username'],
|
||||
User::hashCrypt($this->_post['password']),
|
||||
Util::ucFirst($this->_post['username']),
|
||||
$this->_post['username'],
|
||||
$this->_post['email'],
|
||||
User::$ip,
|
||||
$this->_post['remember_me'] != 'yes',
|
||||
User::$localeId,
|
||||
Lang::getLocale()->value,
|
||||
U_GROUP_PENDING,
|
||||
ACC_STATUS_NEW,
|
||||
CFG_ACC_CREATE_SAVE_DECAY,
|
||||
Cfg::get('ACC_CREATE_SAVE_DECAY'),
|
||||
$token
|
||||
);
|
||||
if (!$ok)
|
||||
return Lang::main('intError');
|
||||
else if ($_ = $this->sendMail(Lang::user('accConfirm', 0), sprintf(Lang::user('accConfirm', 1), $token), CFG_ACC_CREATE_SAVE_DECAY))
|
||||
{
|
||||
|
||||
if (!Util::sendMail($this->_post['email'], 'activate-account', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
|
||||
return Lang::main('intError2', ['send mail']);
|
||||
|
||||
if ($id = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE token = ?', $token))
|
||||
Util::gainSiteReputation($id, SITEREP_ACTION_REGISTER);
|
||||
|
||||
// success:: update ip-bans
|
||||
if (!$ip || $ip['unbanDate'] < time())
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, 1, UNIX_TIMESTAMP() + ?d)', User::$ip, CFG_ACC_FAILED_AUTH_BLOCK);
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, 1, UNIX_TIMESTAMP() + ?d)', User::$ip, Cfg::get('ACC_FAILED_AUTH_BLOCK'));
|
||||
else
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ? AND type = 1', CFG_ACC_FAILED_AUTH_BLOCK, User::$ip);
|
||||
|
||||
return $_;
|
||||
}
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ? AND type = 1', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip);
|
||||
}
|
||||
|
||||
private function doRecoverPass()
|
||||
{
|
||||
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_PASS, CFG_ACC_RECOVERY_DECAY, $token))
|
||||
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_PASS, Cfg::get('ACC_RECOVERY_DECAY'), $token))
|
||||
return $_;
|
||||
|
||||
// send recovery mail
|
||||
return $this->sendMail(Lang::user('resetPass', 0), sprintf(Lang::user('resetPass', 1), $token), CFG_ACC_RECOVERY_DECAY);
|
||||
if (!Util::sendMail($this->_post['email'], 'reset-password', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
|
||||
return Lang::main('intError2', ['send mail']);
|
||||
}
|
||||
|
||||
private function doResetPass()
|
||||
@@ -467,28 +482,28 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
|
||||
if (!Util::isValidEmail($this->_post['email']))
|
||||
return Lang::account('emailInvalid');
|
||||
|
||||
$uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE token = ? AND email = ? AND status = ?d AND statusTimer > UNIX_TIMESTAMP()',
|
||||
$userData = DB::Aowow()->selectRow('SELECT `id, `passHash` FROM ?_account WHERE `token` = ? AND `email` = ? AND `status` = ?d AND `statusTimer` > UNIX_TIMESTAMP()',
|
||||
$this->_post['token'],
|
||||
$this->_post['email'],
|
||||
ACC_STATUS_RECOVER_PASS
|
||||
);
|
||||
if (!$uId)
|
||||
if (!$userData)
|
||||
return Lang::account('emailNotFound'); // assume they didn't meddle with the token
|
||||
|
||||
if (!User::verifyCrypt($this->_post['c_password']))
|
||||
if (!User::verifyCrypt($this->_post['c_password'], $userData['passHash']))
|
||||
return Lang::account('newPassDiff');
|
||||
|
||||
if (!DB::Aowow()->query('UPDATE ?_account SET passHash = ?, status = ?d WHERE id = ?d', User::hashCrypt($this->_post['c_password']), ACC_STATUS_OK, $uId))
|
||||
if (!DB::Aowow()->query('UPDATE ?_account SET `passHash` = ?, `status` = ?d WHERE `id` = ?d', User::hashCrypt($this->_post['c_password']), ACC_STATUS_OK, $userData['id']))
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
private function doRecoverUser()
|
||||
{
|
||||
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_USER, CFG_ACC_RECOVERY_DECAY, $token))
|
||||
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_USER, Cfg::get('ACC_RECOVERY_DECAY'), $token))
|
||||
return $_;
|
||||
|
||||
// send recovery mail
|
||||
return $this->sendMail(Lang::user('recoverUser', 0), sprintf(Lang::user('recoverUser', 1), $token), CFG_ACC_RECOVERY_DECAY);
|
||||
if (!Util::sendMail($this->_post['email'], 'recover-user', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
|
||||
return Lang::main('intError2', ['send mail']);
|
||||
}
|
||||
|
||||
private function initRecovery($type, $delay, &$token)
|
||||
@@ -506,19 +521,6 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
private function sendMail($subj, $msg, $delay = 300)
|
||||
{
|
||||
// send recovery mail
|
||||
$subj = CFG_NAME_SHORT.Lang::main('colon') . $subj;
|
||||
$msg .= "\r\n\r\n".sprintf(Lang::user('tokenExpires'), Util::formatTime($delay * 1000))."\r\n";
|
||||
$header = 'From: '.CFG_CONTACT_EMAIL . "\r\n" .
|
||||
'Reply-To: '.CFG_CONTACT_EMAIL . "\r\n" .
|
||||
'X-Mailer: PHP/' . phpversion();
|
||||
|
||||
if (!mail($this->_post['email'], $subj, $msg, $header))
|
||||
return sprintf(Lang::main('intError2'), 'send mail');
|
||||
}
|
||||
|
||||
private function getNext($forHeader = false)
|
||||
{
|
||||
$next = $forHeader ? '.' : '';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user