diff --git a/data/wordlist.txt b/data/wordlist.txt new file mode 100644 index 000000000..fb0a9e35e --- /dev/null +++ b/data/wordlist.txt @@ -0,0 +1,1296 @@ +1111 acid +1112 acorn +1113 acre +1114 acts +1115 afar +1116 affix +1121 aged +1122 agent +1123 agile +1124 aging +1125 agony +1126 ahead +1131 aide +1132 aids +1133 aim +1134 ajar +1135 alarm +1136 alias +1141 alibi +1142 alien +1143 alike +1144 alive +1145 aloe +1146 aloft +1151 aloha +1152 alone +1153 amend +1154 amino +1155 ample +1156 amuse +1161 angel +1162 anger +1163 angle +1164 ankle +1165 apple +1166 april +1211 apron +1212 aqua +1213 area +1214 arena +1215 argue +1216 arise +1221 armed +1222 armor +1223 army +1224 aroma +1225 array +1226 arson +1231 art +1232 ashen +1233 ashes +1234 atlas +1235 atom +1236 attic +1241 audio +1242 avert +1243 avoid +1244 awake +1245 award +1246 awoke +1251 axis +1252 bacon +1253 badge +1254 bagel +1255 baggy +1256 baked +1261 baker +1262 balmy +1263 banjo +1264 barge +1265 barn +1266 bash +1311 basil +1312 bask +1313 batch +1314 bath +1315 baton +1316 bats +1321 blade +1322 blank +1323 blast +1324 blaze +1325 bleak +1326 blend +1331 bless +1332 blimp +1333 blink +1334 bloat +1335 blob +1336 blog +1341 blot +1342 blunt +1343 blurt +1344 blush +1345 boast +1346 boat +1351 body +1352 boil +1353 bok +1354 bolt +1355 boned +1356 boney +1361 bonus +1362 bony +1363 book +1364 booth +1365 boots +1366 boss +1411 botch +1412 both +1413 boxer +1414 breed +1415 bribe +1416 brick +1421 bride +1422 brim +1423 bring +1424 brink +1425 brisk +1426 broad +1431 broil +1432 broke +1433 brook +1434 broom +1435 brush +1436 buck +1441 bud +1442 buggy +1443 bulge +1444 bulk +1445 bully +1446 bunch +1451 bunny +1452 bunt +1453 bush +1454 bust +1455 busy +1456 buzz +1461 cable +1462 cache +1463 cadet +1464 cage +1465 cake +1466 calm +1511 cameo +1512 canal +1513 candy +1514 cane +1515 canon +1516 cape +1521 card +1522 cargo +1523 carol +1524 carry +1525 carve +1526 case +1531 cash +1532 cause +1533 cedar +1534 chain +1535 chair +1536 chant +1541 chaos +1542 charm +1543 chase +1544 cheek +1545 cheer +1546 chef +1551 chess +1552 chest +1553 chew +1554 chief +1555 chili +1556 chill +1561 chip +1562 chomp +1563 chop +1564 chow +1565 chuck +1566 chump +1611 chunk +1612 churn +1613 chute +1614 cider +1615 cinch +1616 city +1621 civic +1622 civil +1623 clad +1624 claim +1625 clamp +1626 clap +1631 clash +1632 clasp +1633 class +1634 claw +1635 clay +1636 clean +1641 clear +1642 cleat +1643 cleft +1644 clerk +1645 click +1646 cling +1651 clink +1652 clip +1653 cloak +1654 clock +1655 clone +1656 cloth +1661 cloud +1662 clump +1663 coach +1664 coast +1665 coat +1666 cod +2111 coil +2112 coke +2113 cola +2114 cold +2115 colt +2116 coma +2121 come +2122 comic +2123 comma +2124 cone +2125 cope +2126 copy +2131 coral +2132 cork +2133 cost +2134 cot +2135 couch +2136 cough +2141 cover +2142 cozy +2143 craft +2144 cramp +2145 crane +2146 crank +2151 crate +2152 crave +2153 crawl +2154 crazy +2155 creme +2156 crepe +2161 crept +2162 crib +2163 cried +2164 crisp +2165 crook +2166 crop +2211 cross +2212 crowd +2213 crown +2214 crumb +2215 crush +2216 crust +2221 cub +2222 cult +2223 cupid +2224 cure +2225 curl +2226 curry +2231 curse +2232 curve +2233 curvy +2234 cushy +2235 cut +2236 cycle +2241 dab +2242 dad +2243 daily +2244 dairy +2245 daisy +2246 dance +2251 dandy +2252 darn +2253 dart +2254 dash +2255 data +2256 date +2261 dawn +2262 deaf +2263 deal +2264 dean +2265 debit +2266 debt +2311 debug +2312 decaf +2313 decal +2314 decay +2315 deck +2316 decor +2321 decoy +2322 deed +2323 delay +2324 denim +2325 dense +2326 dent +2331 depth +2332 derby +2333 desk +2334 dial +2335 diary +2336 dice +2341 dig +2342 dill +2343 dime +2344 dimly +2345 diner +2346 dingy +2351 disco +2352 dish +2353 disk +2354 ditch +2355 ditzy +2356 dizzy +2361 dock +2362 dodge +2363 doing +2364 doll +2365 dome +2366 donor +2411 donut +2412 dose +2413 dot +2414 dove +2415 down +2416 dowry +2421 doze +2422 drab +2423 drama +2424 drank +2425 draw +2426 dress +2431 dried +2432 drift +2433 drill +2434 drive +2435 drone +2436 droop +2441 drove +2442 drown +2443 drum +2444 dry +2445 duck +2446 duct +2451 dude +2452 dug +2453 duke +2454 duo +2455 dusk +2456 dust +2461 duty +2462 dwarf +2463 dwell +2464 eagle +2465 early +2466 earth +2511 easel +2512 east +2513 eaten +2514 eats +2515 ebay +2516 ebony +2521 ebook +2522 echo +2523 edge +2524 eel +2525 eject +2526 elbow +2531 elder +2532 elf +2533 elk +2534 elm +2535 elope +2536 elude +2541 elves +2542 email +2543 emit +2544 empty +2545 emu +2546 enter +2551 entry +2552 envoy +2553 equal +2554 erase +2555 error +2556 erupt +2561 essay +2562 etch +2563 evade +2564 even +2565 evict +2566 evil +2611 evoke +2612 exact +2613 exit +2614 fable +2615 faced +2616 fact +2621 fade +2622 fall +2623 false +2624 fancy +2625 fang +2626 fax +2631 feast +2632 feed +2633 femur +2634 fence +2635 fend +2636 ferry +2641 fetal +2642 fetch +2643 fever +2644 fiber +2645 fifth +2646 fifty +2651 film +2652 filth +2653 final +2654 finch +2655 fit +2656 five +2661 flag +2662 flaky +2663 flame +2664 flap +2665 flask +2666 fled +3111 flick +3112 fling +3113 flint +3114 flip +3115 flirt +3116 float +3121 flock +3122 flop +3123 floss +3124 flyer +3125 foam +3126 foe +3131 fog +3132 foil +3133 folic +3134 folk +3135 food +3136 fool +3141 found +3142 fox +3143 foyer +3144 frail +3145 frame +3146 fray +3151 fresh +3152 fried +3153 frill +3154 frisk +3155 from +3156 front +3161 frost +3162 froth +3163 frown +3164 froze +3165 fruit +3166 gag +3211 gains +3212 gala +3213 game +3214 gap +3215 gas +3216 gave +3221 gear +3222 gecko +3223 geek +3224 gem +3225 genre +3226 gift +3231 gig +3232 gills +3233 given +3234 giver +3235 glad +3236 glass +3241 glide +3242 gloss +3243 glove +3244 glow +3245 glue +3246 goal +3251 going +3252 golf +3253 gong +3254 good +3255 gooey +3256 goofy +3261 gore +3262 gown +3263 grab +3264 grain +3265 grant +3266 grape +3311 graph +3312 grasp +3313 grass +3314 grave +3315 gravy +3316 gray +3321 green +3322 greet +3323 grew +3324 grid +3325 grief +3326 grill +3331 grip +3332 grit +3333 groom +3334 grope +3335 growl +3336 grub +3341 grunt +3342 guide +3343 gulf +3344 gulp +3345 gummy +3346 guru +3351 gush +3352 gut +3353 guy +3354 habit +3355 half +3356 halo +3361 halt +3362 happy +3363 harm +3364 hash +3365 hasty +3366 hatch +3411 hate +3412 haven +3413 hazel +3414 hazy +3415 heap +3416 heat +3421 heave +3422 hedge +3423 hefty +3424 help +3425 herbs +3426 hers +3431 hub +3432 hug +3433 hula +3434 hull +3435 human +3436 humid +3441 hump +3442 hung +3443 hunk +3444 hunt +3445 hurry +3446 hurt +3451 hush +3452 hut +3453 ice +3454 icing +3455 icon +3456 icy +3461 igloo +3462 image +3463 ion +3464 iron +3465 islam +3466 issue +3511 item +3512 ivory +3513 ivy +3514 jab +3515 jam +3516 jaws +3521 jazz +3522 jeep +3523 jelly +3524 jet +3525 jiffy +3526 job +3531 jog +3532 jolly +3533 jolt +3534 jot +3535 joy +3536 judge +3541 juice +3542 juicy +3543 july +3544 jumbo +3545 jump +3546 junky +3551 juror +3552 jury +3553 keep +3554 keg +3555 kept +3556 kick +3561 kilt +3562 king +3563 kite +3564 kitty +3565 kiwi +3566 knee +3611 knelt +3612 koala +3613 kung +3614 ladle +3615 lady +3616 lair +3621 lake +3622 lance +3623 land +3624 lapel +3625 large +3626 lash +3631 lasso +3632 last +3633 latch +3634 late +3635 lazy +3636 left +3641 legal +3642 lemon +3643 lend +3644 lens +3645 lent +3646 level +3651 lever +3652 lid +3653 life +3654 lift +3655 lilac +3656 lily +3661 limb +3662 limes +3663 line +3664 lint +3665 lion +3666 lip +4111 list +4112 lived +4113 liver +4114 lunar +4115 lunch +4116 lung +4121 lurch +4122 lure +4123 lurk +4124 lying +4125 lyric +4126 mace +4131 maker +4132 malt +4133 mama +4134 mango +4135 manor +4136 many +4141 map +4142 march +4143 mardi +4144 marry +4145 mash +4146 match +4151 mate +4152 math +4153 moan +4154 mocha +4155 moist +4156 mold +4161 mom +4162 moody +4163 mop +4164 morse +4165 most +4166 motor +4211 motto +4212 mount +4213 mouse +4214 mousy +4215 mouth +4216 move +4221 movie +4222 mower +4223 mud +4224 mug +4225 mulch +4226 mule +4231 mull +4232 mumbo +4233 mummy +4234 mural +4235 muse +4236 music +4241 musky +4242 mute +4243 nacho +4244 nag +4245 nail +4246 name +4251 nanny +4252 nap +4253 navy +4254 near +4255 neat +4256 neon +4261 nerd +4262 nest +4263 net +4264 next +4265 niece +4266 ninth +4311 nutty +4312 oak +4313 oasis +4314 oat +4315 ocean +4316 oil +4321 old +4322 olive +4323 omen +4324 onion +4325 only +4326 ooze +4331 opal +4332 open +4333 opera +4334 opt +4335 otter +4336 ouch +4341 ounce +4342 outer +4343 oval +4344 oven +4345 owl +4346 ozone +4351 pace +4352 pagan +4353 pager +4354 palm +4355 panda +4356 panic +4361 pants +4362 panty +4363 paper +4364 park +4365 party +4366 pasta +4411 patch +4412 path +4413 patio +4414 payer +4415 pecan +4416 penny +4421 pep +4422 perch +4423 perky +4424 perm +4425 pest +4426 petal +4431 petri +4432 petty +4433 photo +4434 plank +4435 plant +4436 plaza +4441 plead +4442 plot +4443 plow +4444 pluck +4445 plug +4446 plus +4451 poach +4452 pod +4453 poem +4454 poet +4455 pogo +4456 point +4461 poise +4462 poker +4463 polar +4464 polio +4465 polka +4466 polo +4511 pond +4512 pony +4513 poppy +4514 pork +4515 poser +4516 pouch +4521 pound +4522 pout +4523 power +4524 prank +4525 press +4526 print +4531 prior +4532 prism +4533 prize +4534 probe +4535 prong +4536 proof +4541 props +4542 prude +4543 prune +4544 pry +4545 pug +4546 pull +4551 pulp +4552 pulse +4553 puma +4554 punch +4555 punk +4556 pupil +4561 puppy +4562 purr +4563 purse +4564 push +4565 putt +4566 quack +4611 quake +4612 query +4613 quiet +4614 quill +4615 quilt +4616 quit +4621 quota +4622 quote +4623 rabid +4624 race +4625 rack +4626 radar +4631 radio +4632 raft +4633 rage +4634 raid +4635 rail +4636 rake +4641 rally +4642 ramp +4643 ranch +4644 range +4645 rank +4646 rant +4651 rash +4652 raven +4653 reach +4654 react +4655 ream +4656 rebel +4661 recap +4662 relax +4663 relay +4664 relic +4665 remix +4666 repay +5111 repel +5112 reply +5113 rerun +5114 reset +5115 rhyme +5116 rice +5121 rich +5122 ride +5123 rigid +5124 rigor +5125 rinse +5126 riot +5131 ripen +5132 rise +5133 risk +5134 ritzy +5135 rival +5136 river +5141 roast +5142 robe +5143 robin +5144 rock +5145 rogue +5146 roman +5151 romp +5152 rope +5153 rover +5154 royal +5155 ruby +5156 rug +5161 ruin +5162 rule +5163 runny +5164 rush +5165 rust +5166 rut +5211 sadly +5212 sage +5213 said +5214 saint +5215 salad +5216 salon +5221 salsa +5222 salt +5223 same +5224 sandy +5225 santa +5226 satin +5231 sauna +5232 saved +5233 savor +5234 sax +5235 say +5236 scale +5241 scam +5242 scan +5243 scare +5244 scarf +5245 scary +5246 scoff +5251 scold +5252 scoop +5253 scoot +5254 scope +5255 score +5256 scorn +5261 scout +5262 scowl +5263 scrap +5264 scrub +5265 scuba +5266 scuff +5311 sect +5312 sedan +5313 self +5314 send +5315 sepia +5316 serve +5321 set +5322 seven +5323 shack +5324 shade +5325 shady +5326 shaft +5331 shaky +5332 sham +5333 shape +5334 share +5335 sharp +5336 shed +5341 sheep +5342 sheet +5343 shelf +5344 shell +5345 shine +5346 shiny +5351 ship +5352 shirt +5353 shock +5354 shop +5355 shore +5356 shout +5361 shove +5362 shown +5363 showy +5364 shred +5365 shrug +5366 shun +5411 shush +5412 shut +5413 shy +5414 sift +5415 silk +5416 silly +5421 silo +5422 sip +5423 siren +5424 sixth +5425 size +5426 skate +5431 skew +5432 skid +5433 skier +5434 skies +5435 skip +5436 skirt +5441 skit +5442 sky +5443 slab +5444 slack +5445 slain +5446 slam +5451 slang +5452 slash +5453 slate +5454 slaw +5455 sled +5456 sleek +5461 sleep +5462 sleet +5463 slept +5464 slice +5465 slick +5466 slimy +5511 sling +5512 slip +5513 slit +5514 slob +5515 slot +5516 slug +5521 slum +5522 slurp +5523 slush +5524 small +5525 smash +5526 smell +5531 smile +5532 smirk +5533 smog +5534 snack +5535 snap +5536 snare +5541 snarl +5542 sneak +5543 sneer +5544 sniff +5545 snore +5546 snort +5551 snout +5552 snowy +5553 snub +5554 snuff +5555 speak +5556 speed +5561 spend +5562 spent +5563 spew +5564 spied +5565 spill +5566 spiny +5611 spoil +5612 spoke +5613 spoof +5614 spool +5615 spoon +5616 sport +5621 spot +5622 spout +5623 spray +5624 spree +5625 spur +5626 squad +5631 squat +5632 squid +5633 stack +5634 staff +5635 stage +5636 stain +5641 stall +5642 stamp +5643 stand +5644 stank +5645 stark +5646 start +5651 stash +5652 state +5653 stays +5654 steam +5655 steep +5656 stem +5661 step +5662 stew +5663 stick +5664 sting +5665 stir +5666 stock +6111 stole +6112 stomp +6113 stony +6114 stood +6115 stool +6116 stoop +6121 stop +6122 storm +6123 stout +6124 stove +6125 straw +6126 stray +6131 strut +6132 stuck +6133 stud +6134 stuff +6135 stump +6136 stung +6141 stunt +6142 suds +6143 sugar +6144 sulk +6145 surf +6146 sushi +6151 swab +6152 swan +6153 swarm +6154 sway +6155 swear +6156 sweat +6161 sweep +6162 swell +6163 swept +6164 swim +6165 swing +6166 swipe +6211 swirl +6212 swoop +6213 swore +6214 syrup +6215 tacky +6216 taco +6221 tag +6222 take +6223 tall +6224 talon +6225 tamer +6226 tank +6231 taper +6232 taps +6233 tarot +6234 tart +6235 task +6236 taste +6241 tasty +6242 taunt +6243 thank +6244 thaw +6245 theft +6246 theme +6251 thigh +6252 thing +6253 think +6254 thong +6255 thorn +6256 those +6261 throb +6262 thud +6263 thumb +6264 thump +6265 thus +6266 tiara +6311 tidal +6312 tidy +6313 tiger +6314 tile +6315 tilt +6316 tint +6321 tiny +6322 trace +6323 track +6324 trade +6325 train +6326 trait +6331 trap +6332 trash +6333 tray +6334 treat +6335 tree +6336 trek +6341 trend +6342 trial +6343 tribe +6344 trick +6345 trio +6346 trout +6351 truce +6352 truck +6353 trump +6354 trunk +6355 try +6356 tug +6361 tulip +6362 tummy +6363 turf +6364 tusk +6365 tutor +6366 tutu +6411 tux +6412 tweak +6413 tweet +6414 twice +6415 twine +6416 twins +6421 twirl +6422 twist +6423 uncle +6424 uncut +6425 undo +6426 unify +6431 union +6432 unit +6433 untie +6434 upon +6435 upper +6436 urban +6441 used +6442 user +6443 usher +6444 utter +6445 value +6446 vapor +6451 vegan +6452 venue +6453 verse +6454 vest +6455 veto +6456 vice +6461 video +6462 view +6463 viral +6464 virus +6465 visa +6466 visor +6511 vixen +6512 vocal +6513 voice +6514 void +6515 volt +6516 voter +6521 vowel +6522 wad +6523 wafer +6524 wager +6525 wages +6526 wagon +6531 wake +6532 walk +6533 wand +6534 wasp +6535 watch +6536 water +6541 wavy +6542 wheat +6543 whiff +6544 whole +6545 whoop +6546 wick +6551 widen +6552 widow +6553 width +6554 wife +6555 wifi +6556 wilt +6561 wimp +6562 wind +6563 wing +6564 wink +6565 wipe +6566 wired +6611 wiry +6612 wise +6613 wish +6614 wispy +6615 wok +6616 wolf +6621 womb +6622 wool +6623 woozy +6624 word +6625 work +6626 worry +6631 wound +6632 woven +6633 wrath +6634 wreck +6635 wrist +6636 xerox +6641 yahoo +6642 yam +6643 yard +6644 year +6645 yeast +6646 yelp +6651 yield +6652 yo-yo +6653 yodel +6654 yoga +6655 yoyo +6656 yummy +6661 zebra +6662 zero +6663 zesty +6664 zippy +6665 zone +6666 zoom diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 0baa8ddec..8c7d0967d 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2828,12 +2828,18 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData) { apSqlServers[i] = new CSqlServer(pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), &pSelf->m_GlobalSqlLock, ReadOnly, SetUpDb); - if(SetUpDb) - thread_init(CreateTablesThread, apSqlServers[i], "CreateTables"); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); + str_format(aBuf, sizeof(aBuf), + "Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", + ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), + apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), + apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort()); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + if(SetUpDb) + { + if(!apSqlServers[i]->CreateTables()) + pSelf->SetErrorShutdown("database create tables failed"); + } return; } } @@ -2866,11 +2872,6 @@ void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData) } } -void CServer::CreateTablesThread(void *pData) -{ - ((CSqlServer *)pData)->CreateTables(); -} - #endif void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 6c8a456cb..1889434d0 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -377,8 +377,6 @@ public: // console commands for sqlmasters static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData); static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData); - - static void CreateTablesThread(void *pData); #endif static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); diff --git a/src/engine/server/sql_server.cpp b/src/engine/server/sql_server.cpp index 32304dbaf..58f7bddb8 100644 --- a/src/engine/server/sql_server.cpp +++ b/src/engine/server/sql_server.cpp @@ -167,11 +167,12 @@ void CSqlServer::Disconnect() m_SqlLock.release(); } -void CSqlServer::CreateTables() +bool CSqlServer::CreateTables() { if (!Connect()) - return; + return false; + bool Success = false; try { char aBuf[1024]; @@ -186,13 +187,14 @@ void CSqlServer::CreateTables() str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix); executeSql(aBuf); - str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix); + str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, SaveID VARCHAR(36), UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix); executeSql(aBuf); str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH); executeSql(aBuf); dbg_msg("sql", "Tables were created successfully"); + Success = true; } catch (sql::SQLException &e) { @@ -200,6 +202,7 @@ void CSqlServer::CreateTables() } Disconnect(); + return Success; } void CSqlServer::executeSql(const char *pCommand) diff --git a/src/engine/server/sql_server.h b/src/engine/server/sql_server.h index ee85d5ace..3b6a39a8e 100644 --- a/src/engine/server/sql_server.h +++ b/src/engine/server/sql_server.h @@ -17,7 +17,7 @@ public: bool Connect(); void Disconnect(); - void CreateTables(); + bool CreateTables(); void executeSql(const char *pCommand); void executeSqlQuery(const char *pQuery); @@ -30,6 +30,7 @@ public: const char* GetPass() { return m_aPass; } const char* GetIP() { return m_aIp; } int GetPort() { return m_Port; } + sql::Connection *Connection() const { return m_pConnection; } static int ms_NumReadServer; static int ms_NumWriteServer; diff --git a/src/engine/server/sql_string_helpers.h b/src/engine/server/sql_string_helpers.h index bb9ae80bb..342be3f98 100644 --- a/src/engine/server/sql_string_helpers.h +++ b/src/engine/server/sql_string_helpers.h @@ -1,6 +1,8 @@ #ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H #define ENGINE_SERVER_SQL_STRING_HELPERS_H +#include + namespace sqlstr { @@ -30,7 +32,7 @@ public: const char* Str() const { return m_aString; } const char* ClrStr() const { return m_aClearString; } - CSqlString& operator = (const char *pStr) + CSqlString& operator=(const char *pStr) { str_copy(m_aString, pStr, size); str_copy(m_aClearString, pStr, size); @@ -38,6 +40,11 @@ public: return *this; } + bool operator<(const CSqlString& other) const + { + return strcmp(m_aString, other.m_aString) < 0; + } + private: char m_aString[size]; char m_aClearString[size * 2 - 1]; diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 7be20d337..4f4035fa3 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -386,10 +386,9 @@ void CGameContext::ConTeamTop5(IConsole::IResult *pResult, void *pUserData) } if (pResult->NumArguments() > 0) - pSelf->Score()->ShowTeamTop5(pResult, pResult->m_ClientID, pUserData, - pResult->GetInteger(0)); + pSelf->Score()->ShowTeamTop5(pResult->m_ClientID, pResult->GetInteger(0)); else - pSelf->Score()->ShowTeamTop5(pResult, pResult->m_ClientID, pUserData); + pSelf->Score()->ShowTeamTop5(pResult->m_ClientID); #if defined(CONF_SQL) if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) @@ -417,10 +416,9 @@ void CGameContext::ConTop5(IConsole::IResult *pResult, void *pUserData) } if (pResult->NumArguments() > 0) - pSelf->Score()->ShowTop5(pResult, pResult->m_ClientID, pUserData, - pResult->GetInteger(0)); + pSelf->Score()->ShowTop5(pResult->m_ClientID, pResult->GetInteger(0)); else - pSelf->Score()->ShowTop5(pResult, pResult->m_ClientID, pUserData); + pSelf->Score()->ShowTop5(pResult->m_ClientID); #if defined(CONF_SQL) if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) @@ -520,13 +518,16 @@ void CGameContext::ConMap(IConsole::IResult *pResult, void *pUserData) if (!pPlayer) return; + if(pSelf->RateLimitPlayerVote(pResult->m_ClientID) || pSelf->RateLimitPlayerMapVote(pResult->m_ClientID)) + return; + #if defined(CONF_SQL) if(g_Config.m_SvUseSQL) if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick()) return; #endif - pSelf->Score()->MapVote(&pSelf->m_pMapVoteResult, pResult->m_ClientID, pResult->GetString(0)); + pSelf->Score()->MapVote(pResult->m_ClientID, pResult->GetString(0)); #if defined(CONF_SQL) if(g_Config.m_SvUseSQL) @@ -695,19 +696,20 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData) if(pPlayer->m_LastSQLQuery + g_Config.m_SvSqlQueriesDelay * pSelf->Server()->TickSpeed() >= pSelf->Server()->Tick()) return; - int Team = ((CGameControllerDDRace*) pSelf->m_pController)->m_Teams.m_Core.Team(pResult->m_ClientID); + const char* pCode = ""; + if(pResult->NumArguments() > 0) + pCode = pResult->GetString(0); - const char* pCode = pResult->GetString(0); char aCountry[5]; - if(str_length(pCode) > 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A' + if(str_length(pCode) >= 3 && pCode[0] >= 'A' && pCode[0] <= 'Z' && pCode[1] >= 'A' && pCode[1] <= 'Z' && pCode[2] >= 'A' && pCode[2] <= 'Z') { - if(pCode[3] == ' ') + if(str_length(pCode) == 3 || pCode[3] == ' ') { str_copy(aCountry, pCode, 4); pCode = str_skip_whitespaces_const(pCode + 4); } - else if(str_length(pCode) > 4 && pCode[4] == ' ') + else if(str_length(pCode) == 4 || (str_length(pCode) > 4 && pCode[4] == ' ')) { str_copy(aCountry, pCode, 5); pCode = str_skip_whitespaces_const(pCode + 5); @@ -724,7 +726,7 @@ void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData) if(str_in_list(g_Config.m_SvSqlValidServerNames, ",", aCountry)) { - pSelf->Score()->SaveTeam(Team, pCode, pResult->m_ClientID, aCountry); + pSelf->Score()->SaveTeam(pResult->m_ClientID, pCode, aCountry); if(g_Config.m_SvUseSQL) pPlayer->m_LastSQLQuery = pSelf->Server()->Tick(); @@ -790,8 +792,7 @@ void CGameContext::ConTeamRank(IConsole::IResult *pResult, void *pUserData) if (pResult->NumArguments() > 0) if (!g_Config.m_SvHideScore) - pSelf->Score()->ShowTeamRank(pResult->m_ClientID, pResult->GetString(0), - true); + pSelf->Score()->ShowTeamRank(pResult->m_ClientID, pResult->GetString(0)); else pSelf->Console()->Print( IConsole::OUTPUT_LEVEL_STANDARD, @@ -825,8 +826,7 @@ void CGameContext::ConRank(IConsole::IResult *pResult, void *pUserData) if (pResult->NumArguments() > 0) if (!g_Config.m_SvHideScore) - pSelf->Score()->ShowRank(pResult->m_ClientID, pResult->GetString(0), - true); + pSelf->Score()->ShowRank(pResult->m_ClientID, pResult->GetString(0)); else pSelf->Console()->Print( IConsole::OUTPUT_LEVEL_STANDARD, @@ -1548,8 +1548,7 @@ void CGameContext::ConPoints(IConsole::IResult *pResult, void *pUserData) if (pResult->NumArguments() > 0) if (!g_Config.m_SvHideScore) - pSelf->Score()->ShowPoints(pResult->m_ClientID, pResult->GetString(0), - true); + pSelf->Score()->ShowPoints(pResult->m_ClientID, pResult->GetString(0)); else pSelf->Console()->Print( IConsole::OUTPUT_LEVEL_STANDARD, @@ -1583,10 +1582,9 @@ void CGameContext::ConTopPoints(IConsole::IResult *pResult, void *pUserData) } if (pResult->NumArguments() > 0) - pSelf->Score()->ShowTopPoints(pResult, pResult->m_ClientID, pUserData, - pResult->GetInteger(0)); + pSelf->Score()->ShowTopPoints(pResult->m_ClientID, pResult->GetInteger(0)); else - pSelf->Score()->ShowTopPoints(pResult, pResult->m_ClientID, pUserData); + pSelf->Score()->ShowTopPoints(pResult->m_ClientID); if(pSelf->m_apPlayers[pResult->m_ClientID] && g_Config.m_SvUseSQL) pSelf->m_apPlayers[pResult->m_ClientID]->m_LastSQLQuery = pSelf->Server()->Tick(); diff --git a/src/game/server/ddracechat.h b/src/game/server/ddracechat.h index e0d3ec894..48787a00c 100644 --- a/src/game/server/ddracechat.h +++ b/src/game/server/ddracechat.h @@ -27,7 +27,7 @@ CHAT_COMMAND("dnd", "", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConD CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)") CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s") CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank") -CHAT_COMMAND("save", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.") +CHAT_COMMAND("save", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.") CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves") CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name") CHAT_COMMAND("rankteam", "?r[player name]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)") diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 8e7ed6273..a523bb562 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -1503,6 +1503,26 @@ void CCharacter::HandleTiles(int Index) // start if(((m_TileIndex == TILE_BEGIN) || (m_TileFIndex == TILE_BEGIN) || FTile1 == TILE_BEGIN || FTile2 == TILE_BEGIN || FTile3 == TILE_BEGIN || FTile4 == TILE_BEGIN || Tile1 == TILE_BEGIN || Tile2 == TILE_BEGIN || Tile3 == TILE_BEGIN || Tile4 == TILE_BEGIN) && (m_DDRaceState == DDRACE_NONE || m_DDRaceState == DDRACE_FINISHED || (m_DDRaceState == DDRACE_STARTED && !Team() && g_Config.m_SvTeam != 3))) { + if(Teams()->GetSaving(Team())) + { + if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) + { + GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't start while loading/saving of team is in progress"); + m_LastStartWarning = Server()->Tick(); + } + Die(GetPlayer()->GetCID(), WEAPON_WORLD); + return; + } + if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1)) + { + if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) + { + GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You have to be in a team with other tees to start"); + m_LastStartWarning = Server()->Tick(); + } + Die(GetPlayer()->GetCID(), WEAPON_WORLD); + return; + } if(g_Config.m_SvResetPickups) { for (int i = WEAPON_SHOTGUN; i < NUM_WEAPONS; ++i) @@ -1512,16 +1532,6 @@ void CCharacter::HandleTiles(int Index) m_Core.m_ActiveWeapon = WEAPON_GUN; } } - if(g_Config.m_SvTeam == 2 && (Team() == TEAM_FLOCK || Teams()->Count(Team()) <= 1)) - { - if(m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed()) - { - GameServer()->SendChatTarget(GetPlayer()->GetCID(),"You have to be in a team with other tees to start"); - m_LastStartWarning = Server()->Tick(); - } - Die(GetPlayer()->GetCID(), WEAPON_WORLD); - return; - } Teams()->OnCharacterStart(m_pPlayer->GetCID()); m_CpActive = -2; diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index f78447fee..56369415c 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -57,9 +57,6 @@ void CGameContext::Construct(int Resetting) m_ChatResponseTargetID = -1; m_aDeleteTempfile[0] = 0; m_TeeHistorianActive = false; - - m_pRandomMapResult = nullptr; - m_pMapVoteResult = nullptr; } CGameContext::CGameContext(int Resetting) @@ -901,6 +898,7 @@ void CGameContext::OnTick() } if(Collision()->m_NumSwitchers > 0) + { for (int i = 0; i < Collision()->m_NumSwitchers+1; ++i) { for (int j = 0; j < MAX_CLIENTS; ++j) @@ -919,28 +917,6 @@ void CGameContext::OnTick() } } } - - if(m_pRandomMapResult && m_pRandomMapResult->m_Done) - { - str_copy(g_Config.m_SvMap, m_pRandomMapResult->m_aMap, sizeof(g_Config.m_SvMap)); - m_pRandomMapResult = NULL; - } - - if(m_pMapVoteResult && m_pMapVoteResult->m_Done) - { - m_VoteKick = false; - m_VoteSpec = false; - m_LastMapVote = time_get(); - - char aCmd[256]; - str_format(aCmd, sizeof(aCmd), "sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"", m_pMapVoteResult->m_aServer, m_pMapVoteResult->m_aMap); - - char aChatmsg[512]; - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", Server()->ClientName(m_pMapVoteResult->m_ClientID), m_pMapVoteResult->m_aMap, "/map"); - - CallVote(m_pMapVoteResult->m_ClientID, m_pMapVoteResult->m_aMap, aCmd, "/map", aChatmsg); - - m_pMapVoteResult = NULL; } #ifdef CONF_DEBUG @@ -1076,8 +1052,7 @@ void CGameContext::OnClientEnter(int ClientID) // Can't set score here as LoadScore() is threaded, run it in // LoadScoreThreaded() instead - Score()->LoadScore(ClientID); - Score()->CheckBirthday(ClientID); + Score()->LoadPlayerData(ClientID); { int Empty = -1; @@ -1424,68 +1399,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } else if(MsgID == NETMSGTYPE_CL_CALLVOTE) { - int64 Now = Server()->Tick(); - int64 TickSpeed = Server()->TickSpeed(); - - if(g_Config.m_SvRconVote && !Server()->GetAuthedState(ClientID)) - { - SendChatTarget(ClientID, "You can only vote after logging in."); + if(RateLimitPlayerVote(ClientID)) return; - } - - if (g_Config.m_SvDnsblVote && !m_pServer->DnsblWhite(ClientID) && Server()->DistinctClientCount() > 1) - { - // blacklisted by dnsbl - SendChatTarget(ClientID, "You are not allowed to vote due to DNSBL."); - return; - } - - if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + TickSpeed * 3 > Now) - return; - - pPlayer->m_LastVoteTry = Now; - if(g_Config.m_SvSpectatorVotes == 0 && pPlayer->GetTeam() == TEAM_SPECTATORS) - { - SendChatTarget(ClientID, "Spectators aren't allowed to start a vote."); - return; - } - - if(m_VoteCloseTime) - { - SendChatTarget(ClientID, "Wait for current vote to end before calling a new one."); - return; - } - - if(Now < pPlayer->m_FirstVoteTick) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before making your first vote.", (int)((pPlayer->m_FirstVoteTick - Now) / TickSpeed) + 1); - SendChatTarget(ClientID, aBuf); - return; - } - - int TimeLeft = pPlayer->m_LastVoteCall + TickSpeed * g_Config.m_SvVoteDelay - Now; - if(pPlayer->m_LastVoteCall && TimeLeft > 0) - { - char aChatmsg[64]; - str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote.", (int)(TimeLeft / TickSpeed) + 1); - SendChatTarget(ClientID, aChatmsg); - return; - } - - NETADDR Addr; - Server()->GetClientAddr(ClientID, &Addr); - int VoteMuted = 0; - for(int i = 0; i < m_NumVoteMutes && !VoteMuted; i++) - if(!net_addr_comp_noport(&Addr, &m_aVoteMutes[i].m_Addr)) - VoteMuted = (m_aVoteMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); - if(VoteMuted > 0) - { - char aChatmsg[64]; - str_format(aChatmsg, sizeof(aChatmsg), "You are not permitted to vote for the next %d seconds.", VoteMuted); - SendChatTarget(ClientID, aChatmsg); - return; - } char aChatmsg[512] = {0}; char aDesc[VOTE_DESC_LENGTH] = {0}; @@ -1516,11 +1431,12 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) SendChatTarget(ClientID, "Invalid option"); return; } - if(!Authed && (str_startswith(pOption->m_aCommand, "sv_map ") || str_startswith(pOption->m_aCommand, "change_map ") || str_startswith(pOption->m_aCommand, "random_map") || str_startswith(pOption->m_aCommand, "random_unfinished_map")) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) + if((str_startswith(pOption->m_aCommand, "sv_map ") + || str_startswith(pOption->m_aCommand, "change_map ") + || str_startswith(pOption->m_aCommand, "random_map") + || str_startswith(pOption->m_aCommand, "random_unfinished_map")) + && RateLimitPlayerMapVote(ClientID)) { - str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second delay between map-votes, please wait %d seconds.", g_Config.m_SvVoteMapTimeDelay, (int)(((m_LastMapVote+(g_Config.m_SvVoteMapTimeDelay * time_freq()))/time_freq())-(time_get()/time_freq()))); - SendChatTarget(ClientID, aChatmsg); - return; } @@ -1877,18 +1793,8 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) // reload scores Score()->PlayerData(ClientID)->Reset(); - Score()->LoadScore(ClientID); - Score()->PlayerData(ClientID)->m_CurrentTime = Score()->PlayerData(ClientID)->m_BestTime; - - // -9999 stands for no time and isn't displayed in scoreboard, so - // shift the time by a second if the player actually took 9999 - // seconds to finish the map. - if(!Score()->PlayerData(ClientID)->m_BestTime) - m_apPlayers[ClientID]->m_Score = -9999; - else if((int)Score()->PlayerData(ClientID)->m_BestTime == -9999) - m_apPlayers[ClientID]->m_Score = -10000; - else - m_apPlayers[ClientID]->m_Score = Score()->PlayerData(ClientID)->m_BestTime; + m_apPlayers[ClientID]->m_Score = -9999; + Score()->LoadPlayerData(ClientID); } Server()->SetClientClan(ClientID, pMsg->m_pClan); Server()->SetClientCountry(ClientID, pMsg->m_Country); @@ -2241,7 +2147,7 @@ void CGameContext::ConRandomMap(IConsole::IResult *pResult, void *pUserData) int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1; - pSelf->m_pScore->RandomMap(&pSelf->m_pRandomMapResult, pSelf->m_VoteCreator, Stars); + pSelf->m_pScore->RandomMap(pSelf->m_VoteCreator, Stars); } void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUserData) @@ -2250,7 +2156,7 @@ void CGameContext::ConRandomUnfinishedMap(IConsole::IResult *pResult, void *pUse int Stars = pResult->NumArguments() ? pResult->GetInteger(0) : -1; - pSelf->m_pScore->RandomUnfinishedMap(&pSelf->m_pRandomMapResult, pSelf->m_VoteCreator, Stars); + pSelf->m_pScore->RandomUnfinishedMap(pSelf->m_VoteCreator, Stars); } void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData) @@ -3591,3 +3497,84 @@ void CGameContext::ForceVote(int EnforcerID, bool Success) str_format(aBuf, sizeof(aBuf), "forcing vote %s", pOption); Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } + +bool CGameContext::RateLimitPlayerVote(int ClientID) +{ + int64 Now = Server()->Tick(); + int64 TickSpeed = Server()->TickSpeed(); + CPlayer *pPlayer = m_apPlayers[ClientID]; + + if(g_Config.m_SvRconVote && !Server()->GetAuthedState(ClientID)) + { + SendChatTarget(ClientID, "You can only vote after logging in."); + return true; + } + + if (g_Config.m_SvDnsblVote && !m_pServer->DnsblWhite(ClientID) && Server()->DistinctClientCount() > 1) + { + // blacklisted by dnsbl + SendChatTarget(ClientID, "You are not allowed to vote due to DNSBL."); + return true; + } + + if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + TickSpeed * 3 > Now) + return true; + + pPlayer->m_LastVoteTry = Now; + if(g_Config.m_SvSpectatorVotes == 0 && pPlayer->GetTeam() == TEAM_SPECTATORS) + { + SendChatTarget(ClientID, "Spectators aren't allowed to start a vote."); + return true; + } + + if(m_VoteCloseTime) + { + SendChatTarget(ClientID, "Wait for current vote to end before calling a new one."); + return true; + } + + if(Now < pPlayer->m_FirstVoteTick) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before making your first vote.", (int)((pPlayer->m_FirstVoteTick - Now) / TickSpeed) + 1); + SendChatTarget(ClientID, aBuf); + return true; + } + + int TimeLeft = pPlayer->m_LastVoteCall + TickSpeed * g_Config.m_SvVoteDelay - Now; + if(pPlayer->m_LastVoteCall && TimeLeft > 0) + { + char aChatmsg[64]; + str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote.", (int)(TimeLeft / TickSpeed) + 1); + SendChatTarget(ClientID, aChatmsg); + return true; + } + + NETADDR Addr; + Server()->GetClientAddr(ClientID, &Addr); + int VoteMuted = 0; + for(int i = 0; i < m_NumVoteMutes && !VoteMuted; i++) + if(!net_addr_comp_noport(&Addr, &m_aVoteMutes[i].m_Addr)) + VoteMuted = (m_aVoteMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); + if(VoteMuted > 0) + { + char aChatmsg[64]; + str_format(aChatmsg, sizeof(aChatmsg), "You are not permitted to vote for the next %d seconds.", VoteMuted); + SendChatTarget(ClientID, aChatmsg); + return true; + } + return false; +} + +bool CGameContext::RateLimitPlayerMapVote(int ClientID) +{ + if(!Server()->GetAuthedState(ClientID) && time_get() < m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) + { + char aChatmsg[512] = {0}; + str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second delay between map-votes, please wait %d seconds.", + g_Config.m_SvVoteMapTimeDelay, (int)((m_LastMapVote + g_Config.m_SvVoteMapTimeDelay * time_freq() - time_get())/time_freq())); + SendChatTarget(ClientID, aChatmsg); + return true; + } + return false; +} diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 061344005..6efb400de 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -57,8 +57,6 @@ enum class IConsole; class IEngine; class IStorage; -class CRandomMapResult; -class CMapVoteResult; struct CAntibotData; class CGameContext : public IGameServer @@ -81,9 +79,6 @@ class CGameContext : public IGameServer CMapBugs m_MapBugs; CPrng m_Prng; - std::shared_ptr m_pRandomMapResult; - std::shared_ptr m_pMapVoteResult; - static void CommandCallback(int ClientID, int FlagMask, const char *pCmd, IConsole::IResult *pResult, void *pUser); static void TeeHistorianWrite(const void *pData, int DataSize, void *pUser); @@ -264,6 +259,10 @@ public: bool PlayerModerating(); void ForceVote(int EnforcerID, bool Success); + // Checks if player can vote and notify them about the reason + bool RateLimitPlayerVote(int ClientID); + bool RateLimitPlayerMapVote(int ClientID); + private: bool m_VoteWillPass; diff --git a/src/game/server/gamemodes/DDRace.cpp b/src/game/server/gamemodes/DDRace.cpp index f0476e752..dd9c76c99 100644 --- a/src/game/server/gamemodes/DDRace.cpp +++ b/src/game/server/gamemodes/DDRace.cpp @@ -9,8 +9,12 @@ #include "DDRace.h" #include "gamemode.h" +#if defined(CONF_SQL) +#include +#endif + CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) : - IGameController(pGameServer), m_Teams(pGameServer) + IGameController(pGameServer), m_Teams(pGameServer), m_pInitResult(nullptr) { m_pGameType = g_Config.m_SvTestingCommands ? TEST_NAME : GAME_NAME; @@ -25,6 +29,18 @@ CGameControllerDDRace::~CGameControllerDDRace() void CGameControllerDDRace::Tick() { IGameController::Tick(); +#if defined(CONF_SQL) + m_Teams.ProcessSaveTeam(); + + if(m_pInitResult != nullptr && m_pInitResult.use_count() == 1) + { + if(m_pInitResult->m_Done) + { + m_CurrentRecord = m_pInitResult->m_CurrentRecord; + } + m_pInitResult = nullptr; + } +#endif } void CGameControllerDDRace::InitTeleporter() diff --git a/src/game/server/gamemodes/DDRace.h b/src/game/server/gamemodes/DDRace.h index 20be25cb5..0db166138 100644 --- a/src/game/server/gamemodes/DDRace.h +++ b/src/game/server/gamemodes/DDRace.h @@ -8,6 +8,7 @@ #include #include +struct CSqlInitResult; class CGameControllerDDRace: public IGameController { public: @@ -22,5 +23,7 @@ public: void InitTeleporter(); virtual void Tick(); + + std::shared_ptr m_pInitResult; }; #endif // GAME_SERVER_GAMEMODES_DDRACE_H diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 47fa83646..7249f6a9d 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -12,6 +12,10 @@ #include "gamemodes/DDRace.h" #include +#if defined(CONF_SQL) +#include "score/sql_score.h" +#endif + MACRO_ALLOC_POOL_ID_IMPL(CPlayer, MAX_CLIENTS) IServer *CPlayer::Server() const { return m_pGameServer->Server(); } @@ -41,7 +45,6 @@ void CPlayer::Reset() m_JoinTick = Server()->Tick(); delete m_pCharacter; m_pCharacter = 0; - m_KillMe = 0; m_SpectatorID = SPEC_FREEVIEW; m_LastActionTick = Server()->Tick(); m_TeamChangeTick = Server()->Tick(); @@ -119,6 +122,8 @@ void CPlayer::Reset() m_Last_Team = 0; #if defined(CONF_SQL) m_LastSQLQuery = 0; + m_SqlQueryResult = nullptr; + m_SqlFinishResult = nullptr; #endif int64 Now = Server()->Tick(); @@ -127,7 +132,7 @@ void CPlayer::Reset() // non-empty, allow them to vote immediately. This allows players to // vote after map changes or when they join an empty server. // - // Otherwise, block voting in the begnning after joining. + // Otherwise, block voting in the beginning after joining. if(Now > GameServer()->m_NonEmptySince + 10 * TickSpeed) m_FirstVoteTick = Now + g_Config.m_SvJoinVoteDelay * TickSpeed; else @@ -143,16 +148,35 @@ void CPlayer::Tick() #ifdef CONF_DEBUG if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies) #endif +#if defined(CONF_SQL) + if(m_SqlQueryResult != nullptr && m_SqlQueryResult.use_count() == 1) + { + ProcessSqlResult(*m_SqlQueryResult); + m_SqlQueryResult = nullptr; + } + if(m_SqlFinishResult != nullptr && m_SqlFinishResult.use_count() == 1) + { + ProcessSqlResult(*m_SqlFinishResult); + m_SqlFinishResult = nullptr; + } + if(m_SqlRandomMapResult!= nullptr && m_SqlRandomMapResult.use_count() == 1) + { + if(m_SqlRandomMapResult->m_Done) + { + if(m_SqlRandomMapResult->m_aMessage[0] != '\0') + GameServer()->SendChatTarget(m_ClientID, m_SqlRandomMapResult->m_aMessage); + if(m_SqlRandomMapResult->m_Map[0] != '\0') + str_copy(g_Config.m_SvMap, m_SqlRandomMapResult->m_Map, sizeof(g_Config.m_SvMap)); + else + GameServer()->m_LastMapVote = 0; + } + m_SqlRandomMapResult = nullptr; + } +#endif + if(!Server()->ClientIngame(m_ClientID)) return; - if(m_KillMe != 0) - { - KillCharacter(m_KillMe); - m_KillMe = 0; - return; - } - if (m_ChatScore > 0) m_ChatScore--; @@ -233,7 +257,7 @@ void CPlayer::Tick() int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_ViewPos); m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex); - if (m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time + if (m_TuneZone != m_TuneZoneOld) // don't send tunings all the time { GameServer()->SendTuningParams(m_ClientID, m_TuneZone); } @@ -258,11 +282,11 @@ void CPlayer::PostTick() void CPlayer::PostPostTick() { - #ifdef CONF_DEBUG - if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies) - #endif - if(!Server()->ClientIngame(m_ClientID)) - return; +#ifdef CONF_DEBUG + if(!g_Config.m_DbgDummies || m_ClientID < MAX_CLIENTS-g_Config.m_DbgDummies) +#endif + if(!Server()->ClientIngame(m_ClientID)) + return; if(!GameServer()->m_World.m_Paused && !m_pCharacter && m_Spawning && m_WeakHookSpawn) TryRespawn(); @@ -491,11 +515,6 @@ CCharacter *CPlayer::GetCharacter() return 0; } -void CPlayer::ThreadKillCharacter(int Weapon) -{ - m_KillMe = Weapon; -} - void CPlayer::KillCharacter(int Weapon) { if(m_pCharacter) @@ -782,3 +801,79 @@ void CPlayer::SpectatePlayerName(const char *pName) } } } + +#if defined(CONF_SQL) +void CPlayer::ProcessSqlResult(CSqlPlayerResult &Result) +{ + if(Result.m_Done) // SQL request was successful + { + int NumMessages = (int)(sizeof(Result.m_aaMessages)/sizeof(Result.m_aaMessages[0])); + switch(Result.m_MessageKind) + { + case CSqlPlayerResult::DIRECT: + for(int i = 0; i < NumMessages; i++) + { + if(Result.m_aaMessages[i][0] == 0) + break; + GameServer()->SendChatTarget(m_ClientID, Result.m_aaMessages[i]); + } + break; + case CSqlPlayerResult::ALL: + for(int i = 0; i < NumMessages; i++) + { + if(Result.m_aaMessages[i][0] == 0) + break; + GameServer()->SendChat(-1, CGameContext::CHAT_ALL, Result.m_aaMessages[i]); + } + break; + case CSqlPlayerResult::BROADCAST: + if(Result.m_Data.m_Broadcast[0] != 0) + GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1); + break; + case CSqlPlayerResult::MAP_VOTE: + GameServer()->m_VoteKick = false; + GameServer()->m_VoteSpec = false; + GameServer()->m_LastMapVote = time_get(); + + char aCmd[256]; + str_format(aCmd, sizeof(aCmd), + "sv_reset_file types/%s/flexreset.cfg; change_map \"%s\"", + Result.m_Data.m_MapVote.m_Server, Result.m_Data.m_MapVote.m_Map); + + char aChatmsg[512]; + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", + Server()->ClientName(m_ClientID), Result.m_Data.m_MapVote.m_Map, "/map"); + + GameServer()->CallVote(m_ClientID, Result.m_Data.m_MapVote.m_Map, aCmd, "/map", aChatmsg); + break; + case CSqlPlayerResult::PLAYER_INFO: + GameServer()->Score()->PlayerData(m_ClientID)->Set( + Result.m_Data.m_Info.m_Time, + Result.m_Data.m_Info.m_CpTime + ); + m_Score = Result.m_Data.m_Info.m_Score; + m_HasFinishScore = Result.m_Data.m_Info.m_HasFinishScore; + // -9999 stands for no time and isn't displayed in scoreboard, so + // shift the time by a second if the player actually took 9999 + // seconds to finish the map. + if(m_HasFinishScore && m_Score == -9999) + m_Score = -10000; + Server()->ExpireServerInfo(); + int Birthday = Result.m_Data.m_Info.m_Birthday; + if(Birthday != 0) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), + "Happy DDNet birthday to %s for finishing their first map %d year%s ago!", + Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : ""); + GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, m_ClientID); + str_format(aBuf, sizeof(aBuf), + "Happy DDNet birthday, %s!\nYou have finished your first map exactly %d year%s ago!", + Server()->ClientName(m_ClientID), Birthday, Birthday > 1 ? "s" : ""); + GameServer()->SendBroadcast(aBuf, m_ClientID); + } + break; + } + } +} +#endif diff --git a/src/game/server/player.h b/src/game/server/player.h index ada68a22a..e2aeac559 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -5,7 +5,14 @@ // this include should perhaps be removed #include "entities/character.h" +#include "score.h" #include "gamecontext.h" +#include + +#if defined(CONF_SQL) +class CSqlPlayerResult; +class CSqlRandomMapResult; +#endif // player object class CPlayer @@ -41,7 +48,6 @@ public: void OnPredictedEarlyInput(CNetObj_PlayerInput *NewInput); void OnDisconnect(const char *pReason); - void ThreadKillCharacter(int Weapon = WEAPON_GAME); void KillCharacter(int Weapon = WEAPON_GAME); CCharacter *GetCharacter(); @@ -168,7 +174,6 @@ public: bool m_SpecTeam; bool m_NinjaJetpack; bool m_Afk; - int m_KillMe; bool m_HasFinishScore; int m_ChatScore; @@ -194,7 +199,11 @@ public: bool m_Halloween; bool m_FirstPacket; #if defined(CONF_SQL) + void ProcessSqlResult(CSqlPlayerResult &Result); int64 m_LastSQLQuery; + std::shared_ptr m_SqlQueryResult; + std::shared_ptr m_SqlFinishResult; + std::shared_ptr m_SqlRandomMapResult; #endif bool m_NotEligibleForFinish; int64 m_EligibleForFinishCheck; diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index fec42a0c6..2953bfdb3 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -1,9 +1,10 @@ +#include "save.h" + #include #include -#include "save.h" #include "teams.h" -#include "./gamemodes/DDRace.h" +#include "gamemodes/DDRace.h" #include CSaveTee::CSaveTee() @@ -16,13 +17,14 @@ CSaveTee::~CSaveTee() void CSaveTee::save(CCharacter *pChr) { - str_copy(m_aName, pChr->m_pPlayer->Server()->ClientName(pChr->m_pPlayer->GetCID()), sizeof(m_aName)); + m_ClientID = pChr->m_pPlayer->GetCID(); + str_copy(m_aName, pChr->m_pPlayer->Server()->ClientName(m_ClientID), sizeof(m_aName)); m_Alive = pChr->m_Alive; m_Paused = abs(pChr->m_pPlayer->IsPaused()); m_NeededFaketuning = pChr->m_NeededFaketuning; - m_TeeFinished = pChr->Teams()->TeeFinished(pChr->m_pPlayer->GetCID()); + m_TeeFinished = pChr->Teams()->TeeFinished(m_ClientID); m_IsSolo = pChr->m_Solo; for(int i = 0; i< NUM_WEAPONS; i++) @@ -51,7 +53,9 @@ void CSaveTee::save(CCharacter *pChr) m_TuneZoneOld = pChr->m_TuneZoneOld; if(pChr->m_StartTime) - m_Time = pChr->Server()->Tick() - pChr->m_StartTime + 60 * pChr->Server()->TickSpeed(); + m_Time = pChr->Server()->Tick() - pChr->m_StartTime; + else + m_Time = 0; m_Pos = pChr->m_Pos; m_PrevPos = pChr->m_PrevPos; @@ -182,6 +186,9 @@ void CSaveTee::load(CCharacter *pChr, int Team) char* CSaveTee::GetString() { + // Add time penalty of 60 seconds (only to the database) + int Time = m_Time + 60 * SERVER_TICK_SPEED; + str_format(m_aString, sizeof(m_aString), "%s\t%d\t%d\t%d\t%d\t%d\t" // weapons @@ -222,7 +229,7 @@ char* CSaveTee::GetString() m_LastWeapon, m_QueuedWeapon, // tee states m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook, - m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, m_Time, + m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, Time, (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y, m_TeleCheckpoint, m_LastPenalty, (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y, @@ -243,7 +250,7 @@ char* CSaveTee::GetString() return m_aString; } -int CSaveTee::LoadString(char* String) +int CSaveTee::LoadString(const char* String) { int Num; Num = sscanf(String, @@ -398,6 +405,8 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientID, CGameContext *pGameCon { switch(Result) { + case 0: + return false; case 1: pGameContext->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); break; @@ -408,44 +417,19 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientID, CGameContext *pGameCon pGameContext->SendChatTarget(ClientID, "Unable to find all Characters"); break; case 4: - pGameContext->SendChatTarget(ClientID, "Your team is not started yet"); + pGameContext->SendChatTarget(ClientID, "Your team has not started yet"); + break; + default: // this state should never be reached + pGameContext->SendChatTarget(ClientID, "Unknown error while saving"); break; - default: - return false; } - return true; } -int CSaveTeam::load(int Team) +void CSaveTeam::load(int Team) { - if(Team <= 0 || Team >= MAX_CLIENTS) - return 1; - CGameTeams* pTeams = &(((CGameControllerDDRace*)m_pController)->m_Teams); - if(pTeams->Count(Team) > m_MembersCount) - return 2; - - CCharacter *pChr; - - for (int i = 0; i < m_MembersCount; i++) - { - int ID = MatchPlayer(m_pSavedTees[i].GetName()); - if(ID == -1) // first check if team can be loaded / do not load half teams - { - return i+10; // +10 to leave space for other return-values - } - if(m_pController->GameServer()->m_apPlayers[ID] && m_pController->GameServer()->m_apPlayers[ID]->GetCharacter() && m_pController->GameServer()->m_apPlayers[ID]->GetCharacter()->m_DDRaceState) - { - return i+100; // +100 to leave space for other return-values - } - if(Team != pTeams->m_Core.Team(ID)) - { - return i+200; // +100 to leave space for other return-values - } - } - pTeams->ChangeTeamState(Team, m_TeamState); pTeams->SetTeamLock(Team, m_TeamLocked); if(m_Practice) @@ -453,14 +437,16 @@ int CSaveTeam::load(int Team) for (int i = 0; i < m_MembersCount; i++) { - pChr = MatchCharacter(m_pSavedTees[i].GetName(), i); - if(pChr) + int ClientID = m_pSavedTees[i].GetClientID(); + if(m_pController->GameServer()->m_apPlayers[ClientID] && pTeams->m_Core.Team(ClientID) == Team) { + CCharacter *pChr = MatchCharacter(m_pSavedTees[i].GetClientID(), i); m_pSavedTees[i].load(pChr, Team); } } if(m_pController->GameServer()->Collision()->m_NumSwitchers) + { for(int i=1; i < m_pController->GameServer()->Collision()->m_NumSwitchers+1; i++) { m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Status[Team] = m_pSwitchers[i].m_Status; @@ -468,33 +454,15 @@ int CSaveTeam::load(int Team) m_pController->GameServer()->Collision()->m_pSwitchers[i].m_EndTick[Team] = m_pController->Server()->Tick() - m_pSwitchers[i].m_EndTime; m_pController->GameServer()->Collision()->m_pSwitchers[i].m_Type[Team] = m_pSwitchers[i].m_Type; } - return 0; + } } -int CSaveTeam::MatchPlayer(char name[16]) +CCharacter* CSaveTeam::MatchCharacter(int ClientID, int SaveID) { - for (int i = 0; i < MAX_CLIENTS; i++) - { - if(str_comp(m_pController->Server()->ClientName(i), name) == 0) - { - return i; - } - } - return -1; -} - -CCharacter* CSaveTeam::MatchCharacter(char name[16], int SaveID) -{ - int ID = MatchPlayer(name); - if(ID >= 0 && m_pController->GameServer()->m_apPlayers[ID]) - { - if(m_pController->GameServer()->m_apPlayers[ID]->GetCharacter()) - return m_pController->GameServer()->m_apPlayers[ID]->GetCharacter(); - else - return m_pController->GameServer()->m_apPlayers[ID]->ForceSpawn(m_pSavedTees[SaveID].GetPos()); - } - - return 0; + if(m_pController->GameServer()->m_apPlayers[ClientID]->GetCharacter()) + return m_pController->GameServer()->m_apPlayers[ClientID]->GetCharacter(); + else + return m_pController->GameServer()->m_apPlayers[ClientID]->ForceSpawn(m_pSavedTees[SaveID].GetPos()); } char* CSaveTeam::GetString() @@ -667,3 +635,48 @@ int CSaveTeam::LoadString(const char* String) return 0; } + +bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen) +{ + if(NumPlayer > m_MembersCount) + { + str_format(pMessage, MessageLen, "Too many players in this team, should be %d", m_MembersCount); + return false; + } + // check for wrong players + for(int i = 0; i < NumPlayer; i++) + { + int Found = false; + for(int j = 0; j < m_MembersCount; j++) + { + if(strcmp(paNames[i], m_pSavedTees[j].GetName()) == 0) + { + Found = true; + } + } + if(!Found) + { + str_format(pMessage, MessageLen, "'%s' don't belong to this team", paNames[i]); + return false; + } + } + // check for missing players + for(int i = 0; i < m_MembersCount; i++) + { + int Found = false; + for(int j = 0; j < NumPlayer; j++) + { + if(strcmp(m_pSavedTees[i].GetName(), paNames[j]) == 0) + { + m_pSavedTees[i].SetClientID(pClientID[j]); + Found = true; + } + } + if(!Found) + { + str_format(pMessage, MessageLen, "'%s' has to be in this team", m_pSavedTees[i].GetName()); + return false; + } + } + return true; +} diff --git a/src/game/server/save.h b/src/game/server/save.h index 293b4d2a1..6ea6c0305 100644 --- a/src/game/server/save.h +++ b/src/game/server/save.h @@ -1,8 +1,9 @@ #ifndef GAME_SERVER_SAVE_H #define GAME_SERVER_SAVE_H -#include "./entities/character.h" -#include +#include "entities/character.h" +class IGameController; +class CGameContext; class CSaveTee { @@ -12,11 +13,14 @@ public: void save(CCharacter* pchr); void load(CCharacter* pchr, int Team); char* GetString(); - int LoadString(char* String); - vec2 GetPos() { return m_Pos; } - char* GetName() { return m_aName; } + int LoadString(const char* String); + vec2 GetPos() const { return m_Pos; } + const char* GetName() const { return m_aName; } + int GetClientID() const { return m_ClientID; } + void SetClientID(int ClientID) { m_ClientID = ClientID; }; private: + int m_ClientID; char m_aString [2048]; char m_aName [16]; @@ -94,16 +98,19 @@ public: CSaveTeam(IGameController* Controller); ~CSaveTeam(); char* GetString(); - int GetMembersCount() { return m_MembersCount; } + int GetMembersCount() const { return m_MembersCount; } + // MatchPlayers has to be called afterwards int LoadString(const char* String); + // returns true if a team can load, otherwise writes a nice error Message in pMessage + bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen); int save(int Team); - int load(int Team); + void load(int Team); CSaveTee* m_pSavedTees; + // returns true if an error occured static bool HandleSaveError(int Result, int ClientID, CGameContext *pGameContext); private: - int MatchPlayer(char name[16]); - CCharacter* MatchCharacter(char name[16], int SaveID); + CCharacter* MatchCharacter(int ClientID, int SaveID); IGameController* m_pController; diff --git a/src/game/server/score.h b/src/game/server/score.h index 8943ffb17..63598fe73 100644 --- a/src/game/server/score.h +++ b/src/game/server/score.h @@ -2,9 +2,9 @@ #define GAME_SERVER_SCORE_H #include +#include -#include "entities/character.h" -#include "gamecontext.h" +#include "save.h" enum { @@ -19,6 +19,7 @@ public: { Reset(); } + ~CPlayerData() {} void Reset() { @@ -31,6 +32,7 @@ public: void Set(float Time, float CpTime[NUM_CHECKPOINTS]) { m_BestTime = Time; + m_CurrentTime = Time; for(int i = 0; i < NUM_CHECKPOINTS; i++) m_aBestCpTime[i] = CpTime[i]; } @@ -40,28 +42,6 @@ public: float m_aBestCpTime[NUM_CHECKPOINTS]; }; -// Watch this: TODO(2019-05-20): Temporary fix for the random maps race -// condition. See you in ten years. -class CRandomMapResult -{ -public: - bool m_Done; - char m_aMap[64]; - - CRandomMapResult() : m_Done(false) {} -}; - -class CMapVoteResult -{ -public: - bool m_Done; - char m_aMap[64]; - char m_aServer[32]; - int m_ClientID; - - CMapVoteResult() : m_Done(false) {} -}; - class IScore { CPlayerData m_aPlayerData[MAX_CLIENTS]; @@ -72,26 +52,25 @@ public: CPlayerData *PlayerData(int ID) { return &m_aPlayerData[ID]; } virtual void MapInfo(int ClientID, const char *pMapName) = 0; - virtual void MapVote(std::shared_ptr *ppResult, int ClientID, const char *pMapName) = 0; - virtual void CheckBirthday(int ClientID) = 0; - virtual void LoadScore(int ClientID) = 0; + virtual void MapVote(int ClientID, const char *pMapName) = 0; + virtual void LoadPlayerData(int ClientID) = 0; virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0; virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0; - virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; - virtual void ShowRank(int ClientID, const char *pName, bool Search=false) = 0; + virtual void ShowTop5(int ClientID, int Offset=1) = 0; + virtual void ShowRank(int ClientID, const char *pName) = 0; - virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; - virtual void ShowTeamRank(int ClientID, const char *pName, bool Search=false) = 0; + virtual void ShowTeamTop5(int ClientID, int Offset=1) = 0; + virtual void ShowTeamRank(int ClientID, const char *pName) = 0; - virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0; - virtual void ShowPoints(int ClientID, const char *pName, bool Search=false) = 0; + virtual void ShowTopPoints(int ClientID, int Offset=1) = 0; + virtual void ShowPoints(int ClientID, const char *pName) = 0; - virtual void RandomMap(std::shared_ptr *ppResult, int ClientID, int Stars) = 0; - virtual void RandomUnfinishedMap(std::shared_ptr *ppResult, int ClientID, int Stars) = 0; + virtual void RandomMap(int ClientID, int Stars) = 0; + virtual void RandomUnfinishedMap(int ClientID, int Stars) = 0; - virtual void SaveTeam(int Team, const char *pCode, int ClientID, const char *pServer) = 0; + virtual void SaveTeam(int ClientID, const char *pCode, const char *pServer) = 0; virtual void LoadTeam(const char *pCode, int ClientID) = 0; virtual void GetSaves(int ClientID) = 0; diff --git a/src/game/server/score/file_score.cpp b/src/game/server/score/file_score.cpp index 798dc2473..4b4184c6d 100644 --- a/src/game/server/score/file_score.cpp +++ b/src/game/server/score/file_score.cpp @@ -58,7 +58,7 @@ void CFileScore::MapInfo(int ClientID, const char* MapName) // TODO: implement } -void CFileScore::MapVote(std::shared_ptr *ppResult, int ClientID, const char* MapName) +void CFileScore::MapVote(int ClientID, const char* MapName) { // TODO: implement } @@ -205,12 +205,7 @@ void CFileScore::UpdatePlayer(int ID, float Score, Save(); } -void CFileScore::CheckBirthday(int ClientID) -{ - // TODO: implement -} - -void CFileScore::LoadScore(int ClientID) +void CFileScore::LoadPlayerData(int ClientID) { CPlayerScore *pPlayer = SearchScore(ClientID, 0); if (pPlayer) @@ -241,37 +236,32 @@ void CFileScore::SaveScore(int ClientID, float Time, const char *pTimestamp, UpdatePlayer(ClientID, Time, CpTime); } -void CFileScore::ShowTop5(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut) +void CFileScore::ShowTop5(int ClientID, int Offset) { - CGameContext *pSelf = (CGameContext *) pUserData; char aBuf[512]; - Debut = maximum(1, Debut < 0 ? m_Top.size() + Debut - 3 : Debut); - pSelf->SendChatTarget(ClientID, "----------- Top 5 -----------"); + Offset = maximum(1, Offset < 0 ? m_Top.size() + Offset - 3 : Offset); + GameServer()->SendChatTarget(ClientID, "----------- Top 5 -----------"); for (int i = 0; i < 5; i++) { - if (i + Debut > m_Top.size()) + if (i + Offset > m_Top.size()) break; - CPlayerScore *r = &m_Top[i + Debut - 1]; + CPlayerScore *r = &m_Top[i + Offset - 1]; str_format(aBuf, sizeof(aBuf), - "%d. %s Time: %d minute(s) %5.2f second(s)", i + Debut, + "%d. %s Time: %d minute(s) %5.2f second(s)", i + Offset, r->m_aName, (int)r->m_Score / 60, r->m_Score - ((int)r->m_Score / 60 * 60)); - pSelf->SendChatTarget(ClientID, aBuf); + GameServer()->SendChatTarget(ClientID, aBuf); } - pSelf->SendChatTarget(ClientID, "------------------------------"); + GameServer()->SendChatTarget(ClientID, "------------------------------"); } -void CFileScore::ShowRank(int ClientID, const char* pName, bool Search) +void CFileScore::ShowRank(int ClientID, const char* pName) { CPlayerScore *pScore; int Pos = -2; char aBuf[512]; - if (!Search) - pScore = SearchScore(ClientID, &Pos); - else - pScore = SearchName(pName, &Pos, 1); + pScore = SearchName(pName, &Pos, 1); if (pScore && Pos > -1) { @@ -285,66 +275,63 @@ void CFileScore::ShowRank(int ClientID, const char* pName, bool Search) "%d. %s Time: %d minute(s) %5.2f second(s), requested by (%s)", Pos, pScore->m_aName, (int)Time / 60, Time - ((int)Time / 60 * 60), Server()->ClientName(ClientID)); - if (!Search) - GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID); - else + if (g_Config.m_SvHideScore) GameServer()->SendChatTarget(ClientID, aBuf); + else + GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID); return; } else if (Pos == -1) str_format(aBuf, sizeof(aBuf), "Several players were found."); else - str_format(aBuf, sizeof(aBuf), "%s is not ranked", - Search ? pName : Server()->ClientName(ClientID)); + str_format(aBuf, sizeof(aBuf), "%s is not ranked", pName); GameServer()->SendChatTarget(ClientID, aBuf); } -void CFileScore::ShowTeamTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) +void CFileScore::ShowTeamTop5(int ClientID, int Offset) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); } -void CFileScore::ShowTeamRank(int ClientID, const char* pName, bool Search) +void CFileScore::ShowTeamRank(int ClientID, const char* pName) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); } -void CFileScore::ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) +void CFileScore::ShowTopPoints(int ClientID, int Offset) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); } -void CFileScore::ShowPoints(int ClientID, const char* pName, bool Search) +void CFileScore::ShowPoints(int ClientID, const char* pName) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Points not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); } -void CFileScore::RandomMap(std::shared_ptr *ppResult, int ClientID, int stars) +void CFileScore::RandomMap(int ClientID, int Stars) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Random map not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); - *ppResult = NULL; } -void CFileScore::RandomUnfinishedMap(std::shared_ptr *ppResult, int ClientID, int stars) +void CFileScore::RandomUnfinishedMap(int ClientID, int Stars) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Random unfinished map not supported in file based servers"); GameServer()->SendChatTarget(ClientID, aBuf); - *ppResult = NULL; } -void CFileScore::SaveTeam(int Team, const char* Code, int ClientID, const char* Server) +void CFileScore::SaveTeam(int ClientID, const char* Code, const char* Server) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers"); diff --git a/src/game/server/score/file_score.h b/src/game/server/score/file_score.h index ceeecb88c..465d95acb 100644 --- a/src/game/server/score/file_score.h +++ b/src/game/server/score/file_score.h @@ -21,10 +21,8 @@ class CFileScore: public IScore float m_Score; float m_aCpTime[NUM_CHECKPOINTS]; - CPlayerScore() - { - } - ; + CPlayerScore() {} + CPlayerScore(const char *pName, float Score, float aCpTime[NUM_CHECKPOINTS]); @@ -49,7 +47,6 @@ class CFileScore: public IScore { return SearchName(Server()->ClientName(ID), pPosition, 0); } - ; CPlayerScore *SearchName(const char *pName, int *pPosition, bool MatchCase); void UpdatePlayer(int ID, float Score, float aCpTime[NUM_CHECKPOINTS]); @@ -63,27 +60,24 @@ public: CFileScore(CGameContext *pGameServer); ~CFileScore(); - virtual void CheckBirthday(int ClientID); - virtual void LoadScore(int ClientID); + virtual void LoadPlayerData(int ClientID); virtual void MapInfo(int ClientID, const char* MapName); - virtual void MapVote(std::shared_ptr *ppResult, int ClientID, const char* MapName); + virtual void MapVote(int ClientID, const char* MapName); virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible); virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp); - virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut = 1); - virtual void ShowRank(int ClientID, const char* pName, bool Search = false); + virtual void ShowTop5(int ClientID, int Offset = 1); + virtual void ShowRank(int ClientID, const char* pName); - virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut = 1); - virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false); + virtual void ShowTeamTop5(int ClientID, int Offset = 1); + virtual void ShowTeamRank(int ClientID, const char* pName); - virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut); - virtual void ShowPoints(int ClientID, const char* pName, bool Search); - virtual void RandomMap(std::shared_ptr *ppResult, int ClientID, int stars); - virtual void RandomUnfinishedMap(std::shared_ptr *ppResult, int ClientID, int stars); - virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server); + virtual void ShowTopPoints(int ClientID, int Offset); + virtual void ShowPoints(int ClientID, const char* pName); + virtual void RandomMap(int ClientID, int Stars); + virtual void RandomUnfinishedMap(int ClientID, int Stars); + virtual void SaveTeam(int ClientID, const char* Code, const char* Server); virtual void LoadTeam(const char* Code, int ClientID); virtual void GetSaves(int ClientID); diff --git a/src/game/server/score/sql_score.cpp b/src/game/server/score/sql_score.cpp index 6d425d691..8c97f9f53 100644 --- a/src/game/server/score/sql_score.cpp +++ b/src/game/server/score/sql_score.cpp @@ -2,75 +2,128 @@ /* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ /* CSqlScore class by Sushi */ #if defined(CONF_SQL) +#include "sql_score.h" + #include #include +#include +#include +#include +#include #include #include +#include #include - -#include "sql_score.h" +#include #include "../entities/character.h" #include "../gamemodes/DDRace.h" #include "../save.h" -CGameContext* CSqlData::ms_pGameServer = 0; -IServer* CSqlData::ms_pServer = 0; -CPlayerData* CSqlData::ms_pPlayerData = 0; -const char* CSqlData::ms_pMap = 0; -const char* CSqlData::ms_pGameUuid = 0; +std::atomic_int CSqlScore::ms_InstanceCount(0); -bool CSqlData::ms_GameContextAvailable = false; -int CSqlData::ms_Instance = 0; +CSqlPlayerResult::CSqlPlayerResult() : + m_Done(false) +{ + SetVariant(Variant::DIRECT); +} -volatile int CSqlExecData::ms_InstanceCount = 0; +void CSqlPlayerResult::SetVariant(Variant v) +{ + m_MessageKind = v; + switch(v) + { + case DIRECT: + case ALL: + for(int i = 0; i < (int)(sizeof(m_aaMessages)/sizeof(m_aaMessages[0])); i++) + m_aaMessages[i][0] = 0; + break; + case BROADCAST: + m_Data.m_Broadcast[0] = 0; + break; + case MAP_VOTE: + m_Data.m_MapVote.m_Map[0] = '\0'; + m_Data.m_MapVote.m_Reason[0] = '\0'; + m_Data.m_MapVote.m_Server[0] = '\0'; + break; + case PLAYER_INFO: + m_Data.m_Info.m_Score = -9999; + m_Data.m_Info.m_Birthday = 0; + m_Data.m_Info.m_HasFinishScore = false; + m_Data.m_Info.m_Time = 0; + for(int i = 0; i < NUM_CHECKPOINTS; i++) + m_Data.m_Info.m_CpTime[i] = 0; + } +} + +template < typename TResult > +CSqlExecData::CSqlExecData( + bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), + CSqlData *pSqlResult, + bool ReadOnly +) : + m_pFuncPtr(pFuncPtr), + m_pSqlData(pSqlResult), + m_ReadOnly(ReadOnly) +{ + ++CSqlScore::ms_InstanceCount; +} + +template < typename TResult > +CSqlExecData::~CSqlExecData() +{ + --CSqlScore::ms_InstanceCount; +} + +std::shared_ptr CSqlScore::NewSqlPlayerResult(int ClientID) +{ + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + if(pCurPlayer->m_SqlQueryResult != nullptr) // TODO: send player a message: "too many requests" + return nullptr; + pCurPlayer->m_SqlQueryResult = std::make_shared(); + return pCurPlayer->m_SqlQueryResult; +} + +void CSqlScore::ExecPlayerThread( + bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), + const char* pThreadName, + int ClientID, + const char* pName, + int Offset +) { + auto pResult = NewSqlPlayerResult(ClientID); + if(pResult == nullptr) + return; + CSqlPlayerRequest *Tmp = new CSqlPlayerRequest(pResult); + Tmp->m_Name = pName; + Tmp->m_Map = g_Config.m_SvMap; + Tmp->m_RequestingPlayer = Server()->ClientName(ClientID); + Tmp->m_Offset = Offset; + + thread_init_and_detach(CSqlExecData::ExecSqlFunc, + new CSqlExecData(pFuncPtr, Tmp), + pThreadName); +} + +void CSqlScore::GeneratePassphrase(char *pBuf, int BufSize) +{ + for(int i = 0; i < 3; i++) + { + if(i != 0) + str_append(pBuf, " ", BufSize); + // TODO: decide if the slight bias towards lower numbers is ok + int Rand = m_Prng.RandomBits() % m_aWordlist.size(); + str_append(pBuf, m_aWordlist[Rand].c_str(), BufSize); + } +} LOCK CSqlScore::ms_FailureFileLock = lock_create(); -CSqlTeamSave::~CSqlTeamSave() -{ - try - { - ((class CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.SetSaving(m_Team, false); - } - catch (CGameContextError& e) {} -} - - - -CSqlScore::CSqlScore(CGameContext *pGameServer) : -m_pGameServer(pGameServer), -m_pServer(pGameServer->Server()) -{ - str_copy(m_aMap, g_Config.m_SvMap, sizeof(m_aMap)); - FormatUuid(m_pGameServer->GameUuid(), m_aGameUuid, sizeof(m_aGameUuid)); - - CSqlData::ms_pGameServer = m_pGameServer; - CSqlData::ms_pServer = m_pServer; - CSqlData::ms_pPlayerData = PlayerData(0); - CSqlData::ms_pMap = m_aMap; - CSqlData::ms_pGameUuid = m_aGameUuid; - - CSqlData::ms_GameContextAvailable = true; - ++CSqlData::ms_Instance; - - CSqlConnector::ResetReachable(); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(Init, new CSqlData()), "SqlScore constructor"); -} - - -CSqlScore::~CSqlScore() -{ - CSqlData::ms_GameContextAvailable = false; -} - void CSqlScore::OnShutdown() { - CSqlData::ms_GameContextAvailable = false; int i = 0; - while (CSqlExecData::ms_InstanceCount != 0) + while (CSqlScore::ms_InstanceCount != 0) { if (i > 600) { dbg_msg("sql", "Waited 60 seconds for score-threads to complete, quitting anyway"); @@ -79,7 +132,7 @@ void CSqlScore::OnShutdown() // print a log about every two seconds if (i % 20 == 0) - dbg_msg("sql", "Waiting for score-threads to complete (%d left)", CSqlExecData::ms_InstanceCount); + dbg_msg("sql", "Waiting for score-threads to complete (%d left)", CSqlScore::ms_InstanceCount.load()); ++i; thread_sleep(100000); } @@ -87,16 +140,17 @@ void CSqlScore::OnShutdown() lock_destroy(ms_FailureFileLock); } -void CSqlScore::ExecSqlFunc(void *pUser) +template < typename TResult > +void CSqlExecData::ExecSqlFunc(void *pUser) { - CSqlExecData* pData = (CSqlExecData *)pUser; + CSqlExecData* pData = (CSqlExecData *)pUser; CSqlConnector connector; bool Success = false; try { - // try to connect to a working databaseserver + // try to connect to a working database server while (!Success && !connector.MaxTriesReached(pData->m_ReadOnly) && connector.ConnectSqlServer(pData->m_ReadOnly)) { try { @@ -106,7 +160,7 @@ void CSqlScore::ExecSqlFunc(void *pUser) dbg_msg("sql", "Unexpected exception caught"); } - // disconnect from databaseserver + // disconnect from database server connector.SqlServer()->Disconnect(); } @@ -122,9 +176,52 @@ void CSqlScore::ExecSqlFunc(void *pUser) delete pData; } -bool CSqlScore::Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +CSqlScore::CSqlScore(CGameContext *pGameServer) : + m_pGameServer(pGameServer), + m_pServer(pGameServer->Server()) { - const CSqlData* pData = pGameData; + CSqlConnector::ResetReachable(); + + auto InitResult = std::make_shared(); + CSqlInitData *Tmp = new CSqlInitData(InitResult); + ((CGameControllerDDRace*)(pGameServer->m_pController))->m_pInitResult = InitResult; + Tmp->m_Map = g_Config.m_SvMap; + + IOHANDLE File = GameServer()->Storage()->OpenFile("wordlist.txt", IOFLAG_READ, IStorage::TYPE_ALL); + if(!File) + { + dbg_msg("sql", "failed to open wordlist"); + Server()->SetErrorShutdown("sql open wordlist error"); + return; + } + + uint64 aSeed[2]; + secure_random_fill(aSeed, sizeof(aSeed)); + m_Prng.Seed(aSeed); + CLineReader LineReader; + LineReader.Init(File); + char *pLine; + while((pLine = LineReader.Get())) + { + char Word[32] = {0}; + sscanf(pLine, "%*s %31s", Word); + Word[31] = 0; + m_aWordlist.push_back(Word); + } + if(m_aWordlist.size() < 1000) + { + dbg_msg("sql", "too few words in wordlist"); + Server()->SetErrorShutdown("sql too few words in wordlist"); + return; + } + thread_init_and_detach(CSqlExecData::ExecSqlFunc, + new CSqlExecData(Init, Tmp), + "SqlScore constructor"); +} + +bool CSqlScore::Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +{ + const CSqlInitData *pData = dynamic_cast(pGameData); if (HandleFailure) { @@ -134,95 +231,37 @@ bool CSqlScore::Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool Han try { - char aBuf[1024]; + char aBuf[512]; // get the best time - str_format(aBuf, sizeof(aBuf), "SELECT Time FROM %s_race WHERE Map='%s' ORDER BY `Time` ASC LIMIT 0, 1;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT Time FROM %s_race WHERE Map='%s' ORDER BY `Time` ASC LIMIT 1;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->next()) - { - ((CGameControllerDDRace*)pData->GameServer()->m_pController)->m_CurrentRecord = (float)pSqlServer->GetResults()->getDouble("Time"); + pData->m_pResult->m_CurrentRecord = (float)pSqlServer->GetResults()->getDouble("Time"); - dbg_msg("sql", "Getting best time on server done"); - } + pData->m_pResult->m_Done = true; + dbg_msg("sql", "Getting best time on server done"); return true; } catch (sql::SQLException &e) { dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Tables were NOT created"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Setting best time failed (game reloaded)."); - } - return false; } -void CSqlScore::CheckBirthday(int ClientID) +void CSqlScore::LoadPlayerData(int ClientID) { - CSqlPlayerData *Tmp = new CSqlPlayerData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = Server()->ClientName(ClientID); - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(CheckBirthdayThread, Tmp), "birthday check"); -} - -bool CSqlScore::CheckBirthdayThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) -{ - const CSqlPlayerData *pData = dynamic_cast(pGameData); - - if (HandleFailure) - return true; - - try - { - char aBuf[512]; - - str_format(aBuf, sizeof(aBuf), "select year(Current) - year(Stamp) as YearsAgo from (select CURRENT_TIMESTAMP as Current, min(Timestamp) as Stamp from %s_race WHERE Name='%s') as l where dayofmonth(Current) = dayofmonth(Stamp) and month(Current) = month(Stamp) and year(Current) > year(Stamp);", pSqlServer->GetPrefix(), pData->m_Name.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); - - if(pSqlServer->GetResults()->next()) - { - int yearsAgo = pSqlServer->GetResults()->getInt("YearsAgo"); - str_format(aBuf, sizeof(aBuf), "Happy DDNet birthday to %s for finishing their first map %d year%s ago!", pData->m_Name.Str(), yearsAgo, yearsAgo > 1 ? "s" : ""); - pData->GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, pData->m_ClientID); - - str_format(aBuf, sizeof(aBuf), "Happy DDNet birthday, %s!\nYou have finished your first map exactly %d year%s ago!", pData->m_Name.Str(), yearsAgo, yearsAgo > 1 ? "s" : ""); - - pData->GameServer()->SendBroadcast(aBuf, pData->m_ClientID); - } - - dbg_msg("sql", "checking birthday done"); - return true; - } - catch (sql::SQLException &e) - { - dbg_msg("sql", "MySQL ERROR: %s", e.what()); - dbg_msg("sql", "ERROR: could not check birthday"); - } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted checking ddnet-birthday due to reload/change of map."); - return true; - } - - return false; -} - -void CSqlScore::LoadScore(int ClientID) -{ - CSqlPlayerData *Tmp = new CSqlPlayerData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = Server()->ClientName(ClientID); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(LoadScoreThread, Tmp), "load score"); + ExecPlayerThread(LoadPlayerDataThread, "load player data", ClientID, "", 0); } // update stuff -bool CSqlScore::LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlPlayerData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + pData->m_pResult->SetVariant(CSqlPlayerResult::PLAYER_INFO); if (HandleFailure) return true; @@ -230,20 +269,22 @@ bool CSqlScore::LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat try { char aBuf[512]; - - str_format(aBuf, sizeof(aBuf), "SELECT * FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); + // get best race time + str_format(aBuf, sizeof(aBuf), + "SELECT * " + "FROM %s_race " + "WHERE Map='%s' AND Name='%s' " + "ORDER BY Time ASC " + "LIMIT 1;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_RequestingPlayer.ClrStr()); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->next()) { // get the best time float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - pData->PlayerData(pData->m_ClientID)->m_BestTime = Time; - pData->PlayerData(pData->m_ClientID)->m_CurrentTime = Time; - if(pData->GameServer()->m_apPlayers[pData->m_ClientID]) - { - pData->GameServer()->m_apPlayers[pData->m_ClientID]->m_Score = -Time; - pData->GameServer()->m_apPlayers[pData->m_ClientID]->m_HasFinishScore = true; - } + pData->m_pResult->m_Data.m_Info.m_Time = Time; + pData->m_pResult->m_Data.m_Info.m_Score = -Time; + pData->m_pResult->m_Data.m_Info.m_HasFinishScore = true; char aColumn[8]; if(g_Config.m_SvCheckpointSave) @@ -251,12 +292,31 @@ bool CSqlScore::LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat for(int i = 0; i < NUM_CHECKPOINTS; i++) { str_format(aColumn, sizeof(aColumn), "cp%d", i+1); - pData->PlayerData(pData->m_ClientID)->m_aBestCpTime[i] = (float)pSqlServer->GetResults()->getDouble(aColumn); + pData->m_pResult->m_Data.m_Info.m_CpTime[i] = (float)pSqlServer->GetResults()->getDouble(aColumn); } } } - dbg_msg("sql", "Getting best time done"); + // birthday check + str_format(aBuf, sizeof(aBuf), + "SELECT YEAR(Current) - YEAR(Stamp) AS YearsAgo " + "FROM (" + "SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp " + "FROM %s_race " + "WHERE Name='%s'" + ") AS l " + "WHERE DAYOFMONTH(Current) = DAYOFMONTH(Stamp) AND MONTH(Current) = MONTH(Stamp) " + "AND YEAR(Current) > YEAR(Stamp);", + pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); + pSqlServer->executeSqlQuery(aBuf); + + if(pSqlServer->GetResults()->next()) + { + int YearsAgo = pSqlServer->GetResults()->getInt("YearsAgo"); + pData->m_pResult->m_Data.m_Info.m_Birthday = YearsAgo; + } + pData->m_pResult->m_Done = true; + dbg_msg("sql", "Finished loading player data"); return true; } catch (sql::SQLException &e) @@ -264,89 +324,66 @@ bool CSqlScore::LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not update account"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted loading score due to reload/change of map."); - return true; - } return false; } -void CSqlScore::MapVote(std::shared_ptr *ppResult, int ClientID, const char* MapName) +void CSqlScore::MapVote(int ClientID, const char* MapName) { - *ppResult = std::make_shared(); - - CSqlMapVoteData *Tmp = new CSqlMapVoteData(); - Tmp->m_ClientID = ClientID; - Tmp->m_RequestedMap = MapName; - Tmp->m_pResult = *ppResult; - str_copy(Tmp->m_aFuzzyMap, MapName, sizeof(Tmp->m_aFuzzyMap)); - sqlstr::ClearString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap)); - sqlstr::FuzzyString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(MapVoteThread, Tmp), "map vote"); + ExecPlayerThread(MapVoteThread, "map vote", ClientID, MapName, 0); } -bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlMapVoteData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; try { + char aFuzzyMap[128]; + str_copy(aFuzzyMap, pData->m_Name.Str(), sizeof(aFuzzyMap)); + sqlstr::ClearString(aFuzzyMap, sizeof(aFuzzyMap)); + sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); + char aBuf[768]; - str_format(aBuf, sizeof(aBuf), "SELECT Map, Server FROM %s_maps WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci ORDER BY CASE WHEN Map = '%s' THEN 0 ELSE 1 END, CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, LENGTH(Map), Map LIMIT 1;", pSqlServer->GetPrefix(), pData->m_aFuzzyMap, pData->m_RequestedMap.ClrStr(), pData->m_RequestedMap.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT Map, Server " + "FROM %s_maps " + "WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci " + "ORDER BY " + "CASE WHEN Map = '%s' THEN 0 ELSE 1 END, " + "CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, " + "LENGTH(Map), Map " + "LIMIT 1;", + pSqlServer->GetPrefix(), aFuzzyMap, + pData->m_Name.ClrStr(), pData->m_Name.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); - - CPlayer *pPlayer = pData->GameServer()->m_apPlayers[pData->m_ClientID]; - - int64 Now = pData->Server()->Tick(); - int Timeleft = 0; - - if(!pPlayer) - goto end; - - Timeleft = pPlayer->m_LastVoteCall + pData->Server()->TickSpeed()*g_Config.m_SvVoteDelay - Now; - if(pSqlServer->GetResults()->rowsCount() != 1) { - str_format(aBuf, sizeof(aBuf), "No map like \"%s\" found. Try adding a '%%' at the start if you don't know the first character. Example: /map %%castle for \"Out of Castle\"", pData->m_RequestedMap.Str()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else if(Now < pPlayer->m_FirstVoteTick) - { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "You must wait %d seconds before making your first vote", (int)((pPlayer->m_FirstVoteTick - Now) / pData->Server()->TickSpeed()) + 1); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else if(pPlayer->m_LastVoteCall && Timeleft > 0) - { - char aChatmsg[512] = {0}; - str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote", (Timeleft/pData->Server()->TickSpeed())+1); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aChatmsg); - } - else if(time_get() < pData->GameServer()->m_LastMapVote + (time_freq() * g_Config.m_SvVoteMapTimeDelay)) - { - char chatmsg[512] = {0}; - str_format(chatmsg, sizeof(chatmsg), "There's a %d second delay between map-votes, please wait %d seconds.", g_Config.m_SvVoteMapTimeDelay, (int)(((pData->GameServer()->m_LastMapVote+(g_Config.m_SvVoteMapTimeDelay * time_freq()))/time_freq())-(time_get()/time_freq()))); - pData->GameServer()->SendChatTarget(pData->m_ClientID, chatmsg); + str_format(paMessages[0], sizeof(paMessages[0]), + "No map like \"%s\" found. " + "Try adding a '%%' at the start if you don't know the first character. " + "Example: /map %%castle for \"Out of Castle\"", + pData->m_Name.Str()); } else { - pSqlServer->GetResults()->next(); - str_copy(pData->m_pResult->m_aMap, pSqlServer->GetResults()->getString("Map").c_str(), sizeof(pData->m_pResult->m_aMap)); - str_copy(pData->m_pResult->m_aServer, pSqlServer->GetResults()->getString("Server").c_str(), sizeof(pData->m_pResult->m_aServer)); + pSqlServer->GetResults()->first(); + auto Server = pSqlServer->GetResults()->getString("Server"); + auto Map = pSqlServer->GetResults()->getString("Map"); + pData->m_pResult->SetVariant(CSqlPlayerResult::MAP_VOTE); + auto MapVote = &pData->m_pResult->m_Data.m_MapVote; + strcpy(MapVote->m_Reason, "/map"); + str_copy(MapVote->m_Server, Server.c_str(), sizeof(MapVote->m_Server)); + str_copy(MapVote->m_Map, Map.c_str(), sizeof(MapVote->m_Map)); - for(char *p = pData->m_pResult->m_aServer; *p; p++) + for(char *p = MapVote->m_Server; *p; p++) // lower case server *p = tolower(*p); - - pData->m_pResult->m_ClientID = pData->m_ClientID; - - pData->m_pResult->m_Done = true; } - end: + pData->m_pResult->m_Done = true; return true; } catch (sql::SQLException &e) @@ -354,49 +391,67 @@ bool CSqlScore::MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not start Mapvote"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted mapvote due to reload/change of map."); - return true; - } return false; } void CSqlScore::MapInfo(int ClientID, const char* MapName) { - CSqlMapData *Tmp = new CSqlMapData(); - Tmp->m_ClientID = ClientID; - Tmp->m_RequestedMap = MapName; - Tmp->m_Name = Server()->ClientName(ClientID); - str_copy(Tmp->m_aFuzzyMap, MapName, sizeof(Tmp->m_aFuzzyMap)); - sqlstr::ClearString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap)); - sqlstr::FuzzyString(Tmp->m_aFuzzyMap, sizeof(Tmp->m_aFuzzyMap)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(MapInfoThread, Tmp), "map info"); + ExecPlayerThread(MapInfoThread, "map info", ClientID, MapName, 0); } -bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlMapData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); if (HandleFailure) return true; try { + char aFuzzyMap[128]; + str_copy(aFuzzyMap, pData->m_Name.Str(), sizeof(aFuzzyMap)); + sqlstr::ClearString(aFuzzyMap, sizeof(aFuzzyMap)); + sqlstr::FuzzyString(aFuzzyMap, sizeof(aFuzzyMap)); + char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "SELECT l.Map, l.Server, Mapper, Points, Stars, (select count(Name) from %s_race where Map = l.Map) as Finishes, (select count(distinct Name) from %s_race where Map = l.Map) as Finishers, (select round(avg(Time)) from %s_race where Map = l.Map) as Average, UNIX_TIMESTAMP(l.Timestamp) as Stamp, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(l.Timestamp) as Ago, (select min(Time) from %s_race where Map = l.Map and Name = '%s') as OwnTime FROM (SELECT * FROM %s_maps WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci ORDER BY CASE WHEN Map = '%s' THEN 0 ELSE 1 END, CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, LENGTH(Map), Map LIMIT 1) as l;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), pSqlServer->GetPrefix(), pData->m_aFuzzyMap, pData->m_RequestedMap.ClrStr(), pData->m_RequestedMap.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT l.Map, l.Server, Mapper, Points, Stars, " + "(select count(Name) from %s_race where Map = l.Map) as Finishes, " + "(select count(distinct Name) from %s_race where Map = l.Map) as Finishers, " + "(select round(avg(Time)) from %s_race where Map = l.Map) as Average, " + "UNIX_TIMESTAMP(l.Timestamp) as Stamp, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(l.Timestamp) as Ago, " + "(select min(Time) from %s_race where Map = l.Map and Name = '%s') as OwnTime " + "FROM (" + "SELECT * FROM %s_maps " + "WHERE Map LIKE '%s' COLLATE utf8mb4_general_ci " + "ORDER BY " + "CASE WHEN Map = '%s' THEN 0 ELSE 1 END, " + "CASE WHEN Map LIKE '%s%%' THEN 0 ELSE 1 END, " + "LENGTH(Map), " + "Map " + "LIMIT 1" + ") as l;", + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), + pData->m_RequestingPlayer.ClrStr(), + pSqlServer->GetPrefix(), + aFuzzyMap, + pData->m_Name.ClrStr(), + pData->m_Name.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->rowsCount() != 1) { - str_format(aBuf, sizeof(aBuf), "No map like \"%s\" found.", pData->m_RequestedMap.Str()); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "No map like \"%s\" found.", pData->m_Name.Str()); } else { pSqlServer->GetResults()->next(); - int points = pSqlServer->GetResults()->getInt("Points"); - int stars = pSqlServer->GetResults()->getInt("Stars"); + int Points = pSqlServer->GetResults()->getInt("Points"); + int Stars = pSqlServer->GetResults()->getInt("Stars"); int finishes = pSqlServer->GetResults()->getInt("Finishes"); int finishers = pSqlServer->GetResults()->getInt("Finishers"); int average = pSqlServer->GetResults()->getInt("Average"); @@ -425,7 +480,7 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, } char aStars[20]; - switch(stars) + switch(Stars) { case 0: strcpy(aStars, "✰✰✰✰✰"); break; case 1: strcpy(aStars, "★✰✰✰✰"); break; @@ -439,13 +494,22 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, char aOwnFinishesString[40] = "\0"; if(ownTime > 0) { - str_format(aOwnFinishesString, sizeof(aOwnFinishesString), ", your time: %02d:%05.2f", (int)(ownTime/60), ownTime-((int)ownTime/60*60)); + str_format(aOwnFinishesString, sizeof(aOwnFinishesString), + ", your time: %02d:%05.2f", (int)(ownTime/60), ownTime-((int)ownTime/60*60) + ); } - str_format(aBuf, sizeof(aBuf), "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", aMap, aMapper, aServer, aStars, points, points == 1 ? "point" : "points", aReleasedString, finishes, finishes == 1 ? "finish" : "finishes", finishers, finishers == 1 ? "tee" : "tees", aAverageString, aOwnFinishesString); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "\"%s\" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s", + aMap, aMapper, aServer, aStars, + Points, Points == 1 ? "point" : "points", + aReleasedString, + finishes, finishes == 1 ? "finish" : "finishes", + finishers, finishers == 1 ? "tee" : "tees", + aAverageString, aOwnFinishesString + ); } - - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + pData->m_pResult->m_Done = true; return true; } catch (sql::SQLException &e) @@ -453,74 +517,100 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not get Mapinfo"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted mapinfo-thread due to reload/change of map."); - return true; - } return false; } void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible) { CConsole* pCon = (CConsole*)GameServer()->Console(); - if(pCon->m_Cheated) + if(pCon->m_Cheated || NotEligible) return; - CSqlScoreData *Tmp = new CSqlScoreData(); + + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + if(pCurPlayer->m_SqlFinishResult != nullptr) + dbg_msg("sql", "WARNING: previous save score result didn't complete, overwriting it now"); + pCurPlayer->m_SqlFinishResult = std::make_shared(); + CSqlScoreData *Tmp = new CSqlScoreData(pCurPlayer->m_SqlFinishResult); + Tmp->m_Map = g_Config.m_SvMap; + FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); Tmp->m_ClientID = ClientID; Tmp->m_Name = Server()->ClientName(ClientID); Tmp->m_Time = Time; str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); - Tmp->m_NotEligible = NotEligible; for(int i = 0; i < NUM_CHECKPOINTS; i++) Tmp->m_aCpCurrent[i] = CpTime[i]; - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveScoreThread, Tmp, false), "save score"); + thread_init_and_detach(CSqlExecData::ExecSqlFunc, + new CSqlExecData(SaveScoreThread, Tmp), + "save score"); } -bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { const CSqlScoreData *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; - if (HandleFailure) + if(HandleFailure) { - if (!g_Config.m_SvSqlFailureFile[0]) + if(!g_Config.m_SvSqlFailureFile[0]) return true; lock_wait(ms_FailureFileLock); IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); - if(File) + if(File == 0) { - dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now..."); - - char aBuf[768]; - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s', false);", pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr()); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - io_close(File); - lock_unlock(ms_FailureFileLock); - - pData->GameServer()->SendBroadcast("Database connection failed, score written to a file instead. Admins will add it manually in a few days.", -1); - - return true; + lock_unlock(ms_FailureFileLock); + dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file"); + return false; } - lock_unlock(ms_FailureFileLock); - dbg_msg("sql", "ERROR: Could not save Score, NOT even to a file"); - return false; - } + dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now..."); - if(pData->m_NotEligible) - { - return false; + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, " + "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " + "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " + "GameID, DDNet7) " + "VALUES ('%s', '%s', '%s', '%.2f', '%s'," + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%.2f', '%s', false);", + pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), + pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, + pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], + pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], + pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], + pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], + pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], + pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], + pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], + pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], + pData->m_aCpCurrent[24], + pData->m_GameUuid); + io_write(File, aBuf, str_length(aBuf)); + io_write_newline(File); + io_close(File); + lock_unlock(ms_FailureFileLock); + + pData->m_pResult->SetVariant(CSqlPlayerResult::BROADCAST); + strcpy(pData->m_pResult->m_Data.m_Broadcast, + "Database connection failed, score written to a file instead. Admins will add it manually in a few days."); + pData->m_pResult->m_Done = true; + return true; } try { - char aBuf[768]; + char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "SELECT * FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map='%s' AND Name='%s' ORDER BY time ASC LIMIT 1;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); pSqlServer->executeSqlQuery(aBuf); - if(!pSqlServer->GetResults()->next()) + pSqlServer->GetResults()->first(); + int NumFinished = pSqlServer->GetResults()->getInt("NumFinished"); + if(NumFinished == 0) { str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map ='%s'", pSqlServer->GetPrefix(), pData->m_Map.ClrStr()); pSqlServer->executeSqlQuery(aBuf); @@ -528,35 +618,56 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat if(pSqlServer->GetResults()->rowsCount() == 1) { pSqlServer->GetResults()->next(); - int points = pSqlServer->GetResults()->getInt("Points"); - if (points == 1) - str_format(aBuf, sizeof(aBuf), "You earned %d point for finishing this map!", points); + int Points = pSqlServer->GetResults()->getInt("Points"); + if(Points == 1) + str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point for finishing this map!", Points); else - str_format(aBuf, sizeof(aBuf), "You earned %d points for finishing this map!", points); + str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d points for finishing this map!", Points); - try - { - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - catch (CGameContextError &e) {} // just do nothing, it is not much of a problem if the player is not informed about points during mapchange - - str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_points(Name, Points) VALUES ('%s', '%d') ON duplicate key UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), points); + str_format(aBuf, sizeof(aBuf), + "INSERT INTO %s_points(Name, Points) " + "VALUES ('%s', '%d') " + "ON duplicate key " + "UPDATE Name=VALUES(Name), Points=Points+VALUES(Points);", + pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), Points); pSqlServer->executeSql(aBuf); } } - // if no entry found... create a new one - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s', false);", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr()); + // save score + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_race(" + "Map, Name, Timestamp, Time, Server, " + "cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, " + "cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, " + "GameID, DDNet7) " + "VALUES ('%s', '%s', '%s', '%.2f', '%s', " + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', " + "'%s', false);", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), + pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, + pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], + pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], + pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], + pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], + pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], + pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], + pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], + pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], + pData->m_aCpCurrent[24], pData->m_GameUuid); dbg_msg("sql", "%s", aBuf); pSqlServer->executeSql(aBuf); - dbg_msg("sql", "Updating time done"); + pData->m_pResult->m_Done = true; + dbg_msg("sql", "Saving score done"); return true; } catch (sql::SQLException &e) { dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not update time"); + dbg_msg("sql", "ERROR: Could not insert time"); } return false; } @@ -566,28 +677,32 @@ void CSqlScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, co CConsole* pCon = (CConsole*)GameServer()->Console(); if(pCon->m_Cheated) return; - CSqlTeamScoreData *Tmp = new CSqlTeamScoreData(); - Tmp->m_NotEligible = false; for(unsigned int i = 0; i < Size; i++) { - Tmp->m_aClientIDs[i] = aClientIDs[i]; - Tmp->m_aNames[i] = Server()->ClientName(aClientIDs[i]); - Tmp->m_NotEligible = Tmp->m_NotEligible || GameServer()->m_apPlayers[aClientIDs[i]]->m_NotEligibleForFinish; + if(GameServer()->m_apPlayers[aClientIDs[i]]->m_NotEligibleForFinish) + return; } + CSqlTeamScoreData *Tmp = new CSqlTeamScoreData(nullptr); + for(unsigned int i = 0; i < Size; i++) + Tmp->m_aNames[i] = Server()->ClientName(aClientIDs[i]); Tmp->m_Size = Size; Tmp->m_Time = Time; str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp)); + FormatUuid(GameServer()->GameUuid(), Tmp->m_GameUuid, sizeof(Tmp->m_GameUuid)); + Tmp->m_Map = g_Config.m_SvMap; - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveTeamScoreThread, Tmp, false), "save team score"); + thread_init_and_detach(CSqlExecData::ExecSqlFunc, + new CSqlExecData(SaveTeamScoreThread, Tmp), + "save team score"); } -bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { const CSqlTeamScoreData *pData = dynamic_cast(pGameData); - if (HandleFailure) + if(HandleFailure) { - if (!g_Config.m_SvSqlFailureFile[0]) + if(!g_Config.m_SvSqlFailureFile[0]) return true; dbg_msg("sql", "ERROR: Could not save TeamScore, writing insert to a file now..."); @@ -603,7 +718,7 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam char aBuf[2300]; for(unsigned int i = 0; i < pData->m_Size; i++) { - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid.ClrStr()); + str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid); io_write(File, aBuf, str_length(aBuf)); io_write_newline(File); } @@ -615,99 +730,68 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam return false; } - if(pData->m_NotEligible) - { - return false; - } - try { char aBuf[2300]; - char aUpdateID[17]; - aUpdateID[0] = 0; - str_format(aBuf, sizeof(aBuf), "SELECT Name, l.ID, Time FROM ((SELECT ID FROM %s_teamrace WHERE Map = '%s' AND Name = '%s' and DDNet7 = false) as l) LEFT JOIN %s_teamrace as r ON l.ID = r.ID ORDER BY ID;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[0].ClrStr(), pSqlServer->GetPrefix()); + // get the names sorted in a tab separated string + const sqlstr::CSqlString *apNames[MAX_CLIENTS]; + for(unsigned int i = 0; i < pData->m_Size; i++) + apNames[i] = &pData->m_aNames[i]; + std::sort(apNames, apNames+pData->m_Size); + char aSortedNames[2048] = {0}; + for(unsigned int i = 0; i < pData->m_Size; i++) + { + if(i != 0) + str_append(aSortedNames, "\t", sizeof(aSortedNames)); + str_append(aSortedNames, apNames[i]->ClrStr(), sizeof(aSortedNames)); + } + str_format(aBuf, sizeof(aBuf), + "SELECT l.ID, Time " + "FROM ((" // preselect teams with first name in team + "SELECT ID " + "FROM %s_teamrace " + "WHERE Map = '%s' AND Name = '%s' AND DDNet7 = false" + ") as l" + ") INNER JOIN %s_teamrace AS r ON l.ID = r.ID " + "GROUP BY ID " + "HAVING GROUP_CONCAT(Name ORDER BY Name SEPARATOR '\t') = '%s'", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[0].ClrStr(), + pSqlServer->GetPrefix(), aSortedNames); pSqlServer->executeSqlQuery(aBuf); if (pSqlServer->GetResults()->rowsCount() > 0) { - char aID[17]; - char aID2[17]; - char aName[64]; - unsigned int Count = 0; - bool ValidNames = true; - pSqlServer->GetResults()->first(); float Time = (float)pSqlServer->GetResults()->getDouble("Time"); - strcpy(aID, pSqlServer->GetResults()->getString("ID").c_str()); - - do + auto ID = pSqlServer->GetResults()->getString("ID"); + dbg_msg("sql", "found team rank from same team (old time: %f, new time: %f)", Time, pData->m_Time); + if(pData->m_Time < Time) { - strcpy(aID2, pSqlServer->GetResults()->getString("ID").c_str()); - strcpy(aName, pSqlServer->GetResults()->getString("Name").c_str()); - sqlstr::ClearString(aName); - if (str_comp(aID, aID2) != 0) - { - if (ValidNames && Count == pData->m_Size) - { - if (pData->m_Time < Time) - strcpy(aUpdateID, aID); - else - goto end; - break; - } - - Time = (float)pSqlServer->GetResults()->getDouble("Time"); - ValidNames = true; - Count = 0; - strcpy(aID, aID2); - } - - if (!ValidNames) - continue; - - ValidNames = false; - - for(unsigned int i = 0; i < pData->m_Size; i++) - { - if (str_comp(aName, pData->m_aNames[i].ClrStr()) == 0) - { - ValidNames = true; - Count++; - break; - } - } - } while (pSqlServer->GetResults()->next()); - - if (ValidNames && Count == pData->m_Size) - { - if (pData->m_Time < Time) - strcpy(aUpdateID, aID); - else - goto end; + str_format(aBuf, sizeof(aBuf), + "UPDATE %s_teamrace SET Time='%.2f', Timestamp='%s', DDNet7=false WHERE ID = '%s';", + pSqlServer->GetPrefix(), pData->m_Time, pData->m_aTimestamp, ID.c_str()); + dbg_msg("sql", "%s", aBuf); + pSqlServer->executeSql(aBuf); } } - - if (aUpdateID[0]) - { - str_format(aBuf, sizeof(aBuf), "UPDATE %s_teamrace SET Time='%.2f', Timestamp='%s', DDNet7=false WHERE ID = '%s';", pSqlServer->GetPrefix(), pData->m_Time, pData->m_aTimestamp, aUpdateID); - dbg_msg("sql", "%s", aBuf); - pSqlServer->executeSql(aBuf); - } else { pSqlServer->executeSql("SET @id = UUID();"); for(unsigned int i = 0; i < pData->m_Size; i++) { - // if no entry found... create a new one - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid.ClrStr()); + // if no entry found... create a new one + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) " + "VALUES ('%s', '%s', '%s', '%.2f', @id, '%s', false);", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), + pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid); dbg_msg("sql", "%s", aBuf); pSqlServer->executeSql(aBuf); } } - end: dbg_msg("sql", "Updating team time done"); return true; } @@ -719,40 +803,46 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam return false; } -void CSqlScore::ShowRank(int ClientID, const char* pName, bool Search) +void CSqlScore::ShowRank(int ClientID, const char* pName) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = pName; - Tmp->m_Search = Search; - str_copy(Tmp->m_aRequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_aRequestingPlayer)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowRankThread, Tmp), "show rank"); + ExecPlayerThread(ShowRankThread, "show rank", ClientID, pName, 0); } -bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); - + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); if (HandleFailure) + { + pData->m_pResult->m_Done = true; return true; + } try { // check sort method char aBuf[600]; - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - str_format(aBuf, sizeof(aBuf), "SELECT Rank, Name, Time FROM (SELECT Name, (@pos := @pos+1) pos, (@rank := IF(@prev = Time,@rank, @pos)) rank, (@prev := Time) Time FROM (SELECT Name, min(Time) as Time FROM %s_race WHERE Map = '%s' GROUP BY Name ORDER BY `Time` ASC) as a) as b WHERE Name = '%s';", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Name, Time " + "FROM (" + "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " + "FROM %s_race " + "WHERE Map = '%s' " + "GROUP BY Name " + "WINDOW w AS (ORDER BY Time)" + ") as a " + "WHERE Name = '%s';", + pSqlServer->GetPrefix(), + pData->m_Map.ClrStr(), + pData->m_Name.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->rowsCount() != 1) { - str_format(aBuf, sizeof(aBuf), "%s is not ranked", pData->m_Name.Str()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "%s is not ranked", pData->m_Name.Str()); } else { @@ -762,16 +852,20 @@ bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData int Rank = pSqlServer->GetResults()->getInt("Rank"); if(g_Config.m_SvHideScore) { - str_format(aBuf, sizeof(aBuf), "Your time: %02d:%05.2f", (int)(Time/60), Time-((int)Time/60*60)); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "Your time: %02d:%05.2f", (int)(Time/60), Time-((int)Time/60*60)); } else { - str_format(aBuf, sizeof(aBuf), "%d. %s Time: %02d:%05.2f, requested by %s", Rank, pSqlServer->GetResults()->getString("Name").c_str(), (int)(Time/60), Time-((int)Time/60*60), pData->m_aRequestingPlayer); - pData->GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, pData->m_ClientID); + pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL; + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "%d. %s Time: %02d:%05.2f, requested by %s", + Rank, pSqlServer->GetResults()->getString("Name").c_str(), + (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer.Str()); } } + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing rank done"); return true; } @@ -780,31 +874,22 @@ bool CSqlScore::ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show rank"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing rank due to reload/change of map."); - return true; - } return false; } -void CSqlScore::ShowTeamRank(int ClientID, const char* pName, bool Search) +void CSqlScore::ShowTeamRank(int ClientID, const char* pName) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = pName; - Tmp->m_Search = Search; - str_copy(Tmp->m_aRequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_aRequestingPlayer)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTeamRankThread, Tmp), "show team rank"); + ExecPlayerThread(ShowTeamRankThread, "show team rank", ClientID, pName, 0); } -bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); - + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); if (HandleFailure) + { + pData->m_pResult->m_Done = true; return true; + } try { @@ -813,10 +898,31 @@ bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGame char aNames[2300]; aNames[0] = '\0'; - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - str_format(aBuf, sizeof(aBuf), "SELECT Rank, Name, Time FROM (SELECT Rank, l2.ID FROM ((SELECT ID, (@pos := @pos+1) pos, (@rank := IF(@prev = Time,@rank,@pos)) rank, (@prev := Time) Time FROM (SELECT ID, Time FROM %s_teamrace WHERE Map = '%s' GROUP BY ID ORDER BY Time) as ll) as l2) LEFT JOIN %s_teamrace as r2 ON l2.ID = r2.ID WHERE Map = '%s' AND Name = '%s' ORDER BY Rank LIMIT 1) as l LEFT JOIN %s_teamrace as r ON l.ID = r.ID ORDER BY Name;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pSqlServer->GetPrefix()); + str_format(aBuf, sizeof(aBuf), + "SELECT Time, Rank, Name " + "FROM (" // teamrank score board + "SELECT RANK() OVER w AS Rank, Id " + "FROM %s_teamrace " + "WHERE Map = '%s' " + "GROUP BY Id " + "WINDOW w AS (ORDER BY Time)" + ") as l " + "INNER JOIN %s_teamrace as r ON l.ID = r.ID " + "WHERE l.ID = (" // find id for top teamrank of player + "SELECT Id " + "FROM %s_teamrace " + "WHERE Map = '%s' AND Name = '%s' " + "ORDER BY Time " + "LIMIT 1" + ") " + "ORDER BY Name;", + pSqlServer->GetPrefix(), + pData->m_Map.ClrStr(), + pSqlServer->GetPrefix(), + pSqlServer->GetPrefix(), + pData->m_Map.ClrStr(), + pData->m_Name.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); @@ -824,13 +930,12 @@ bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGame if(Rows < 1) { - str_format(aBuf, sizeof(aBuf), "%s has no team ranks", pData->m_Name.Str()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "%s has no team ranks", pData->m_Name.Str()); } else { pSqlServer->GetResults()->first(); - float Time = (float)pSqlServer->GetResults()->getDouble("Time"); int Rank = pSqlServer->GetResults()->getInt("Rank"); @@ -845,20 +950,21 @@ bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGame str_append(aNames, " & ", sizeof(aNames)); } - pSqlServer->GetResults()->first(); - if(g_Config.m_SvHideScore) { - str_format(aBuf, sizeof(aBuf), "Your team time: %02d:%05.02f", (int)(Time/60), Time-((int)Time/60*60)); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "Your team time: %02d:%05.02f", (int)(Time/60), Time-((int)Time/60*60)); } else { - str_format(aBuf, sizeof(aBuf), "%d. %s Team time: %02d:%05.02f, requested by %s", Rank, aNames, (int)(Time/60), Time-((int)Time/60*60), pData->m_aRequestingPlayer); - pData->GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, pData->m_ClientID); + pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL; + str_format(pData->m_pResult->m_aaMessages[0], sizeof(pData->m_pResult->m_aaMessages[0]), + "%d. %s Team time: %02d:%05.02f, requested by %s", + Rank, aNames, (int)(Time/60), Time-((int)Time/60*60), pData->m_RequestingPlayer.Str()); } } + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing teamrank done"); return true; } @@ -867,58 +973,63 @@ bool CSqlScore::ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGame dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show team rank"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing teamrank due to reload/change of map."); - return true; - } return false; } -void CSqlScore::ShowTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) +void CSqlScore::ShowTop5(int ClientID, int Offset) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_Num = Debut; - Tmp->m_ClientID = ClientID; - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTop5Thread, Tmp), "show top5"); + ExecPlayerThread(ShowTop5Thread, "show top5", ClientID, "", Offset); } -bool CSqlScore::ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); - + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); if (HandleFailure) return true; - int LimitStart = maximum(abs(pData->m_Num)-1, 0); - const char *pOrder = pData->m_Num >= 0 ? "ASC" : "DESC"; + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; try { // check sort method char aBuf[512]; - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - str_format(aBuf, sizeof(aBuf), "SELECT Name, Time, Rank FROM (SELECT Name, (@pos := @pos+1) pos, (@rank := IF(@prev = Time,@rank, @pos)) Rank, (@prev := Time) Time FROM (SELECT Name, min(Time) as Time FROM %s_race WHERE Map = '%s' GROUP BY Name ORDER BY `Time` ASC) as a) as b ORDER BY Rank %s LIMIT %d, 5;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart); + str_format(aBuf, sizeof(aBuf), + "SELECT Name, Time, Rank " + "FROM (" + "SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " + "FROM %s_race " + "WHERE Map = '%s' " + "GROUP BY Name " + "WINDOW w AS (ORDER BY Time)" + ") as a " + "ORDER BY Rank %s " + "LIMIT %d, 5;", + pSqlServer->GetPrefix(), + pData->m_Map.ClrStr(), + pOrder, + LimitStart + ); pSqlServer->executeSqlQuery(aBuf); // show top5 - pData->GameServer()->SendChatTarget(pData->m_ClientID, "----------- Top 5 -----------"); + strcpy(pData->m_pResult->m_aaMessages[0], "----------- Top 5 -----------"); - int Rank = 0; - float Time = 0; + int Line = 1; while(pSqlServer->GetResults()->next()) { - Time = (float)pSqlServer->GetResults()->getDouble("Time"); - Rank = (float)pSqlServer->GetResults()->getInt("Rank"); - str_format(aBuf, sizeof(aBuf), "%d. %s Time: %02d:%05.2f", Rank, pSqlServer->GetResults()->getString("Name").c_str(), (int)(Time/60), Time-((int)Time/60*60)); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - //Rank++; + float Time = (float)pSqlServer->GetResults()->getDouble("Time"); + int Rank = pSqlServer->GetResults()->getInt("Rank"); + str_format(pData->m_pResult->m_aaMessages[Line], sizeof(pData->m_pResult->m_aaMessages[0]), + "%d. %s Time: %02d:%05.2f", + Rank, pSqlServer->GetResults()->getString("Name").c_str(), + (int)(Time/60), Time-((int)Time/60*60) + ); + Line++; } - pData->GameServer()->SendChatTarget(pData->m_ClientID, "-------------------------------"); + strcpy(pData->m_pResult->m_aaMessages[Line], "-------------------------------"); + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing top5 done"); return true; } @@ -927,105 +1038,79 @@ bool CSqlScore::ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show top5"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing top5 due to reload/change of map."); - return true; - } + return false; } -void CSqlScore::ShowTeamTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) +void CSqlScore::ShowTeamTop5(int ClientID, int Offset) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_Num = Debut; - Tmp->m_ClientID = ClientID; - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTeamTop5Thread, Tmp), "show team top5"); + ExecPlayerThread(ShowTeamTop5Thread, "show team top5", ClientID, "", Offset); } -bool CSqlScore::ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); - + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; - int LimitStart = maximum(abs(pData->m_Num)-1, 0); - const char *pOrder = pData->m_Num >= 0 ? "ASC" : "DESC"; + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; try { // check sort method - char aBuf[2400]; + char aBuf[512]; - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @previd := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - str_format(aBuf, sizeof(aBuf), "SELECT ID, Name, Time, Rank FROM (SELECT r.ID, Name, Rank, l.Time FROM ((SELECT ID, Rank, Time FROM (SELECT ID, (@pos := IF(@previd = ID,@pos,@pos+1)) pos, (@previd := ID), (@rank := IF(@prev = Time,@rank,@pos)) Rank, (@prev := Time) Time FROM (SELECT ID, MIN(Time) as Time FROM %s_teamrace WHERE Map = '%s' GROUP BY ID ORDER BY `Time` ASC) as all_top_times) as a ORDER BY Rank %s LIMIT %d, 5) as l) LEFT JOIN %s_teamrace as r ON l.ID = r.ID ORDER BY Time ASC, r.ID, Name ASC) as a;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart, pSqlServer->GetPrefix()); + str_format(aBuf, sizeof(aBuf), + "SELECT Name, Time, Rank, TeamSize " + "FROM (" // limit to 5 + "SELECT Rank, ID, TeamSize " + "FROM (" // teamrank score board + "SELECT RANK() OVER w AS Rank, ID, COUNT(*) AS Teamsize " + "FROM %s_teamrace " + "WHERE Map = '%s' " + "GROUP BY Id " + "WINDOW w AS (ORDER BY Time)" + ") as l1 " + "ORDER BY Rank %s " + "LIMIT %d, 5" + ") as l2 " + "INNER JOIN %s_teamrace as r ON l2.ID = r.ID " + "ORDER BY Rank %s, r.ID, Name ASC;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart, pSqlServer->GetPrefix(), pOrder + ); pSqlServer->executeSqlQuery(aBuf); // show teamtop5 - pData->GameServer()->SendChatTarget(pData->m_ClientID, "------- Team Top 5 -------"); - - int Rows = pSqlServer->GetResults()->rowsCount(); - - if (Rows >= 1) + pSqlServer->GetResults()->first(); + strcpy(paMessages[0], "------- Team Top 5 -------"); + int Line; + for(Line = 1; Line < 6; Line++) // print { - char aID[17]; - char aID2[17]; - char aNames[2300]; - int Rank = 0; - float Time = 0; - int aCuts[320]; // 64 * 5 - int CutPos = 0; + if(pSqlServer->GetResults()->isAfterLast()) + break; + int TeamSize = pSqlServer->GetResults()->getInt("TeamSize"); + float Time = (float)pSqlServer->GetResults()->getDouble("Time"); + int Rank = pSqlServer->GetResults()->getInt("Rank"); - aNames[0] = '\0'; - aCuts[0] = -1; - - pSqlServer->GetResults()->first(); - strcpy(aID, pSqlServer->GetResults()->getString("ID").c_str()); - for(int Row = 0; Row < Rows; Row++) + char aNames[2300] = { 0 }; + for(int i = 0; i < TeamSize; i++) { - strcpy(aID2, pSqlServer->GetResults()->getString("ID").c_str()); - if (str_comp(aID, aID2) != 0) - { - strcpy(aID, aID2); - aCuts[CutPos++] = Row - 1; - } - pSqlServer->GetResults()->next(); - } - aCuts[CutPos] = Rows - 1; - - CutPos = 0; - pSqlServer->GetResults()->first(); - for(int Row = 0; Row < Rows; Row++) - { - str_append(aNames, pSqlServer->GetResults()->getString("Name").c_str(), sizeof(aNames)); - - if (Row < aCuts[CutPos] - 1) + auto Name = pSqlServer->GetResults()->getString("Name"); + str_append(aNames, Name.c_str(), sizeof(aNames)); + if (i < TeamSize - 2) str_append(aNames, ", ", sizeof(aNames)); - else if (Row < aCuts[CutPos]) + else if (i == TeamSize - 2) str_append(aNames, " & ", sizeof(aNames)); - - Time = (float)pSqlServer->GetResults()->getDouble("Time"); - Rank = (float)pSqlServer->GetResults()->getInt("Rank"); - - if (Row == aCuts[CutPos]) - { - str_format(aBuf, sizeof(aBuf), "%d. %s Team Time: %02d:%05.2f", Rank, aNames, (int)(Time/60), Time-((int)Time/60*60)); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - CutPos++; - aNames[0] = '\0'; - } - pSqlServer->GetResults()->next(); } + str_format(paMessages[Line], sizeof(paMessages[0]), "%d. %s Team Time: %02d:%05.2f", + Rank, aNames, (int)(Time/60), Time-((int)Time/60*60)); } + strcpy(paMessages[Line], "-------------------------------"); - pData->GameServer()->SendChatTarget(pData->m_ClientID, "-------------------------------"); - + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing teamtop5 done"); return true; } @@ -1034,96 +1119,107 @@ bool CSqlScore::ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGame dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show teamtop5"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing teamtop5 due to reload/change of map."); - return true; - } return false; } -void CSqlScore::ShowTimes(int ClientID, int Debut) +void CSqlScore::ShowTimes(int ClientID, int Offset) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_Num = Debut; - Tmp->m_ClientID = ClientID; - Tmp->m_Search = false; - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTimesThread, Tmp), "show times"); + ExecPlayerThread(ShowTimesThread, "show times", ClientID, "", Offset); } -void CSqlScore::ShowTimes(int ClientID, const char* pName, int Debut) +void CSqlScore::ShowTimes(int ClientID, const char* pName, int Offset) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_Num = Debut; - Tmp->m_ClientID = ClientID; - Tmp->m_Name = pName; - Tmp->m_Search = true; - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTimesThread, Tmp), "show name's times"); + ExecPlayerThread(ShowTimesThread, "show times", ClientID, pName, Offset); } -bool CSqlScore::ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; - int LimitStart = maximum(abs(pData->m_Num)-1, 0); - const char *pOrder = pData->m_Num >= 0 ? "DESC" : "ASC"; + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "DESC" : "ASC"; try { char aBuf[512]; - if(pData->m_Search) // last 5 times of a player - str_format(aBuf, sizeof(aBuf), "SELECT Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp FROM %s_race WHERE Map = '%s' AND Name = '%s' ORDER BY Timestamp %s LIMIT %d, 5;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pOrder, LimitStart); - else// last 5 times of server - str_format(aBuf, sizeof(aBuf), "SELECT Name, Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp FROM %s_race WHERE Map = '%s' ORDER BY Timestamp %s LIMIT %d, 5;", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart); - + if(pData->m_Name.Str()[0] != '\0') // last 5 times of a player + { + str_format(aBuf, sizeof(aBuf), + "SELECT Time, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, UNIX_TIMESTAMP(Timestamp) as Stamp " + "FROM %s_race " + "WHERE Map = '%s' AND Name = '%s' " + "ORDER BY Timestamp %s " + "LIMIT %d, 5;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pOrder, LimitStart + ); + } + else // last 5 times of server + { + str_format(aBuf, sizeof(aBuf), + "SELECT Name, Time, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago, " + "UNIX_TIMESTAMP(Timestamp) as Stamp " + "FROM %s_race " + "WHERE Map = '%s' " + "ORDER BY Timestamp %s " + "LIMIT %d, 5;", + pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pOrder, LimitStart + ); + } pSqlServer->executeSqlQuery(aBuf); // show top5 if(pSqlServer->GetResults()->rowsCount() == 0) { - pData->GameServer()->SendChatTarget(pData->m_ClientID, "There are no times in the specified range"); + strcpy(paMessages[0], "There are no times in the specified range"); + pData->m_pResult->m_Done = true; return true; } - pData->GameServer()->SendChatTarget(pData->m_ClientID, "------------- Last Times -------------"); - - float pTime = 0; - int pSince = 0; - int pStamp = 0; - + strcpy(paMessages[0], "------------- Last Times -------------"); + int Line = 1; while(pSqlServer->GetResults()->next()) { char aAgoString[40] = "\0"; - pSince = pSqlServer->GetResults()->getInt("Ago"); - pStamp = pSqlServer->GetResults()->getInt("Stamp"); - pTime = (float)pSqlServer->GetResults()->getDouble("Time"); + int pSince = pSqlServer->GetResults()->getInt("Ago"); + int pStamp = pSqlServer->GetResults()->getInt("Stamp"); + float pTime = (float)pSqlServer->GetResults()->getDouble("Time"); - sqlstr::AgoTimeToString(pSince,aAgoString); + sqlstr::AgoTimeToString(pSince, aAgoString); - if(pData->m_Search) // last 5 times of a player + if(pData->m_Name.Str()[0] != '\0') // last 5 times of a player { if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet - str_format(aBuf, sizeof(aBuf), "%02d:%05.02f, don't know how long ago", (int)(pTime/60), pTime-((int)pTime/60*60)); + str_format(paMessages[Line], sizeof(paMessages[0]), + "%02d:%05.02f, don't know how long ago", + (int)(pTime/60), pTime-((int)pTime/60*60)); else - str_format(aBuf, sizeof(aBuf), "%s ago, %02d:%05.02f", aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); + str_format(paMessages[Line], sizeof(paMessages[0]), + "%s ago, %02d:%05.02f", + aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); } else // last 5 times of the server { + auto Name = pSqlServer->GetResults()->getString("Name"); if(pStamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet - str_format(aBuf, sizeof(aBuf), "%s, %02d:%05.02f, don't know when", pSqlServer->GetResults()->getString("Name").c_str(), (int)(pTime/60), pTime-((int)pTime/60*60)); + str_format(paMessages[Line], sizeof(paMessages[0]), + "%s, %02d:%05.02f, don't know when", + Name.c_str(), (int)(pTime/60), pTime-((int)pTime/60*60)); else - str_format(aBuf, sizeof(aBuf), "%s, %s ago, %02d:%05.02f", pSqlServer->GetResults()->getString("Name").c_str(), aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); + str_format(paMessages[Line], sizeof(paMessages[0]), + "%s, %s ago, %02d:%05.02f", + Name.c_str(), aAgoString, (int)(pTime/60), pTime-((int)pTime/60*60)); } - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + Line++; } - pData->GameServer()->SendChatTarget(pData->m_ClientID, "----------------------------------------------------"); + strcpy(paMessages[Line], "----------------------------------------------------"); + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing times done"); return true; } @@ -1131,57 +1227,56 @@ bool CSqlScore::ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameDat { dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show times"); - return false; - } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing times due to reload/change of map."); - return true; } + return false; } -void CSqlScore::ShowPoints(int ClientID, const char* pName, bool Search) +void CSqlScore::ShowPoints(int ClientID, const char* pName) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = pName; - Tmp->m_Search = Search; - str_copy(Tmp->m_aRequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_aRequestingPlayer)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowPointsThread, Tmp), "show points"); + ExecPlayerThread(ShowPointsThread, "show points", ClientID, pName, 0); } -bool CSqlScore::ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; try { - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "SELECT Rank, Points, Name FROM (SELECT Name, (@pos := @pos+1) pos, (@rank := IF(@prev = Points, @rank, @pos)) Rank, (@prev := Points) Points FROM (SELECT Name, Points FROM %s_points GROUP BY Name ORDER BY Points DESC) as a) as b where Name = '%s';", pSqlServer->GetPrefix(), pData->m_Name.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Points, Name " + "FROM (" + "SELECT RANK() OVER w AS Rank, Points, Name " + "FROM %s_points " + "WINDOW w as (ORDER BY Points DESC)" + ") as a " + "WHERE Name = '%s';", + pSqlServer->GetPrefix(), pData->m_Name.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->rowsCount() != 1) { - str_format(aBuf, sizeof(aBuf), "%s has not collected any points so far", pData->m_Name.Str()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(paMessages[0], sizeof(paMessages[0]), + "%s has not collected any points so far", pData->m_Name.Str()); } else { pSqlServer->GetResults()->next(); - int count = pSqlServer->GetResults()->getInt("Points"); - int rank = pSqlServer->GetResults()->getInt("Rank"); - str_format(aBuf, sizeof(aBuf), "%d. %s Points: %d, requested by %s", rank, pSqlServer->GetResults()->getString("Name").c_str(), count, pData->m_aRequestingPlayer); - pData->GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, pData->m_ClientID); + int Count = pSqlServer->GetResults()->getInt("Points"); + int Rank = pSqlServer->GetResults()->getInt("Rank"); + auto Name = pSqlServer->GetResults()->getString("Name"); + pData->m_pResult->m_MessageKind = CSqlPlayerResult::ALL; + str_format(paMessages[0], sizeof(paMessages[0]), + "%d. %s Points: %d, requested by %s", + Rank, Name.c_str(), Count, pData->m_RequestingPlayer.Str()); } + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing points done"); return true; } @@ -1190,53 +1285,58 @@ bool CSqlScore::ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameDa dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show points"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted showing points due to reload/change of map."); - return true; - } return false; } -void CSqlScore::ShowTopPoints(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut) +void CSqlScore::ShowTopPoints(int ClientID, int Offset) { - CSqlScoreData *Tmp = new CSqlScoreData(); - Tmp->m_Num = Debut; - Tmp->m_ClientID = ClientID; - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(ShowTopPointsThread, Tmp), "show top points"); + ExecPlayerThread(ShowTopPointsThread, "show top points", ClientID, "", Offset); } -bool CSqlScore::ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlScoreData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; - int LimitStart = maximum(abs(pData->m_Num)-1, 0); - const char *pOrder = pData->m_Num >= 0 ? "ASC" : "DESC"; + int LimitStart = maximum(abs(pData->m_Offset)-1, 0); + const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; try { char aBuf[512]; - pSqlServer->executeSql("SET @prev := NULL;"); - pSqlServer->executeSql("SET @rank := 1;"); - pSqlServer->executeSql("SET @pos := 0;"); - str_format(aBuf, sizeof(aBuf), "SELECT Rank, Points, Name FROM (SELECT Name, (@pos := @pos+1) pos, (@rank := IF(@prev = Points,@rank, @pos)) Rank, (@prev := Points) Points FROM (SELECT Name, Points FROM %s_points GROUP BY Name ORDER BY Points DESC) as a) as b ORDER BY Rank %s LIMIT %d, 5;", pSqlServer->GetPrefix(), pOrder, LimitStart); + str_format(aBuf, sizeof(aBuf), + "SELECT Rank, Points, Name " + "FROM (" + "SELECT RANK() OVER w AS Rank, Points, Name " + "FROM %s_points " + "WINDOW w as (ORDER BY Points DESC)" + ") as a " + "ORDER BY Rank %s " + "LIMIT %d, 5;", + pSqlServer->GetPrefix(), pOrder, LimitStart + ); pSqlServer->executeSqlQuery(aBuf); // show top points - pData->GameServer()->SendChatTarget(pData->m_ClientID, "-------- Top Points --------"); + strcpy(paMessages[0], "-------- Top Points --------"); + int Line = 1; while(pSqlServer->GetResults()->next()) { - str_format(aBuf, sizeof(aBuf), "%d. %s Points: %d", pSqlServer->GetResults()->getInt("Rank"), pSqlServer->GetResults()->getString("Name").c_str(), pSqlServer->GetResults()->getInt("Points")); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + int Rank = pSqlServer->GetResults()->getInt("Rank"); + auto Name = pSqlServer->GetResults()->getString("Name"); + int Points = pSqlServer->GetResults()->getInt("Points"); + str_format(paMessages[Line], sizeof(paMessages[0]), + "%d. %s Points: %d", Rank, Name.c_str(), Points); + Line++; } - pData->GameServer()->SendChatTarget(pData->m_ClientID, "-------------------------------"); + strcpy(paMessages[Line], "-------------------------------"); + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing toppoints done"); return true; } @@ -1245,31 +1345,30 @@ bool CSqlScore::ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGam dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not show toppoints"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted toppoints-thread due to reload/change of map."); - return true; - } - return false; } -void CSqlScore::RandomMap(std::shared_ptr *ppResult, int ClientID, int Stars) +void CSqlScore::RandomMap(int ClientID, int Stars) { - *ppResult = std::make_shared(); + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + auto pResult = std::make_shared(); + pCurPlayer->m_SqlRandomMapResult = pResult; - CSqlRandomMap *Tmp = new CSqlRandomMap(); - Tmp->m_Num = Stars; - Tmp->m_ClientID = ClientID; - Tmp->m_Name = GameServer()->Server()->ClientName(ClientID); - Tmp->m_pResult = *ppResult; + auto *Tmp = new CSqlRandomMapRequest(pResult); + Tmp->m_Stars = Stars; + Tmp->m_CurrentMap = g_Config.m_SvMap; + Tmp->m_ServerType = g_Config.m_SvServerType; + Tmp->m_RequestingPlayer = GameServer()->Server()->ClientName(ClientID); - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(RandomMapThread, Tmp), "random map"); + thread_init_and_detach( + CSqlExecData::ExecSqlFunc, + new CSqlExecData(RandomMapThread, Tmp), + "random map"); } -bool CSqlScore::RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlRandomMap *pData = dynamic_cast(pGameData); + const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); if (HandleFailure) return true; @@ -1277,26 +1376,44 @@ bool CSqlScore::RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameDat try { char aBuf[512]; - if(pData->m_Num >= 0) - str_format(aBuf, sizeof(aBuf), "select * from %s_maps where Server = \"%s\" and Map != \"%s\" and Stars = \"%d\" order by RAND() limit 1;", pSqlServer->GetPrefix(), g_Config.m_SvServerType, g_Config.m_SvMap, pData->m_Num); + if(0 <= pData->m_Stars && pData->m_Stars <= 5) + { + str_format(aBuf, sizeof(aBuf), + "SELECT * FROM %s_maps " + "WHERE Server = \"%s\" AND Map != \"%s\" AND Stars = \"%d\" " + "ORDER BY RAND() LIMIT 1;", + pSqlServer->GetPrefix(), + pData->m_ServerType.ClrStr(), + pData->m_CurrentMap.ClrStr(), + pData->m_Stars + ); + } else - str_format(aBuf, sizeof(aBuf), "select * from %s_maps where Server = \"%s\" and Map != \"%s\" order by RAND() limit 1;", pSqlServer->GetPrefix(), g_Config.m_SvServerType, g_Config.m_SvMap); + { + str_format(aBuf, sizeof(aBuf), + "SELECT * FROM %s_maps " + "WHERE Server = \"%s\" AND Map != \"%s\" " + "ORDER BY RAND() LIMIT 1;", + pSqlServer->GetPrefix(), + pData->m_ServerType.ClrStr(), + pData->m_CurrentMap.ClrStr() + ); + } pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->rowsCount() != 1) { - pData->GameServer()->SendChatTarget(pData->m_ClientID, "No maps found on this server!"); - pData->GameServer()->m_LastMapVote = 0; + str_copy(pData->m_pResult->m_aMessage, "No maps found on this server!", sizeof(pData->m_pResult->m_aMessage)); } else { pSqlServer->GetResults()->next(); - std::string Map = pSqlServer->GetResults()->getString("Map"); - str_copy(pData->m_pResult->m_aMap, Map.c_str(), sizeof(pData->m_pResult->m_aMap)); - pData->m_pResult->m_Done = true; + auto Map = pSqlServer->GetResults()->getString("Map"); + str_copy(pData->m_pResult->m_Map, Map.c_str(), sizeof(pData->m_pResult->m_Map)); } dbg_msg("sql", "voting random map done"); + pData->m_pResult->m_Done = true; return true; } catch (sql::SQLException &e) @@ -1304,30 +1421,30 @@ bool CSqlScore::RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameDat dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not vote random map"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted random-map-thread due to reload/change of map."); - return true; - } return false; } -void CSqlScore::RandomUnfinishedMap(std::shared_ptr *ppResult, int ClientID, int Stars) +void CSqlScore::RandomUnfinishedMap(int ClientID, int Stars) { - *ppResult = std::make_shared(); + CPlayer *pCurPlayer = GameServer()->m_apPlayers[ClientID]; + auto pResult = std::make_shared(); + pCurPlayer->m_SqlRandomMapResult = pResult; - CSqlRandomMap *Tmp = new CSqlRandomMap(); - Tmp->m_Num = Stars; - Tmp->m_ClientID = ClientID; - Tmp->m_Name = GameServer()->Server()->ClientName(ClientID); - Tmp->m_pResult = *ppResult; + auto *Tmp = new CSqlRandomMapRequest(pResult); + Tmp->m_Stars = Stars; + Tmp->m_CurrentMap = g_Config.m_SvMap; + Tmp->m_ServerType = g_Config.m_SvServerType; + Tmp->m_RequestingPlayer = GameServer()->Server()->ClientName(ClientID); - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(RandomUnfinishedMapThread, Tmp), "random unfinished map"); + thread_init_and_detach( + CSqlExecData::ExecSqlFunc, + new CSqlExecData(RandomUnfinishedMapThread, Tmp), + "random unfinished map"); } -bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlRandomMap *pData = dynamic_cast(pGameData); + const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); if (HandleFailure) return true; @@ -1335,25 +1452,48 @@ bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData try { char aBuf[512]; - if(pData->m_Num >= 0) - str_format(aBuf, sizeof(aBuf), "select * from %s_maps where Server = \"%s\" and Map != \"%s\" and Stars = \"%d\" and not exists (select * from %s_race where Name = \"%s\" and %s_race.Map = %s_maps.Map) order by RAND() limit 1;", pSqlServer->GetPrefix(), g_Config.m_SvServerType, g_Config.m_SvMap, pData->m_Num, pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + if(pData->m_Stars >= 0) + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map " + "FROM %s_maps " + "WHERE Server = \"%s\" AND Map != \"%s\" AND Stars = \"%d\" AND Map NOT IN (" + "SELECT Map " + "FROM %s_race " + "WHERE Name = \"%s\"" + ") ORDER BY RAND() " + "LIMIT 1;", + pSqlServer->GetPrefix(), pData->m_ServerType.ClrStr(), pData->m_CurrentMap.ClrStr(), + pData->m_Stars, pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); + } else - str_format(aBuf, sizeof(aBuf), "select * from %s_maps where Server = \"%s\" and Map != \"%s\" and not exists (select * from %s_race where Name = \"%s\" and %s_race.Map = %s_maps.Map) order by RAND() limit 1;", pSqlServer->GetPrefix(), g_Config.m_SvServerType, g_Config.m_SvMap, pSqlServer->GetPrefix(), pData->m_Name.ClrStr(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); + { + str_format(aBuf, sizeof(aBuf), + "SELECT Map " + "FROM %s_maps AS maps " + "WHERE Server = \"%s\" AND Map != \"%s\" AND Map NOT IN (" + "SELECT Map " + "FROM %s_race as race " + "WHERE Name = \"%s\"" + ") ORDER BY RAND() " + "LIMIT 1;", + pSqlServer->GetPrefix(), pData->m_ServerType.ClrStr(), pData->m_CurrentMap.ClrStr(), + pSqlServer->GetPrefix(), pData->m_RequestingPlayer.ClrStr()); + } pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->rowsCount() != 1) { - pData->GameServer()->SendChatTarget(pData->m_ClientID, "You have no more unfinished maps on this server!"); - pData->GameServer()->m_LastMapVote = 0; + str_copy(pData->m_pResult->m_aMessage, "You have no more unfinished maps on this server!", sizeof(pData->m_pResult->m_aMessage)); } else { pSqlServer->GetResults()->next(); - std::string Map = pSqlServer->GetResults()->getString("Map"); - str_copy(pData->m_pResult->m_aMap, Map.c_str(), sizeof(pData->m_pResult->m_aMap)); - pData->m_pResult->m_Done = true; + auto Map = pSqlServer->GetResults()->getString("Map"); + str_copy(pData->m_pResult->m_Map, Map.c_str(), sizeof(pData->m_pResult->m_Map)); } + pData->m_pResult->m_Done = true; dbg_msg("sql", "voting random unfinished map done"); return true; } @@ -1362,141 +1502,170 @@ bool CSqlScore::RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not vote random unfinished map"); } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted unfinished-map-thread due to reload/change of map."); - return true; - } return false; } -void CSqlScore::SaveTeam(int Team, const char* Code, int ClientID, const char* Server) +void CSqlScore::SaveTeam(int ClientID, const char* Code, const char* Server) { - if((g_Config.m_SvTeam == 3 || (Team > 0 && Team < MAX_CLIENTS)) && ((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.Count(Team) > 0) - { - if(((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.GetSaving(Team)) - return; - ((CGameControllerDDRace*)(GameServer()->m_pController))->m_Teams.SetSaving(Team, true); - } - else - { - GameServer()->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); + auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); + int Team = pController->m_Teams.m_Core.Team(ClientID); + if(pController->m_Teams.GetSaving(Team)) return; - } - CSqlTeamSave *Tmp = new CSqlTeamSave(); - Tmp->m_Team = Team; - Tmp->m_ClientID = ClientID; - Tmp->m_Code = Code; + auto SaveResult = std::make_shared(ClientID, pController); + int Result = SaveResult->m_SavedTeam.save(Team); + if(CSaveTeam::HandleSaveError(Result, ClientID, GameServer())) + return; + pController->m_Teams.SetSaving(Team, SaveResult); + + CSqlTeamSave *Tmp = new CSqlTeamSave(SaveResult); + str_copy(Tmp->m_Code, Code, sizeof(Tmp->m_Code)); + str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + Tmp->m_pResult->m_SaveID = RandomUuid(); str_copy(Tmp->m_Server, Server, sizeof(Tmp->m_Server)); - str_copy(Tmp->m_ClientName, this->Server()->ClientName(Tmp->m_ClientID), sizeof(Tmp->m_ClientName)); + str_copy(Tmp->m_ClientName, this->Server()->ClientName(ClientID), sizeof(Tmp->m_ClientName)); + Tmp->m_aGeneratedCode[0] = '\0'; + GeneratePassphrase(Tmp->m_aGeneratedCode, sizeof(Tmp->m_aGeneratedCode)); - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveTeamThread, Tmp, false), "save team"); + pController->m_Teams.KillSavedTeam(ClientID, Team); + char aBuf[512]; + // TODO: better message, maybe hint that one should wait until the next message to leave + str_format(aBuf, sizeof(aBuf), "Saving team initiated by '%s'", this->Server()->ClientName(ClientID)); + GameServer()->SendChatTeam(Team, aBuf); + thread_init_and_detach( + CSqlExecData::ExecSqlFunc, + new CSqlExecData(SaveTeamThread, Tmp, false), + "save team"); } -bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { const CSqlTeamSave *pData = dynamic_cast(pGameData); - try + char aSaveID[UUID_MAXSTRSIZE]; + FormatUuid(pData->m_pResult->m_SaveID, aSaveID, UUID_MAXSTRSIZE); + + char *pSaveState = pData->m_pResult->m_SavedTeam.GetString(); + if(HandleFailure) { - int Team = pData->m_Team; - - char TeamString[65536]; - - int Num = -1; - - if((g_Config.m_SvTeam == 3 || (Team > 0 && Team < MAX_CLIENTS)) && ((CGameControllerDDRace*)(pData->GameServer()->m_pController))->m_Teams.Count(Team) > 0) - { - CSaveTeam SavedTeam(pData->GameServer()->m_pController); - Num = SavedTeam.save(Team); - if(CSaveTeam::HandleSaveError(Num, pData->m_ClientID, pData->GameServer())) - return true; - - if(!Num) - { - str_copy(TeamString, SavedTeam.GetString(), sizeof(TeamString)); - sqlstr::ClearString(TeamString, sizeof(TeamString)); - } - } - else - pData->GameServer()->SendChatTarget(pData->m_ClientID, "You have to be in a team (from 1-63)"); - - if (Num) + if (!g_Config.m_SvSqlFailureFile[0]) return true; - if (HandleFailure) + lock_wait(ms_FailureFileLock); + IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); + if(File) { - if (!g_Config.m_SvSqlFailureFile[0]) - return true; + dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now..."); + sqlstr::CSqlString<65536> SaveState = pSaveState; + sqlstr::CSqlString<128> Code = pData->m_aGeneratedCode; + sqlstr::CSqlString<128> Map = pData->m_Map; - lock_wait(ms_FailureFileLock); - IOHANDLE File = io_open(g_Config.m_SvSqlFailureFile, IOFLAG_APPEND); - if(File) - { - dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now..."); - - char aBuf[65536]; - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, DDNet7) VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', false);", TeamString, pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), pData->m_Server); - io_write(File, aBuf, str_length(aBuf)); - io_write_newline(File); - io_close(File); - lock_unlock(ms_FailureFileLock); - - pData->GameServer()->SendBroadcast("Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days.", -1); - - return true; - } + char aBuf[65536]; + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " + "VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', '%s', false)", + SaveState.ClrStr(), Map.ClrStr(), + Code.ClrStr(), pData->m_Server, aSaveID + ); + io_write(File, aBuf, str_length(aBuf)); + io_write_newline(File); + io_close(File); lock_unlock(ms_FailureFileLock); - dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file"); - return false; + + pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS; + strcpy(pData->m_pResult->m_aBroadcast, + "Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days."); + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. Use '/load %s' to continue", + pData->m_ClientName, Code.Str()); + return true; + } + lock_unlock(ms_FailureFileLock); + dbg_msg("sql", "ERROR: Could not save Teamsave, NOT even to a file"); + return false; + } + + try + { + char aBuf[65536]; + str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix()); + pSqlServer->executeSql(aBuf); + + char Code[128] = {0}; + str_format(aBuf, sizeof(aBuf), "SELECT Savegame FROM %s_saves WHERE Code = ? AND Map = ?", pSqlServer->GetPrefix()); + std::unique_ptr pPrepStmt; + std::unique_ptr pResult; + pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf)); + bool UseCode = false; + if(pData->m_Code[0] != '\0') + { + pPrepStmt->setString(1, pData->m_Code); + pPrepStmt->setString(2, pData->m_Map); + pResult.reset(pPrepStmt->executeQuery()); + if(pResult->rowsCount() == 0) + { + UseCode = true; + str_copy(Code, pData->m_Code, sizeof(Code)); + } + } + if(!UseCode) + { + // use random generated passphrase if save code exists or no save code given + pPrepStmt->setString(1, pData->m_aGeneratedCode); + pPrepStmt->setString(2, pData->m_Map); + pResult.reset(pPrepStmt->executeQuery()); + if(pResult->rowsCount() == 0) + { + UseCode = true; + str_copy(Code, pData->m_aGeneratedCode, sizeof(Code)); + } } - try + if(UseCode) { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix()); - pSqlServer->executeSql(aBuf); - str_format(aBuf, sizeof(aBuf), "select Savegame from %s_saves where Code = '%s' and Map = '%s';", pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr()); - pSqlServer->executeSqlQuery(aBuf); + str_format(aBuf, sizeof(aBuf), + "INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " + "VALUES (?, ?, ?, CURRENT_TIMESTAMP(), ?, ?, false)", + pSqlServer->GetPrefix()); + pPrepStmt.reset(pSqlServer->Connection()->prepareStatement(aBuf)); + pPrepStmt->setString(1, pSaveState); + pPrepStmt->setString(2, pData->m_Map); + pPrepStmt->setString(3, Code); + pPrepStmt->setString(4, pData->m_Server); + pPrepStmt->setString(5, aSaveID); + dbg_msg("sql", "%s", aBuf); + pPrepStmt->execute(); - if (pSqlServer->GetResults()->rowsCount() == 0) + if(str_comp(pData->m_Server, g_Config.m_SvSqlServerName) == 0) { - char aBuf[65536]; - str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_saves(Savegame, Map, Code, Timestamp, Server, DDNet7) VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s', false)", pSqlServer->GetPrefix(), TeamString, pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), pData->m_Server); - dbg_msg("sql", "%s", aBuf); - pSqlServer->executeSql(aBuf); - - // be sure to keep all calls to pData->GameServer() after inserting the save, otherwise it might be lost due to CGameContextError. - - char aBuf2[512]; - str_format(aBuf2, sizeof(aBuf2), "Team successfully saved by %s. Use '/load %s' to continue", pData->m_ClientName, pData->m_Code.Str()); - pData->GameServer()->SendChatTeam(Team, aBuf2); - ((CGameControllerDDRace*)(pData->GameServer()->m_pController))->m_Teams.KillSavedTeam(Team); + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. Use '/load %s' to continue", + pData->m_ClientName, Code); } else { - dbg_msg("sql", "ERROR: This save-code already exists"); - pData->GameServer()->SendChatTarget(pData->m_ClientID, "This save-code already exists"); + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "Team successfully saved by %s. Use '/load %s' on %s to continue", + pData->m_ClientName, Code, pData->m_Server); } - + pData->m_pResult->m_Status = CSqlSaveResult::SAVE_SUCCESS; } - catch (sql::SQLException &e) + else { - dbg_msg("sql", "MySQL Error: %s", e.what()); - dbg_msg("sql", "ERROR: Could not save the team"); - pData->GameServer()->SendChatTarget(pData->m_ClientID, "MySQL Error: Could not save the team"); - pSqlServer->executeSql("unlock tables;"); - return false; - } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Could not send chatmessage during saving team due to reload/change of map."); + dbg_msg("sql", "ERROR: This save-code already exists"); + pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED; + strcpy(pData->m_pResult->m_aMessage, "This save-code already exists"); } } - catch (CGameContextError &e) + catch (sql::SQLException &e) { - dbg_msg("sql", "WARNING: Aborted saving team due to reload/change of map."); + pData->m_pResult->m_Status = CSqlSaveResult::SAVE_FAILED; + dbg_msg("sql", "MySQL Error: %s", e.what()); + dbg_msg("sql", "ERROR: Could not save the team"); + + strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not save the team"); + pSqlServer->executeSql("unlock tables;"); + return false; } pSqlServer->executeSql("unlock tables;"); @@ -1505,148 +1674,157 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData void CSqlScore::LoadTeam(const char* Code, int ClientID) { - CSqlTeamLoad *Tmp = new CSqlTeamLoad(); + auto pController = ((CGameControllerDDRace*)(GameServer()->m_pController)); + int Team = pController->m_Teams.m_Core.Team(ClientID); + if(pController->m_Teams.GetSaving(Team)) + return; + if(Team <= 0 || Team >= MAX_CLIENTS) + { + GameServer()->SendChatTarget(ClientID, "You have to be in a team (from 1-63)"); + return; + } + if(pController->m_Teams.GetTeamState(Team) != CGameTeams::TEAMSTATE_OPEN) + { + GameServer()->SendChatTarget(ClientID, "Team can't be loaded while racing"); + return; + } + auto SaveResult = std::make_shared(ClientID, pController); + pController->m_Teams.SetSaving(Team, SaveResult); + CSqlTeamLoad *Tmp = new CSqlTeamLoad(SaveResult); Tmp->m_Code = Code; + Tmp->m_Map = g_Config.m_SvMap; Tmp->m_ClientID = ClientID; - str_copy(Tmp->m_ClientName, Server()->ClientName(Tmp->m_ClientID), sizeof(Tmp->m_ClientName)); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(LoadTeamThread, Tmp), "load team"); + Tmp->m_RequestingPlayer = Server()->ClientName(ClientID); + Tmp->m_NumPlayer = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(pController->m_Teams.m_Core.Team(i) == Team) + { + // put all names at the beginning of the array + str_copy(Tmp->m_aClientNames[Tmp->m_NumPlayer], Server()->ClientName(i), sizeof(Tmp->m_aClientNames[0])); + Tmp->m_aClientID[Tmp->m_NumPlayer] = i; + Tmp->m_NumPlayer++; + } + } + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "Loading team initiated by '%s'", this->Server()->ClientName(ClientID)); + GameServer()->SendChatTeam(Team, aBuf); + thread_init_and_detach( + CSqlExecData::ExecSqlFunc, + new CSqlExecData(LoadTeamThread, Tmp, false), + "load team"); } -bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { const CSqlTeamLoad *pData = dynamic_cast(pGameData); + pData->m_pResult->m_Status = CSqlSaveResult::LOAD_FAILED; if (HandleFailure) return true; try { - char aBuf[768]; + char aBuf[512]; str_format(aBuf, sizeof(aBuf), "lock tables %s_saves write;", pSqlServer->GetPrefix()); pSqlServer->executeSql(aBuf); - str_format(aBuf, sizeof(aBuf), "select Savegame, Server, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) as Ago from %s_saves where Code = '%s' and Map = '%s' and DDNet7 = false;", pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT " + "Savegame, Server, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(Timestamp) AS Ago, " + "(UNHEX(REPLACE(SaveID, '-',''))) AS SaveID " + "FROM %s_saves " + "where Code = '%s' AND Map = '%s' AND DDNet7 = false AND Savegame LIKE '%%\\n%s\\t%%';", + pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr(), pData->m_RequestingPlayer.ClrStr()); pSqlServer->executeSqlQuery(aBuf); - if (pSqlServer->GetResults()->rowsCount() > 0) + if(pSqlServer->GetResults()->rowsCount() == 0) { - pSqlServer->GetResults()->first(); - char ServerName[5]; - str_copy(ServerName, pSqlServer->GetResults()->getString("Server").c_str(), sizeof(ServerName)); - if(str_comp(ServerName, g_Config.m_SvSqlServerName)) - { - str_format(aBuf, sizeof(aBuf), "You have to be on the '%s' server to load this savegame", ServerName); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - goto end; - } + strcpy(pData->m_pResult->m_aMessage, "No such savegame for this map"); + goto end; + } + pSqlServer->GetResults()->first(); + auto ServerName = pSqlServer->GetResults()->getString("Server"); + if(str_comp(ServerName.c_str(), g_Config.m_SvSqlServerName) != 0) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "You have to be on the '%s' server to load this savegame", ServerName.c_str()); + goto end; + } - pSqlServer->GetResults()->getInt("Ago"); - int since = pSqlServer->GetResults()->getInt("Ago"); - - if(since < g_Config.m_SvSaveGamesDelay) - { - str_format(aBuf, sizeof(aBuf), "You have to wait %d seconds until you can load this savegame", g_Config.m_SvSaveGamesDelay - since); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - goto end; - } - - CSaveTeam SavedTeam(pData->GameServer()->m_pController); - - int Num = SavedTeam.LoadString(pSqlServer->GetResults()->getString("Savegame").c_str()); - - if(Num) - pData->GameServer()->SendChatTarget(pData->m_ClientID, "Unable to load savegame: data corrupted"); - else - { - bool Found = false; - for (int i = 0; i < SavedTeam.GetMembersCount(); i++) - { - if(str_comp(SavedTeam.m_pSavedTees[i].GetName(), pData->Server()->ClientName(pData->m_ClientID)) == 0) - { - Found = true; - break; - } - } - if(!Found) - pData->GameServer()->SendChatTarget(pData->m_ClientID, "You don't belong to this team"); - else - { - int Team = ((CGameControllerDDRace*)(pData->GameServer()->m_pController))->m_Teams.m_Core.Team(pData->m_ClientID); - - Num = SavedTeam.load(Team); - - if(Num == 1) - { - pData->GameServer()->SendChatTarget(pData->m_ClientID, "You have to be in a team (from 1-63)"); - } - else if(Num == 2) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Too many players in this team, should be %d", SavedTeam.GetMembersCount()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else if(Num >= 10 && Num < 100) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Unable to find player: '%s'", SavedTeam.m_pSavedTees[Num-10].GetName()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else if(Num >= 100 && Num < 200) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "%s is racing right now, Team can't be loaded if a Tee is racing already", SavedTeam.m_pSavedTees[Num-100].GetName()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else if(Num >= 200) - { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Everyone has to be in a team, %s is in team 0 or the wrong team", SavedTeam.m_pSavedTees[Num-200].GetName()); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); - } - else - { - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "Loading successfully done by %s", pData->m_ClientName); - pData->GameServer()->SendChatTeam(Team, aBuf); - str_format(aBuf, sizeof(aBuf), "DELETE from %s_saves where Code='%s' and Map='%s';", pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr()); - pSqlServer->executeSql(aBuf); - } - } - } + int Since = pSqlServer->GetResults()->getInt("Ago"); + if(Since < g_Config.m_SvSaveGamesDelay) + { + str_format(pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage), + "You have to wait %d seconds until you can load this savegame", + g_Config.m_SvSaveGamesDelay - Since); + goto end; + } + if(pSqlServer->GetResults()->isNull("SaveID")) + { + memset(pData->m_pResult->m_SaveID.m_aData, 0, sizeof(pData->m_pResult->m_SaveID.m_aData)); } else - pData->GameServer()->SendChatTarget(pData->m_ClientID, "No such savegame for this map"); + { + auto SaveID = pSqlServer->GetResults()->getBlob("SaveID"); + SaveID->read((char *) pData->m_pResult->m_SaveID.m_aData, 16); + if(SaveID->gcount() != 16) + { + strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: SaveID corrupted"); + goto end; + } + } + + auto SaveString = pSqlServer->GetResults()->getString("Savegame"); + int Num = pData->m_pResult->m_SavedTeam.LoadString(SaveString.c_str()); + + if(Num != 0) + { + strcpy(pData->m_pResult->m_aMessage, "Unable to load savegame: data corrupted"); + goto end; + } + + bool CanLoad = pData->m_pResult->m_SavedTeam.MatchPlayers( + pData->m_aClientNames, pData->m_aClientID, pData->m_NumPlayer, + pData->m_pResult->m_aMessage, sizeof(pData->m_pResult->m_aMessage)); + + if(!CanLoad) + goto end; + + str_format(aBuf, sizeof(aBuf), + "DELETE FROM %s_saves " + "WHERE Code='%s' AND Map='%s';", + pSqlServer->GetPrefix(), pData->m_Code.ClrStr(), pData->m_Map.ClrStr()); + pSqlServer->executeSql(aBuf); + + pData->m_pResult->m_Status = CSqlSaveResult::LOAD_SUCCESS; + strcpy(pData->m_pResult->m_aMessage, "Loading successfully done"); - end: - pSqlServer->executeSql("unlock tables;"); - return true; } catch (sql::SQLException &e) { dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not load the team"); - pData->GameServer()->SendChatTarget(pData->m_ClientID, "MySQL Error: Could not load the team"); + strcpy(pData->m_pResult->m_aMessage, "MySQL Error: Could not load the team"); + pSqlServer->executeSql("unlock tables;"); + + return false; } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted loading team due to reload/change of map."); - return true; - } - return false; + +end: + pSqlServer->executeSql("unlock tables;"); + return true; } void CSqlScore::GetSaves(int ClientID) { - CSqlGetSavesData *Tmp = new CSqlGetSavesData(); - Tmp->m_ClientID = ClientID; - Tmp->m_Name = Server()->ClientName(ClientID); - - thread_init_and_detach(ExecSqlFunc, new CSqlExecData(GetSavesThread, Tmp, false), "get saves"); + ExecPlayerThread(GetSavesThread, "get saves", ClientID, "", 0); } -bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) +bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure) { - const CSqlGetSavesData *pData = dynamic_cast(pGameData); + const CSqlPlayerRequest *pData = dynamic_cast(pGameData); + auto paMessages = pData->m_pResult->m_aaMessages; if (HandleFailure) return true; @@ -1655,7 +1833,15 @@ bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData { char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "SELECT count(*) as NumSaves, UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(max(Timestamp)) as Ago FROM %s_saves WHERE Map='%s' AND Savegame regexp '\\n%s\\t';", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr()); + str_format(aBuf, sizeof(aBuf), + "SELECT COUNT(*) as NumSaves, " + "UNIX_TIMESTAMP(CURRENT_TIMESTAMP)-UNIX_TIMESTAMP(max(Timestamp)) as Ago " + "FROM %s_saves " + "WHERE Map='%s' AND Savegame LIKE '%%\\n%s\\t%%';", + pSqlServer->GetPrefix(), + pData->m_Map.ClrStr(), + pData->m_RequestingPlayer.ClrStr() + ); pSqlServer->executeSqlQuery(aBuf); if(pSqlServer->GetResults()->next()) { @@ -1670,10 +1856,14 @@ bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData str_format(aLastSavedString, sizeof(aLastSavedString), ", last saved %s ago", aAgoString); } - str_format(aBuf, sizeof(aBuf), "%s has %d save%s on %s%s", pData->m_Name.Str(), NumSaves, NumSaves == 1 ? "" : "s", pData->m_Map.Str(), aLastSavedString); - pData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf); + str_format(paMessages[0], sizeof(paMessages[0]), + "%s has %d save%s on %s%s", + pData->m_RequestingPlayer.Str(), + NumSaves, NumSaves == 1 ? "" : "s", + pData->m_Map.Str(), aLastSavedString); } + pData->m_pResult->m_Done = true; dbg_msg("sql", "Showing saves done"); return true; } @@ -1681,12 +1871,6 @@ bool CSqlScore::GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData { dbg_msg("sql", "MySQL Error: %s", e.what()); dbg_msg("sql", "ERROR: Could not get saves"); - pData->GameServer()->SendChatTarget(pData->m_ClientID, "MySQL Error: Could not get saves"); - } - catch (CGameContextError &e) - { - dbg_msg("sql", "WARNING: Aborted getting saves due to reload/change of map."); - return true; } return false; } diff --git a/src/game/server/score/sql_score.h b/src/game/server/score/sql_score.h index 2f0d98fd5..183d3e2be 100644 --- a/src/game/server/score/sql_score.h +++ b/src/game/server/score/sql_score.h @@ -1,113 +1,146 @@ /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ /* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ /* CSqlScore Class by Sushi Tee*/ -#ifndef GAME_SERVER_SCORE_SQL_SCORE_H -#define GAME_SERVER_SCORE_SQL_SCORE_H +#ifndef GAME_SERVER_SCORE_SQL_H +#define GAME_SERVER_SCORE_SQL_H -#include - -#include -#include -#include #include +#include +#include #include "../score.h" +class CSqlServer; -class CGameContextError : public std::runtime_error +struct CSqlPlayerResult { -public: - CGameContextError(const char* pMsg) : std::runtime_error(pMsg) {} + std::atomic_bool m_Done; + CSqlPlayerResult(); + + enum Variant + { + DIRECT, + ALL, + BROADCAST, + MAP_VOTE, + PLAYER_INFO, + } m_MessageKind; + char m_aaMessages[7][512]; + union { + char m_Broadcast[1024]; + struct { + float m_Time; + float m_CpTime[NUM_CHECKPOINTS]; + int m_Score; + int m_HasFinishScore; + int m_Birthday; // 0 indicates no birthday + } m_Info; + struct + { + char m_Reason[VOTE_REASON_LENGTH]; + char m_Server[32+1]; + char m_Map[128+1]; + } m_MapVote; + } m_Data; // PLAYER_INFO + + void SetVariant(Variant v); }; +struct CSqlRandomMapResult +{ + std::atomic_bool m_Done; + CSqlRandomMapResult() : + m_Done(false) + { + m_Map[0] = '\0'; + m_aMessage[0] = '\0'; + } + char m_Map[128]; + char m_aMessage[512]; +}; -// generic implementation to provide gameserver and server +struct CSqlSaveResult { + CSqlSaveResult(int PlayerID, IGameController* Controller) : + m_Status(SAVE_FAILED), + m_SavedTeam(CSaveTeam(Controller)), + m_RequestingPlayer(PlayerID) + { + m_aMessage[0] = '\0'; + m_aBroadcast[0] = '\0'; + } + enum + { + SAVE_SUCCESS, + // load team in the following two cases + SAVE_FAILED, + LOAD_SUCCESS, + LOAD_FAILED, + } m_Status; + char m_aMessage[512]; + char m_aBroadcast[512]; + CSaveTeam m_SavedTeam; + int m_RequestingPlayer; + CUuid m_SaveID; +}; + +struct CSqlInitResult +{ + CSqlInitResult() : + m_Done(false), + m_CurrentRecord(0) + { } + std::atomic_bool m_Done; + float m_CurrentRecord; +}; + +// holding relevant data for one thread, and function pointer for return values +template < typename TResult > struct CSqlData { - CSqlData() : m_Map(ms_pMap), m_GameUuid(ms_pGameUuid) - { - m_Instance = ms_Instance; - } - - virtual ~CSqlData() {} - - bool isGameContextVaild() const - { - return m_Instance == ms_Instance && ms_GameContextAvailable; - } - - CGameContext* GameServer() const { return isGameContextVaild() ? ms_pGameServer : throw CGameContextError("[CSqlData]: GameServer() unavailable."); } - IServer* Server() const { return isGameContextVaild() ? ms_pServer : throw CGameContextError("[CSqlData]: Server() unavailable."); } - CPlayerData* PlayerData(int ID) const { return isGameContextVaild() ? &ms_pPlayerData[ID] : throw CGameContextError("[CSqlData]: PlayerData() unavailable."); } + CSqlData(std::shared_ptr pSqlResult) : + m_pResult(pSqlResult) + { } + std::shared_ptr m_pResult; + virtual ~CSqlData() = default; +}; +struct CSqlInitData : CSqlData +{ + using CSqlData::CSqlData; + // current map sqlstr::CSqlString<128> m_Map; - sqlstr::CSqlString m_GameUuid; - - // counter to keep track to which instance of GameServer this object belongs to. - int m_Instance; - - static CGameContext *ms_pGameServer; - static IServer *ms_pServer; - static CPlayerData *ms_pPlayerData; - static const char *ms_pMap; - static const char *ms_pGameUuid; - - static bool ms_GameContextAvailable; - // contains the instancecount of the current GameServer - static int ms_Instance; }; -struct CSqlExecData +struct CSqlPlayerRequest : CSqlData { - CSqlExecData(bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), CSqlData *pSqlData, bool ReadOnly = true) : - m_pFuncPtr(pFuncPtr), - m_pSqlData(pSqlData), - m_ReadOnly(ReadOnly) - { - ++ms_InstanceCount; - } - ~CSqlExecData() - { - --ms_InstanceCount; - } - - bool (*m_pFuncPtr) (CSqlServer*, const CSqlData *, bool); - CSqlData *m_pSqlData; - bool m_ReadOnly; - - // keeps track of score-threads - volatile static int ms_InstanceCount; + using CSqlData::CSqlData; + // object being requested, either map (128 bytes) or player (16 bytes) + sqlstr::CSqlString<128> m_Name; + // current map + sqlstr::CSqlString<128> m_Map; + sqlstr::CSqlString m_RequestingPlayer; + // relevant for /top5 kind of requests + int m_Offset; }; -struct CSqlPlayerData : CSqlData +struct CSqlRandomMapRequest : CSqlData { - int m_ClientID; - sqlstr::CSqlString m_Name; + using CSqlData::CSqlData; + sqlstr::CSqlString<32> m_ServerType; + sqlstr::CSqlString<32> m_CurrentMap; + sqlstr::CSqlString m_RequestingPlayer; + int m_Stars; }; -// used for mapinfo -struct CSqlMapData : CSqlData +struct CSqlScoreData : CSqlData { - int m_ClientID; - - sqlstr::CSqlString<128> m_RequestedMap; - char m_aFuzzyMap[128]; - sqlstr::CSqlString m_Name; -}; - -// used for mapvote -struct CSqlMapVoteData : CSqlMapData -{ - std::shared_ptr m_pResult; -}; - -struct CSqlScoreData : CSqlData -{ - int m_ClientID; + using CSqlData::CSqlData; + sqlstr::CSqlString m_Map; + char m_GameUuid[UUID_MAXSTRSIZE]; sqlstr::CSqlString m_Name; - bool m_NotEligible; + int m_ClientID; float m_Time; char m_aTimestamp[TIMESTAMP_STR_LENGTH]; float m_aCpCurrent[NUM_CHECKPOINTS]; @@ -116,111 +149,147 @@ struct CSqlScoreData : CSqlData char m_aRequestingPlayer[MAX_NAME_LENGTH]; }; -struct CSqlTeamScoreData : CSqlData +struct CSqlTeamScoreData : CSqlData { - bool m_NotEligible; + using CSqlData::CSqlData; + char m_GameUuid[UUID_MAXSTRSIZE]; + sqlstr::CSqlString m_Map; float m_Time; char m_aTimestamp[TIMESTAMP_STR_LENGTH]; unsigned int m_Size; - int m_aClientIDs[MAX_CLIENTS]; sqlstr::CSqlString m_aNames[MAX_CLIENTS]; }; -struct CSqlTeamSave : CSqlData +struct CSqlTeamSave : CSqlData { - virtual ~CSqlTeamSave(); + using CSqlData::CSqlData; + virtual ~CSqlTeamSave() {}; - int m_Team; - int m_ClientID; char m_ClientName[MAX_NAME_LENGTH]; - sqlstr::CSqlString<128> m_Code; + + char m_Map[128]; + char m_Code[128]; + char m_aGeneratedCode[128]; char m_Server[5]; }; -struct CSqlTeamLoad : CSqlData +struct CSqlTeamLoad : CSqlData { + using CSqlData::CSqlData; sqlstr::CSqlString<128> m_Code; + sqlstr::CSqlString<128> m_Map; + sqlstr::CSqlString m_RequestingPlayer; int m_ClientID; - char m_ClientName[MAX_NAME_LENGTH]; + // struct holding all player names in the team or an empty string + char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int m_aClientID[MAX_CLIENTS]; + int m_NumPlayer; }; -struct CSqlGetSavesData: CSqlData +// controls one thread +template < typename TResult > +struct CSqlExecData { - int m_ClientID; - sqlstr::CSqlString m_Name; + CSqlExecData( + bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), + CSqlData *pSqlResult, + bool ReadOnly = true + ); + ~CSqlExecData(); + + bool (*m_pFuncPtr) (CSqlServer*, const CSqlData *, bool); + CSqlData *m_pSqlData; + bool m_ReadOnly; + + static void ExecSqlFunc(void *pUser); }; -struct CSqlRandomMap : CSqlScoreData -{ - std::shared_ptr m_pResult; -}; +class IServer; +class CGameContext; class CSqlScore: public IScore { + static LOCK ms_FailureFileLock; + + static bool Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure); + + static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + + static bool LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + + static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + + static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + CGameContext *GameServer() { return m_pGameServer; } IServer *Server() { return m_pServer; } CGameContext *m_pGameServer; IServer *m_pServer; - static void ExecSqlFunc(void *pUser); + std::vector m_aWordlist; + CPrng m_Prng; + void GeneratePassphrase(char *pBuf, int BufSize); - static bool Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure); - - char m_aMap[64]; - char m_aGameUuid[UUID_MAXSTRSIZE]; - - static LOCK ms_FailureFileLock; - - static bool CheckBirthdayThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); - static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false); + // returns new SqlResult bound to the player, if no current Thread is active for this player + std::shared_ptr NewSqlPlayerResult(int ClientID); + // Creates for player database requests + void ExecPlayerThread( + bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), + const char* pThreadName, + int ClientID, + const char* pName, + int Offset + ); public: + // keeps track of score-threads + static std::atomic_int ms_InstanceCount; CSqlScore(CGameContext *pGameServer); - ~CSqlScore(); + ~CSqlScore() {} - virtual void CheckBirthday(int ClientID); - virtual void LoadScore(int ClientID); + // Requested by game context, shouldn't fail in case the player started another thread + virtual void RandomMap(int ClientID, int Stars); + virtual void RandomUnfinishedMap(int ClientID, int Stars); + virtual void MapVote(int ClientID, const char* MapName); + + virtual void LoadPlayerData(int ClientID); + // Requested by players (fails if another request by this player is active) virtual void MapInfo(int ClientID, const char* MapName); - virtual void MapVote(std::shared_ptr *ppResult, int ClientID, const char* MapName); + virtual void ShowRank(int ClientID, const char* pName); + virtual void ShowTeamRank(int ClientID, const char* pName); + virtual void ShowPoints(int ClientID, const char* pName); + virtual void ShowTimes(int ClientID, const char* pName, int Offset = 1); + virtual void ShowTimes(int ClientID, int Offset = 1); + virtual void ShowTop5(int ClientID, int Offset = 1); + virtual void ShowTeamTop5(int ClientID, int Offset = 1); + virtual void ShowTopPoints(int ClientID, int Offset = 1); + virtual void GetSaves(int ClientID); + + // requested by teams + virtual void SaveTeam(int ClientID, const char* Code, const char* Server); + virtual void LoadTeam(const char* Code, int ClientID); + + // Game relevant not allowed to fail due to an ongoing SQL request. virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible); virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp); - virtual void ShowRank(int ClientID, const char* pName, bool Search = false); - virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false); - virtual void ShowTimes(int ClientID, const char* pName, int Debut = 1); - virtual void ShowTimes(int ClientID, int Debut = 1); - virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut = 1); - virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut = 1); - virtual void ShowPoints(int ClientID, const char* pName, bool Search = false); - virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID, - void *pUserData, int Debut = 1); - virtual void RandomMap(std::shared_ptr *ppResult, int ClientID, int stars); - virtual void RandomUnfinishedMap(std::shared_ptr *ppResult, int ClientID, int stars); - virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server); - virtual void LoadTeam(const char* Code, int ClientID); - virtual void GetSaves(int ClientID); virtual void OnShutdown(); }; -#endif // GAME_SERVER_SCORE_SQL_SCORE_H +#endif // GAME_SERVER_SCORE_SQL_H diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index d31edc2ec..e5fb052ac 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -2,6 +2,9 @@ #include "teams.h" #include "score.h" #include +#if defined(CONF_SQL) +#include "score/sql_score.h" +#endif CGameTeams::CGameTeams(CGameContext *pGameContext) : m_pGameContext(pGameContext) @@ -19,9 +22,11 @@ void CGameTeams::Reset() m_MembersCount[i] = 0; m_LastChat[i] = 0; m_TeamLocked[i] = false; - m_IsSaving[i] = false; m_Invited[i] = 0; m_Practice[i] = false; +#if defined(CONF_SQL) + m_pSaveTeamResult[i] = nullptr; +#endif } } @@ -250,6 +255,11 @@ bool CGameTeams::SetCharacterTeam(int ClientID, int Team) if (m_Practice[m_Core.Team(ClientID)]) return false; + //you can not join a team which is currently in the process of saving, + //because the save-process can fail and then the team is reset into the game + if((Team != TEAM_SUPER && GetSaving(Team)) + || (m_Core.Team(ClientID) != TEAM_SUPER && GetSaving(m_Core.Team(ClientID)))) + return false; SetForceCharacterTeam(ClientID, Team); //GameServer()->CreatePlayerSpawn(Character(id)->m_Core.m_Pos, TeamMask()); @@ -327,6 +337,7 @@ void CGameTeams::ForceLeaveTeam(int ClientID) SetTeamLock(m_Core.Team(ClientID), false); ResetInvited(m_Core.Team(ClientID)); m_Practice[m_Core.Team(ClientID)] = false; + // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves } } @@ -670,10 +681,56 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp) } } +#if defined(CONF_SQL) +void CGameTeams::ProcessSaveTeam() +{ + for(int Team = 0; Team < MAX_CLIENTS; Team++) + { + if(m_pSaveTeamResult[Team] == nullptr || m_pSaveTeamResult[Team].use_count() != 1) + continue; + if(m_pSaveTeamResult[Team]->m_aBroadcast[0] != '\0') + GameServer()->SendBroadcast(m_pSaveTeamResult[Team]->m_aBroadcast, -1); + if(m_pSaveTeamResult[Team]->m_aMessage[0] != '\0') + GameServer()->SendChatTeam(Team, m_pSaveTeamResult[Team]->m_aMessage); + // TODO: log load/save success/fail in teehistorian + switch(m_pSaveTeamResult[Team]->m_Status) + { + case CSqlSaveResult::SAVE_SUCCESS: + { + ResetSavedTeam(m_pSaveTeamResult[Team]->m_RequestingPlayer, Team); + char aSaveID[UUID_MAXSTRSIZE]; + FormatUuid(m_pSaveTeamResult[Team]->m_SaveID, aSaveID, UUID_MAXSTRSIZE); + dbg_msg("save", "Save successful: %s", aSaveID); + break; + } + case CSqlSaveResult::SAVE_FAILED: + if(m_MembersCount[Team] > 0) + m_pSaveTeamResult[Team]->m_SavedTeam.load(Team); + break; + case CSqlSaveResult::LOAD_SUCCESS: + { + if(m_MembersCount[Team] > 0) + m_pSaveTeamResult[Team]->m_SavedTeam.load(Team); + char aSaveID[UUID_MAXSTRSIZE]; + FormatUuid(m_pSaveTeamResult[Team]->m_SaveID, aSaveID, UUID_MAXSTRSIZE); + dbg_msg("save", "Load successful: %s", aSaveID); + break; + } + case CSqlSaveResult::LOAD_FAILED: + break; + } + m_pSaveTeamResult[Team] = nullptr; + } +} +#endif + void CGameTeams::OnCharacterSpawn(int ClientID) { m_Core.SetSolo(ClientID, false); + if(GetSaving(m_Core.Team(ClientID))) + return; + if (m_Core.Team(ClientID) >= TEAM_SUPER || !m_TeamLocked[m_Core.Team(ClientID)]) // Important to only set a new team here, don't remove from an existing // team since a newly joined player does by definition not have an old team @@ -686,6 +743,8 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon) m_Core.SetSolo(ClientID, false); int Team = m_Core.Team(ClientID); + if(GetSaving(Team)) + return; bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME; if(!Locked) @@ -746,30 +805,27 @@ void CGameTeams::SetClientInvited(int Team, int ClientID, bool Invited) } } -void CGameTeams::KillSavedTeam(int Team) +#if defined(CONF_SQL) +void CGameTeams::KillSavedTeam(int ClientID, int Team) { - // Set so that no finish is accidentally given to some of the players - ChangeTeamState(Team, CGameTeams::TEAMSTATE_OPEN); + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->m_VotedForPractice = false; + GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF); + } + } +} +void CGameTeams::ResetSavedTeam(int ClientID, int Team) +{ for (int i = 0; i < MAX_CLIENTS; i++) { if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) { - // Set so that no finish is accidentally given to some of the players - GameServer()->m_apPlayers[i]->GetCharacter()->m_DDRaceState = DDRACE_NONE; - m_TeeFinished[i] = false; + SetForceCharacterTeam(i, 0); } } - - for (int i = 0; i < MAX_CLIENTS; i++) - if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) - GameServer()->m_apPlayers[i]->ThreadKillCharacter(-2); - - ChangeTeamState(Team, CGameTeams::TEAMSTATE_EMPTY); - - // unlock team when last player leaves - SetTeamLock(Team, false); - ResetInvited(Team); - - m_Practice[Team] = false; } +#endif diff --git a/src/game/server/teams.h b/src/game/server/teams.h index 36fd34d32..e5fdbfc9c 100644 --- a/src/game/server/teams.h +++ b/src/game/server/teams.h @@ -5,15 +5,21 @@ #include #include +#if defined(CONF_SQL) +class CSqlSaveResult; +#endif + class CGameTeams { int m_TeamState[MAX_CLIENTS]; int m_MembersCount[MAX_CLIENTS]; bool m_TeeFinished[MAX_CLIENTS]; bool m_TeamLocked[MAX_CLIENTS]; - bool m_IsSaving[MAX_CLIENTS]; uint64_t m_Invited[MAX_CLIENTS]; bool m_Practice[MAX_CLIENTS]; +#if defined(CONF_SQL) + std::shared_ptr m_pSaveTeamResult[MAX_CLIENTS]; +#endif class CGameContext * m_pGameContext; @@ -85,7 +91,11 @@ public: void SetDDRaceState(CPlayer* Player, int DDRaceState); void SetStartTime(CPlayer* Player, int StartTime); void SetCpActive(CPlayer* Player, int CpActive); - void KillSavedTeam(int Team); +#if defined(CONF_SQL) + void KillSavedTeam(int ClientID, int Team); + void ResetSavedTeam(int ClientID, int Team); + void ProcessSaveTeam(); +#endif bool TeeFinished(int ClientID) { @@ -114,15 +124,20 @@ public: { m_TeeFinished[ClientID] = finished; } - - void SetSaving(int TeamID, bool Value) +#if defined(CONF_SQL) + void SetSaving(int TeamID, std::shared_ptr SaveResult) { - m_IsSaving[TeamID] = Value; + m_pSaveTeamResult[TeamID] = SaveResult; } +#endif bool GetSaving(int TeamID) { - return m_IsSaving[TeamID]; +#if defined(CONF_SQL) + return m_pSaveTeamResult[TeamID] != nullptr; +#else + return false; +#endif } void EnablePractice(int Team)