1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "tools.h"
23#include "configmanager.h"
24
25extern ConfigManager g_config;
26
27void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result)
28{
29 std::cout << '[' << where << "] Failed to load " << fileName << ": " << result.description() << std::endl;
30
31 FILE* file = fopen(fileName.c_str(), "rb");
32 if (!file) {
33 return;
34 }
35
36 char buffer[32768];
37 uint32_t currentLine = 1;
38 std::string line;
39
40 auto offset = static_cast<size_t>(result.offset);
41 size_t lineOffsetPosition = 0;
42 size_t index = 0;
43 size_t bytes;
44 do {
45 bytes = fread(buffer, 1, 32768, file);
46 for (size_t i = 0; i < bytes; ++i) {
47 char ch = buffer[i];
48 if (ch == '\n') {
49 if ((index + i) >= offset) {
50 lineOffsetPosition = line.length() - ((index + i) - offset);
51 bytes = 0;
52 break;
53 }
54 ++currentLine;
55 line.clear();
56 } else {
57 line.push_back(ch);
58 }
59 }
60 index += bytes;
61 } while (bytes == 32768);
62 fclose(file);
63
64 std::cout << "Line " << currentLine << ':' << std::endl;
65 std::cout << line << std::endl;
66 for (size_t i = 0; i < lineOffsetPosition; i++) {
67 if (line[i] == '\t') {
68 std::cout << '\t';
69 } else {
70 std::cout << ' ';
71 }
72 }
73 std::cout << '^' << std::endl;
74}
75
76static uint32_t circularShift(int bits, uint32_t value)
77{
78 return (value << bits) | (value >> (32 - bits));
79}
80
81static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H)
82{
83 uint32_t W[80];
84 for (int i = 0; i < 16; ++i) {
85 const size_t offset = i << 2;
86 W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | messageBlock[offset + 3];
87 }
88
89 for (int i = 16; i < 80; ++i) {
90 W[i] = circularShift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
91 }
92
93 uint32_t A = H[0], B = H[1], C = H[2], D = H[3], E = H[4];
94
95 for (int i = 0; i < 20; ++i) {
96 const uint32_t tmp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999;
97 E = D; D = C; C = circularShift(30, B); B = A; A = tmp;
98 }
99
100 for (int i = 20; i < 40; ++i) {
101 const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1;
102 E = D; D = C; C = circularShift(30, B); B = A; A = tmp;
103 }
104
105 for (int i = 40; i < 60; ++i) {
106 const uint32_t tmp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC;
107 E = D; D = C; C = circularShift(30, B); B = A; A = tmp;
108 }
109
110 for (int i = 60; i < 80; ++i) {
111 const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6;
112 E = D; D = C; C = circularShift(30, B); B = A; A = tmp;
113 }
114
115 H[0] += A;
116 H[1] += B;
117 H[2] += C;
118 H[3] += D;
119 H[4] += E;
120}
121
122std::string transformToSHA1(const std::string& input)
123{
124 uint32_t H[] = {
125 0x67452301,
126 0xEFCDAB89,
127 0x98BADCFE,
128 0x10325476,
129 0xC3D2E1F0
130 };
131
132 uint8_t messageBlock[64];
133 size_t index = 0;
134
135 uint32_t length_low = 0;
136 uint32_t length_high = 0;
137 for (char ch : input) {
138 messageBlock[index++] = ch;
139
140 length_low += 8;
141 if (length_low == 0) {
142 length_high++;
143 }
144
145 if (index == 64) {
146 processSHA1MessageBlock(messageBlock, H);
147 index = 0;
148 }
149 }
150
151 messageBlock[index++] = 0x80;
152
153 if (index > 56) {
154 while (index < 64) {
155 messageBlock[index++] = 0;
156 }
157
158 processSHA1MessageBlock(messageBlock, H);
159 index = 0;
160 }
161
162 while (index < 56) {
163 messageBlock[index++] = 0;
164 }
165
166 messageBlock[56] = length_high >> 24;
167 messageBlock[57] = length_high >> 16;
168 messageBlock[58] = length_high >> 8;
169 messageBlock[59] = length_high;
170
171 messageBlock[60] = length_low >> 24;
172 messageBlock[61] = length_low >> 16;
173 messageBlock[62] = length_low >> 8;
174 messageBlock[63] = length_low;
175
176 processSHA1MessageBlock(messageBlock, H);
177
178 char hexstring[41];
179 static const char hexDigits[] = {"0123456789abcdef"};
180 for (int hashByte = 20; --hashByte >= 0;) {
181 const uint8_t byte = H[hashByte >> 2] >> (((3 - hashByte) & 3) << 3);
182 index = hashByte << 1;
183 hexstring[index] = hexDigits[byte >> 4];
184 hexstring[index + 1] = hexDigits[byte & 15];
185 }
186 return std::string(hexstring, 40);
187}
188
189std::string generateToken(const std::string& key, uint32_t ticks)
190{
191 // generate message from ticks
192 std::string message(8, 0);
193 for (uint8_t i = 8; --i; ticks >>= 8) {
194 message[i] = static_cast<char>(ticks & 0xFF);
195 }
196
197 // hmac key pad generation
198 std::string iKeyPad(64, 0x36), oKeyPad(64, 0x5C);
199 for (uint8_t i = 0; i < key.length(); ++i) {
200 iKeyPad[i] ^= key[i];
201 oKeyPad[i] ^= key[i];
202 }
203
204 oKeyPad.reserve(84);
205
206 // hmac concat inner pad with message
207 iKeyPad.append(message);
208
209 // hmac first pass
210 message.assign(transformToSHA1(iKeyPad));
211
212 // hmac concat outer pad with message, conversion from hex to int needed
213 for (uint8_t i = 0; i < message.length(); i += 2) {
214 oKeyPad.push_back(static_cast<char>(std::strtoul(message.substr(i, 2).c_str(), nullptr, 16)));
215 }
216
217 // hmac second pass
218 message.assign(transformToSHA1(oKeyPad));
219
220 // calculate hmac offset
221 uint32_t offset = static_cast<uint32_t>(std::strtoul(message.substr(39, 1).c_str(), nullptr, 16) & 0xF);
222
223 // get truncated hash
224 uint32_t truncHash = static_cast<uint32_t>(std::strtoul(message.substr(2 * offset, 8).c_str(), nullptr, 16)) & 0x7FFFFFFF;
225 message.assign(std::to_string(truncHash));
226
227 // return only last AUTHENTICATOR_DIGITS (default 6) digits, also asserts exactly 6 digits
228 uint32_t hashLen = message.length();
229 message.assign(message.substr(hashLen - std::min(hashLen, AUTHENTICATOR_DIGITS)));
230 message.insert(0, AUTHENTICATOR_DIGITS - std::min(hashLen, AUTHENTICATOR_DIGITS), '0');
231 return message;
232}
233
234void replaceString(std::string& str, const std::string& sought, const std::string& replacement)
235{
236 size_t pos = 0;
237 size_t start = 0;
238 size_t soughtLen = sought.length();
239 size_t replaceLen = replacement.length();
240
241 while ((pos = str.find(sought, start)) != std::string::npos) {
242 str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen);
243 start = pos + replaceLen;
244 }
245}
246
247void trim_right(std::string& source, char t)
248{
249 source.erase(source.find_last_not_of(t) + 1);
250}
251
252void trim_left(std::string& source, char t)
253{
254 source.erase(0, source.find_first_not_of(t));
255}
256
257void toLowerCaseString(std::string& source)
258{
259 std::transform(source.begin(), source.end(), source.begin(), tolower);
260}
261
262std::string asLowerCaseString(std::string source)
263{
264 toLowerCaseString(source);
265 return source;
266}
267
268std::string asUpperCaseString(std::string source)
269{
270 std::transform(source.begin(), source.end(), source.begin(), toupper);
271 return source;
272}
273
274StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/)
275{
276 StringVector returnVector;
277 std::string::size_type start = 0, end = 0;
278
279 while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) {
280 returnVector.push_back(inString.substr(start, end - start));
281 start = end + separator.size();
282 }
283
284 returnVector.push_back(inString.substr(start));
285 return returnVector;
286}
287
288IntegerVector vectorAtoi(const StringVector& stringVector)
289{
290 IntegerVector returnVector;
291 for (const auto& string : stringVector) {
292 returnVector.push_back(std::stoi(string));
293 }
294 return returnVector;
295}
296
297std::mt19937& getRandomGenerator()
298{
299 static std::random_device rd;
300 static std::mt19937 generator(rd());
301 return generator;
302}
303
304int32_t uniform_random(int32_t minNumber, int32_t maxNumber)
305{
306 static std::uniform_int_distribution<int32_t> uniformRand;
307 if (minNumber == maxNumber) {
308 return minNumber;
309 } else if (minNumber > maxNumber) {
310 std::swap(minNumber, maxNumber);
311 }
312 return uniformRand(getRandomGenerator(), std::uniform_int_distribution<int32_t>::param_type(minNumber, maxNumber));
313}
314
315int32_t normal_random(int32_t minNumber, int32_t maxNumber)
316{
317 static std::normal_distribution<float> normalRand(0.5f, 0.25f);
318 if (minNumber == maxNumber) {
319 return minNumber;
320 } else if (minNumber > maxNumber) {
321 std::swap(minNumber, maxNumber);
322 }
323
324 int32_t increment;
325 const int32_t diff = maxNumber - minNumber;
326 const float v = normalRand(getRandomGenerator());
327 if (v < 0.0) {
328 increment = diff / 2;
329 } else if (v > 1.0) {
330 increment = (diff + 1) / 2;
331 } else {
332 increment = round(v * diff);
333 }
334 return minNumber + increment;
335}
336
337bool boolean_random(double probability/* = 0.5*/)
338{
339 static std::bernoulli_distribution booleanRand;
340 return booleanRand(getRandomGenerator(), std::bernoulli_distribution::param_type(probability));
341}
342
343void trimString(std::string& str)
344{
345 str.erase(str.find_last_not_of(' ') + 1);
346 str.erase(0, str.find_first_not_of(' '));
347}
348
349std::string convertIPToString(uint32_t ip)
350{
351 char buffer[17];
352
353 int res = sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24));
354 if (res < 0) {
355 return {};
356 }
357
358 return buffer;
359}
360
361std::string formatDate(time_t time)
362{
363 const tm* tms = localtime(&time);
364 if (!tms) {
365 return {};
366 }
367
368 char buffer[20];
369 int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec);
370 if (res < 0) {
371 return {};
372 }
373 return {buffer, 19};
374}
375
376std::string formatDateShort(time_t time)
377{
378 const tm* tms = localtime(&time);
379 if (!tms) {
380 return {};
381 }
382
383 char buffer[12];
384 size_t res = strftime(buffer, 12, "%d %b %Y", tms);
385 if (res == 0) {
386 return {};
387 }
388 return {buffer, 11};
389}
390
391Direction getDirection(const std::string& string)
392{
393 Direction direction = DIRECTION_NORTH;
394
395 if (string == "north" || string == "n" || string == "0") {
396 direction = DIRECTION_NORTH;
397 } else if (string == "east" || string == "e" || string == "1") {
398 direction = DIRECTION_EAST;
399 } else if (string == "south" || string == "s" || string == "2") {
400 direction = DIRECTION_SOUTH;
401 } else if (string == "west" || string == "w" || string == "3") {
402 direction = DIRECTION_WEST;
403 } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") {
404 direction = DIRECTION_SOUTHWEST;
405 } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") {
406 direction = DIRECTION_SOUTHEAST;
407 } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") {
408 direction = DIRECTION_NORTHWEST;
409 } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") {
410 direction = DIRECTION_NORTHEAST;
411 }
412
413 return direction;
414}
415
416Position getNextPosition(Direction direction, Position pos)
417{
418 switch (direction) {
419 case DIRECTION_NORTH:
420 pos.y--;
421 break;
422
423 case DIRECTION_SOUTH:
424 pos.y++;
425 break;
426
427 case DIRECTION_WEST:
428 pos.x--;
429 break;
430
431 case DIRECTION_EAST:
432 pos.x++;
433 break;
434
435 case DIRECTION_SOUTHWEST:
436 pos.x--;
437 pos.y++;
438 break;
439
440 case DIRECTION_NORTHWEST:
441 pos.x--;
442 pos.y--;
443 break;
444
445 case DIRECTION_NORTHEAST:
446 pos.x++;
447 pos.y--;
448 break;
449
450 case DIRECTION_SOUTHEAST:
451 pos.x++;
452 pos.y++;
453 break;
454
455 default:
456 break;
457 }
458
459 return pos;
460}
461
462Direction getDirectionTo(const Position& from, const Position& to)
463{
464 Direction dir;
465
466 int32_t x_offset = Position::getOffsetX(from, to);
467 if (x_offset < 0) {
468 dir = DIRECTION_EAST;
469 x_offset = std::abs(x_offset);
470 } else {
471 dir = DIRECTION_WEST;
472 }
473
474 int32_t y_offset = Position::getOffsetY(from, to);
475 if (y_offset >= 0) {
476 if (y_offset > x_offset) {
477 dir = DIRECTION_NORTH;
478 } else if (y_offset == x_offset) {
479 if (dir == DIRECTION_EAST) {
480 dir = DIRECTION_NORTHEAST;
481 } else {
482 dir = DIRECTION_NORTHWEST;
483 }
484 }
485 } else {
486 y_offset = std::abs(y_offset);
487 if (y_offset > x_offset) {
488 dir = DIRECTION_SOUTH;
489 } else if (y_offset == x_offset) {
490 if (dir == DIRECTION_EAST) {
491 dir = DIRECTION_SOUTHEAST;
492 } else {
493 dir = DIRECTION_SOUTHWEST;
494 }
495 }
496 }
497 return dir;
498}
499
500using MagicEffectNames = std::unordered_map<std::string, MagicEffectClasses>;
501using ShootTypeNames = std::unordered_map<std::string, ShootType_t>;
502using CombatTypeNames = std::unordered_map<CombatType_t, std::string, std::hash<int32_t>>;
503using AmmoTypeNames = std::unordered_map<std::string, Ammo_t>;
504using WeaponActionNames = std::unordered_map<std::string, WeaponAction_t>;
505using SkullNames = std::unordered_map<std::string, Skulls_t>;
506
507MagicEffectNames magicEffectNames = {
508 {"redspark", CONST_ME_DRAWBLOOD},
509 {"bluebubble", CONST_ME_LOSEENERGY},
510 {"poff", CONST_ME_POFF},
511 {"yellowspark", CONST_ME_BLOCKHIT},
512 {"explosionarea", CONST_ME_EXPLOSIONAREA},
513 {"explosion", CONST_ME_EXPLOSIONHIT},
514 {"firearea", CONST_ME_FIREAREA},
515 {"yellowbubble", CONST_ME_YELLOW_RINGS},
516 {"greenbubble", CONST_ME_GREEN_RINGS},
517 {"blackspark", CONST_ME_HITAREA},
518 {"teleport", CONST_ME_TELEPORT},
519 {"energy", CONST_ME_ENERGYHIT},
520 {"blueshimmer", CONST_ME_MAGIC_BLUE},
521 {"redshimmer", CONST_ME_MAGIC_RED},
522 {"greenshimmer", CONST_ME_MAGIC_GREEN},
523 {"fire", CONST_ME_HITBYFIRE},
524 {"greenspark", CONST_ME_HITBYPOISON},
525 {"mortarea", CONST_ME_MORTAREA},
526 {"greennote", CONST_ME_SOUND_GREEN},
527 {"rednote", CONST_ME_SOUND_RED},
528 {"poison", CONST_ME_POISONAREA},
529 {"yellownote", CONST_ME_SOUND_YELLOW},
530 {"purplenote", CONST_ME_SOUND_PURPLE},
531 {"bluenote", CONST_ME_SOUND_BLUE},
532 {"whitenote", CONST_ME_SOUND_WHITE},
533 {"bubbles", CONST_ME_BUBBLES},
534 {"dice", CONST_ME_CRAPS},
535 {"giftwraps", CONST_ME_GIFT_WRAPS},
536 {"yellowfirework", CONST_ME_FIREWORK_YELLOW},
537 {"redfirework", CONST_ME_FIREWORK_RED},
538 {"bluefirework", CONST_ME_FIREWORK_BLUE},
539 {"stun", CONST_ME_STUN},
540 {"sleep", CONST_ME_SLEEP},
541 {"watercreature", CONST_ME_WATERCREATURE},
542 {"groundshaker", CONST_ME_GROUNDSHAKER},
543 {"hearts", CONST_ME_HEARTS},
544 {"fireattack", CONST_ME_FIREATTACK},
545 {"energyarea", CONST_ME_ENERGYAREA},
546 {"smallclouds", CONST_ME_SMALLCLOUDS},
547 {"holydamage", CONST_ME_HOLYDAMAGE},
548 {"bigclouds", CONST_ME_BIGCLOUDS},
549 {"icearea", CONST_ME_ICEAREA},
550 {"icetornado", CONST_ME_ICETORNADO},
551 {"iceattack", CONST_ME_ICEATTACK},
552 {"stones", CONST_ME_STONES},
553 {"smallplants", CONST_ME_SMALLPLANTS},
554 {"carniphila", CONST_ME_CARNIPHILA},
555 {"purpleenergy", CONST_ME_PURPLEENERGY},
556 {"yellowenergy", CONST_ME_YELLOWENERGY},
557 {"holyarea", CONST_ME_HOLYAREA},
558 {"bigplants", CONST_ME_BIGPLANTS},
559 {"cake", CONST_ME_CAKE},
560 {"giantice", CONST_ME_GIANTICE},
561 {"watersplash", CONST_ME_WATERSPLASH},
562 {"plantattack", CONST_ME_PLANTATTACK},
563 {"tutorialarrow", CONST_ME_TUTORIALARROW},
564 {"tutorialsquare", CONST_ME_TUTORIALSQUARE},
565 {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL},
566 {"mirrorvertical", CONST_ME_MIRRORVERTICAL},
567 {"skullhorizontal", CONST_ME_SKULLHORIZONTAL},
568 {"skullvertical", CONST_ME_SKULLVERTICAL},
569 {"assassin", CONST_ME_ASSASSIN},
570 {"stepshorizontal", CONST_ME_STEPSHORIZONTAL},
571 {"bloodysteps", CONST_ME_BLOODYSTEPS},
572 {"stepsvertical", CONST_ME_STEPSVERTICAL},
573 {"yalaharighost", CONST_ME_YALAHARIGHOST},
574 {"bats", CONST_ME_BATS},
575 {"smoke", CONST_ME_SMOKE},
576 {"insects", CONST_ME_INSECTS},
577 {"dragonhead", CONST_ME_DRAGONHEAD},
578 {"orcshaman", CONST_ME_ORCSHAMAN},
579 {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE},
580 {"thunder", CONST_ME_THUNDER},
581 {"ferumbras", CONST_ME_FERUMBRAS},
582 {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL},
583 {"confettivertical", CONST_ME_CONFETTI_VERTICAL},
584 {"blacksmoke", CONST_ME_BLACKSMOKE},
585 {"redsmoke", CONST_ME_REDSMOKE},
586 {"yellowsmoke", CONST_ME_YELLOWSMOKE},
587 {"greensmoke", CONST_ME_GREENSMOKE},
588 {"purplesmoke", CONST_ME_PURPLESMOKE},
589};
590
591ShootTypeNames shootTypeNames = {
592 {"spear", CONST_ANI_SPEAR},
593 {"bolt", CONST_ANI_BOLT},
594 {"arrow", CONST_ANI_ARROW},
595 {"fire", CONST_ANI_FIRE},
596 {"energy", CONST_ANI_ENERGY},
597 {"poisonarrow", CONST_ANI_POISONARROW},
598 {"burstarrow", CONST_ANI_BURSTARROW},
599 {"throwingstar", CONST_ANI_THROWINGSTAR},
600 {"throwingknife", CONST_ANI_THROWINGKNIFE},
601 {"smallstone", CONST_ANI_SMALLSTONE},
602 {"death", CONST_ANI_DEATH},
603 {"largerock", CONST_ANI_LARGEROCK},
604 {"snowball", CONST_ANI_SNOWBALL},
605 {"powerbolt", CONST_ANI_POWERBOLT},
606 {"poison", CONST_ANI_POISON},
607 {"infernalbolt", CONST_ANI_INFERNALBOLT},
608 {"huntingspear", CONST_ANI_HUNTINGSPEAR},
609 {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR},
610 {"redstar", CONST_ANI_REDSTAR},
611 {"greenstar", CONST_ANI_GREENSTAR},
612 {"royalspear", CONST_ANI_ROYALSPEAR},
613 {"sniperarrow", CONST_ANI_SNIPERARROW},
614 {"onyxarrow", CONST_ANI_ONYXARROW},
615 {"piercingbolt", CONST_ANI_PIERCINGBOLT},
616 {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD},
617 {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE},
618 {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB},
619 {"etherealspear", CONST_ANI_ETHEREALSPEAR},
620 {"ice", CONST_ANI_ICE},
621 {"earth", CONST_ANI_EARTH},
622 {"holy", CONST_ANI_HOLY},
623 {"suddendeath", CONST_ANI_SUDDENDEATH},
624 {"flasharrow", CONST_ANI_FLASHARROW},
625 {"flammingarrow", CONST_ANI_FLAMMINGARROW},
626 {"shiverarrow", CONST_ANI_SHIVERARROW},
627 {"energyball", CONST_ANI_ENERGYBALL},
628 {"smallice", CONST_ANI_SMALLICE},
629 {"smallholy", CONST_ANI_SMALLHOLY},
630 {"smallearth", CONST_ANI_SMALLEARTH},
631 {"eartharrow", CONST_ANI_EARTHARROW},
632 {"explosion", CONST_ANI_EXPLOSION},
633 {"cake", CONST_ANI_CAKE},
634 {"tarsalarrow", CONST_ANI_TARSALARROW},
635 {"vortexbolt", CONST_ANI_VORTEXBOLT},
636 {"prismaticbolt", CONST_ANI_PRISMATICBOLT},
637 {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW},
638 {"drillbolt", CONST_ANI_DRILLBOLT},
639 {"envenomedarrow", CONST_ANI_ENVENOMEDARROW},
640 {"gloothspear", CONST_ANI_GLOOTHSPEAR},
641 {"simplearrow", CONST_ANI_SIMPLEARROW},
642};
643
644CombatTypeNames combatTypeNames = {
645 {COMBAT_PHYSICALDAMAGE, "physical"},
646 {COMBAT_ENERGYDAMAGE, "energy"},
647 {COMBAT_EARTHDAMAGE, "earth"},
648 {COMBAT_FIREDAMAGE, "fire"},
649 {COMBAT_UNDEFINEDDAMAGE, "undefined"},
650 {COMBAT_LIFEDRAIN, "lifedrain"},
651 {COMBAT_MANADRAIN, "manadrain"},
652 {COMBAT_HEALING, "healing"},
653 {COMBAT_DROWNDAMAGE, "drown"},
654 {COMBAT_ICEDAMAGE, "ice"},
655 {COMBAT_HOLYDAMAGE, "holy"},
656 {COMBAT_DEATHDAMAGE, "death"},
657};
658
659AmmoTypeNames ammoTypeNames = {
660 {"spear", AMMO_SPEAR},
661 {"bolt", AMMO_BOLT},
662 {"arrow", AMMO_ARROW},
663 {"poisonarrow", AMMO_ARROW},
664 {"burstarrow", AMMO_ARROW},
665 {"throwingstar", AMMO_THROWINGSTAR},
666 {"throwingknife", AMMO_THROWINGKNIFE},
667 {"smallstone", AMMO_STONE},
668 {"largerock", AMMO_STONE},
669 {"snowball", AMMO_SNOWBALL},
670 {"powerbolt", AMMO_BOLT},
671 {"infernalbolt", AMMO_BOLT},
672 {"huntingspear", AMMO_SPEAR},
673 {"enchantedspear", AMMO_SPEAR},
674 {"royalspear", AMMO_SPEAR},
675 {"sniperarrow", AMMO_ARROW},
676 {"onyxarrow", AMMO_ARROW},
677 {"piercingbolt", AMMO_BOLT},
678 {"etherealspear", AMMO_SPEAR},
679 {"flasharrow", AMMO_ARROW},
680 {"flammingarrow", AMMO_ARROW},
681 {"shiverarrow", AMMO_ARROW},
682 {"eartharrow", AMMO_ARROW},
683};
684
685WeaponActionNames weaponActionNames = {
686 {"move", WEAPONACTION_MOVE},
687 {"removecharge", WEAPONACTION_REMOVECHARGE},
688 {"removecount", WEAPONACTION_REMOVECOUNT},
689};
690
691SkullNames skullNames = {
692 {"none", SKULL_NONE},
693 {"yellow", SKULL_YELLOW},
694 {"green", SKULL_GREEN},
695 {"white", SKULL_WHITE},
696 {"red", SKULL_RED},
697 {"black", SKULL_BLACK},
698 {"orange", SKULL_ORANGE},
699};
700
701MagicEffectClasses getMagicEffect(const std::string& strValue)
702{
703 auto magicEffect = magicEffectNames.find(strValue);
704 if (magicEffect != magicEffectNames.end()) {
705 return magicEffect->second;
706 }
707 return CONST_ME_NONE;
708}
709
710ShootType_t getShootType(const std::string& strValue)
711{
712 auto shootType = shootTypeNames.find(strValue);
713 if (shootType != shootTypeNames.end()) {
714 return shootType->second;
715 }
716 return CONST_ANI_NONE;
717}
718
719std::string getCombatName(CombatType_t combatType)
720{
721 auto combatName = combatTypeNames.find(combatType);
722 if (combatName != combatTypeNames.end()) {
723 return combatName->second;
724 }
725 return "unknown";
726}
727
728Ammo_t getAmmoType(const std::string& strValue)
729{
730 auto ammoType = ammoTypeNames.find(strValue);
731 if (ammoType != ammoTypeNames.end()) {
732 return ammoType->second;
733 }
734 return AMMO_NONE;
735}
736
737WeaponAction_t getWeaponAction(const std::string& strValue)
738{
739 auto weaponAction = weaponActionNames.find(strValue);
740 if (weaponAction != weaponActionNames.end()) {
741 return weaponAction->second;
742 }
743 return WEAPONACTION_NONE;
744}
745
746Skulls_t getSkullType(const std::string& strValue)
747{
748 auto skullType = skullNames.find(strValue);
749 if (skullType != skullNames.end()) {
750 return skullType->second;
751 }
752 return SKULL_NONE;
753}
754
755std::string getSpecialSkillName(uint8_t skillid)
756{
757 switch (skillid) {
758 case SPECIALSKILL_CRITICALHITCHANCE:
759 return "critical hit chance";
760
761 case SPECIALSKILL_CRITICALHITAMOUNT:
762 return "critical extra damage";
763
764 case SPECIALSKILL_LIFELEECHCHANCE:
765 return "hitpoints leech chance";
766
767 case SPECIALSKILL_LIFELEECHAMOUNT:
768 return "hitpoints leech amount";
769
770 case SPECIALSKILL_MANALEECHCHANCE:
771 return "manapoints leech chance";
772
773 case SPECIALSKILL_MANALEECHAMOUNT:
774 return "mana points leech amount";
775
776 default:
777 return "unknown";
778 }
779}
780
781std::string getSkillName(uint8_t skillid)
782{
783 switch (skillid) {
784 case SKILL_FIST:
785 return "shotgun combat";
786
787 case SKILL_CLUB:
788 return "machine-gun combat";
789
790 case SKILL_SWORD:
791 return "rifle combat";
792
793 case SKILL_AXE:
794 return "pistol combat";
795
796 case SKILL_DISTANCE:
797 return "skill distance";
798
799 case SKILL_SHIELD:
800 return "shielding";
801
802 case SKILL_FISHING:
803 return "fishing";
804
805 case SKILL_MAGLEVEL:
806 return "magic level";
807
808 case SKILL_LEVEL:
809 return "level";
810
811 default:
812 return "unknown";
813 }
814}
815
816uint32_t adlerChecksum(const uint8_t* data, size_t length)
817{
818 if (length > NETWORKMESSAGE_MAXSIZE) {
819 return 0;
820 }
821
822 const uint16_t adler = 65521;
823
824 uint32_t a = 1, b = 0;
825
826 while (length > 0) {
827 size_t tmp = length > 5552 ? 5552 : length;
828 length -= tmp;
829
830 do {
831 a += *data++;
832 b += a;
833 } while (--tmp);
834
835 a %= adler;
836 b %= adler;
837 }
838
839 return (b << 16) | a;
840}
841
842std::string ucfirst(std::string str)
843{
844 for (char& i : str) {
845 if (i != ' ') {
846 i = toupper(i);
847 break;
848 }
849 }
850 return str;
851}
852
853std::string ucwords(std::string str)
854{
855 size_t strLength = str.length();
856 if (strLength == 0) {
857 return str;
858 }
859
860 str[0] = toupper(str.front());
861 for (size_t i = 1; i < strLength; ++i) {
862 if (str[i - 1] == ' ') {
863 str[i] = toupper(str[i]);
864 }
865 }
866
867 return str;
868}
869
870bool booleanString(const std::string& str)
871{
872 if (str.empty()) {
873 return false;
874 }
875
876 char ch = tolower(str.front());
877 return ch != 'f' && ch != 'n' && ch != '0';
878}
879
880std::string getWeaponName(WeaponType_t weaponType)
881{
882 switch (weaponType) {
883 case WEAPON_SWORD: return "sword";
884 case WEAPON_CLUB: return "club";
885 case WEAPON_AXE: return "axe";
886 case WEAPON_DISTANCE: return "distance";
887 case WEAPON_WAND: return "wand";
888 case WEAPON_AMMO: return "ammunition";
889 default: return std::string();
890 }
891}
892
893size_t combatTypeToIndex(CombatType_t combatType)
894{
895 switch (combatType) {
896 case COMBAT_PHYSICALDAMAGE:
897 return 0;
898 case COMBAT_ENERGYDAMAGE:
899 return 1;
900 case COMBAT_EARTHDAMAGE:
901 return 2;
902 case COMBAT_FIREDAMAGE:
903 return 3;
904 case COMBAT_UNDEFINEDDAMAGE:
905 return 4;
906 case COMBAT_LIFEDRAIN:
907 return 5;
908 case COMBAT_MANADRAIN:
909 return 6;
910 case COMBAT_HEALING:
911 return 7;
912 case COMBAT_DROWNDAMAGE:
913 return 8;
914 case COMBAT_ICEDAMAGE:
915 return 9;
916 case COMBAT_HOLYDAMAGE:
917 return 10;
918 case COMBAT_DEATHDAMAGE:
919 return 11;
920 default:
921 return 0;
922 }
923}
924
925CombatType_t indexToCombatType(size_t v)
926{
927 return static_cast<CombatType_t>(1 << v);
928}
929
930uint8_t serverFluidToClient(uint8_t serverFluid)
931{
932 uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t);
933 for (uint8_t i = 0; i < size; ++i) {
934 if (clientToServerFluidMap[i] == serverFluid) {
935 return i;
936 }
937 }
938 return 0;
939}
940
941uint8_t clientFluidToServer(uint8_t clientFluid)
942{
943 uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t);
944 if (clientFluid >= size) {
945 return 0;
946 }
947 return clientToServerFluidMap[clientFluid];
948}
949
950itemAttrTypes stringToItemAttribute(const std::string& str)
951{
952 if (str == "aid") {
953 return ITEM_ATTRIBUTE_ACTIONID;
954 } else if (str == "uid") {
955 return ITEM_ATTRIBUTE_UNIQUEID;
956 } else if (str == "description") {
957 return ITEM_ATTRIBUTE_DESCRIPTION;
958 } else if (str == "text") {
959 return ITEM_ATTRIBUTE_TEXT;
960 } else if (str == "date") {
961 return ITEM_ATTRIBUTE_DATE;
962 } else if (str == "writer") {
963 return ITEM_ATTRIBUTE_WRITER;
964 } else if (str == "name") {
965 return ITEM_ATTRIBUTE_NAME;
966 } else if (str == "article") {
967 return ITEM_ATTRIBUTE_ARTICLE;
968 } else if (str == "pluralname") {
969 return ITEM_ATTRIBUTE_PLURALNAME;
970 } else if (str == "weight") {
971 return ITEM_ATTRIBUTE_WEIGHT;
972 } else if (str == "attack") {
973 return ITEM_ATTRIBUTE_ATTACK;
974 } else if (str == "defense") {
975 return ITEM_ATTRIBUTE_DEFENSE;
976 } else if (str == "extradefense") {
977 return ITEM_ATTRIBUTE_EXTRADEFENSE;
978 } else if (str == "armor") {
979 return ITEM_ATTRIBUTE_ARMOR;
980 } else if (str == "hitchance") {
981 return ITEM_ATTRIBUTE_HITCHANCE;
982 } else if (str == "shootrange") {
983 return ITEM_ATTRIBUTE_SHOOTRANGE;
984 } else if (str == "owner") {
985 return ITEM_ATTRIBUTE_OWNER;
986 } else if (str == "duration") {
987 return ITEM_ATTRIBUTE_DURATION;
988 } else if (str == "decaystate") {
989 return ITEM_ATTRIBUTE_DECAYSTATE;
990 } else if (str == "corpseowner") {
991 return ITEM_ATTRIBUTE_CORPSEOWNER;
992 } else if (str == "charges") {
993 return ITEM_ATTRIBUTE_CHARGES;
994 } else if (str == "fluidtype") {
995 return ITEM_ATTRIBUTE_FLUIDTYPE;
996 } else if (str == "doorid") {
997 return ITEM_ATTRIBUTE_DOORID;
998 } else if (str == "wrapid") {
999 return ITEM_ATTRIBUTE_WRAPID;
1000 }
1001 return ITEM_ATTRIBUTE_NONE;
1002}
1003
1004std::string getFirstLine(const std::string& str)
1005{
1006 std::string firstLine;
1007 firstLine.reserve(str.length());
1008 for (const char c : str) {
1009 if (c == '\n') {
1010 break;
1011 }
1012 firstLine.push_back(c);
1013 }
1014 return firstLine;
1015}
1016
1017const char* getReturnMessage(ReturnValue value)
1018{
1019 switch (value) {
1020 case RETURNVALUE_DESTINATIONOUTOFREACH:
1021 return "Destination is out of range.";
1022
1023 case RETURNVALUE_NOTMOVEABLE:
1024 return "You cannot move this object.";
1025
1026 case RETURNVALUE_DROPTWOHANDEDITEM:
1027 return "Drop the double-handed object first.";
1028
1029 case RETURNVALUE_BOTHHANDSNEEDTOBEFREE:
1030 return "Both hands need to be free.";
1031
1032 case RETURNVALUE_CANNOTBEDRESSED:
1033 return "You cannot dress this object there.";
1034
1035 case RETURNVALUE_PUTTHISOBJECTINYOURHAND:
1036 return "Put this object in your hand.";
1037
1038 case RETURNVALUE_PUTTHISOBJECTINBOTHHANDS:
1039 return "Put this object in both hands.";
1040
1041 case RETURNVALUE_CANONLYUSEONEWEAPON:
1042 return "You may only use one weapon.";
1043
1044 case RETURNVALUE_TOOFARAWAY:
1045 return "You are too far away.";
1046
1047 case RETURNVALUE_FIRSTGODOWNSTAIRS:
1048 return "First go downstairs.";
1049
1050 case RETURNVALUE_FIRSTGOUPSTAIRS:
1051 return "First go upstairs.";
1052
1053 case RETURNVALUE_NOTENOUGHCAPACITY:
1054 return "This object is too heavy for you to carry.";
1055
1056 case RETURNVALUE_CONTAINERNOTENOUGHROOM:
1057 return "You cannot put more objects in this container.";
1058
1059 case RETURNVALUE_NEEDEXCHANGE:
1060 case RETURNVALUE_NOTENOUGHROOM:
1061 return "There is not enough room.";
1062
1063 case RETURNVALUE_CANNOTPICKUP:
1064 return "You cannot take this object.";
1065
1066 case RETURNVALUE_CANNOTTHROW:
1067 return "You cannot throw there.";
1068
1069 case RETURNVALUE_THEREISNOWAY:
1070 return "There is no way.";
1071
1072 case RETURNVALUE_THISISIMPOSSIBLE:
1073 return "This is impossible.";
1074
1075 case RETURNVALUE_PLAYERISPZLOCKED:
1076 return "You can not enter a protection zone after attacking another player.";
1077
1078 case RETURNVALUE_PLAYERISNOTINVITED:
1079 return "You are not invited.";
1080
1081 case RETURNVALUE_CREATUREDOESNOTEXIST:
1082 return "Creature does not exist.";
1083
1084 case RETURNVALUE_DEPOTISFULL:
1085 return "You cannot put more items in this depot.";
1086
1087 case RETURNVALUE_CANNOTUSETHISOBJECT:
1088 return "You cannot use this object.";
1089
1090 case RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE:
1091 return "A player with this name is not online.";
1092
1093 case RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE:
1094 return "You do not have the required magic level to use this rune.";
1095
1096 case RETURNVALUE_YOUAREALREADYTRADING:
1097 return "You are already trading.";
1098
1099 case RETURNVALUE_THISPLAYERISALREADYTRADING:
1100 return "This player is already trading.";
1101
1102 case RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT:
1103 return "You may not logout during or immediately after a fight!";
1104
1105 case RETURNVALUE_DIRECTPLAYERSHOOT:
1106 return "You are not allowed to shoot directly on players.";
1107
1108 case RETURNVALUE_NOTENOUGHLEVEL:
1109 return "Your level is too low.";
1110
1111 case RETURNVALUE_NOTENOUGHMAGICLEVEL:
1112 return "You do not have enough magic level.";
1113
1114 case RETURNVALUE_NOTENOUGHMANA:
1115 return "You do not have enough mana.";
1116
1117 case RETURNVALUE_NOTENOUGHSOUL:
1118 return "You do not have enough soul.";
1119
1120 case RETURNVALUE_YOUAREEXHAUSTED:
1121 return "You are exhausted.";
1122
1123 case RETURNVALUE_YOUCANNOTUSEOBJECTSTHATFAST:
1124 return "You cannot use objects that fast.";
1125
1126 case RETURNVALUE_CANONLYUSETHISRUNEONCREATURES:
1127 return "You can only use it on creatures.";
1128
1129 case RETURNVALUE_PLAYERISNOTREACHABLE:
1130 return "Player is not reachable.";
1131
1132 case RETURNVALUE_CREATUREISNOTREACHABLE:
1133 return "Creature is not reachable.";
1134
1135 case RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE:
1136 return "This action is not permitted in a protection zone.";
1137
1138 case RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER:
1139 return "You may not attack this person.";
1140
1141 case RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE:
1142 return "You may not attack this creature.";
1143
1144 case RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE:
1145 return "You may not attack a person in a protection zone.";
1146
1147 case RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE:
1148 return "You may not attack a person while you are in a protection zone.";
1149
1150 case RETURNVALUE_YOUCANONLYUSEITONCREATURES:
1151 return "You can only use it on creatures.";
1152
1153 case RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS:
1154 return "Turn secure mode off if you really want to attack unmarked players.";
1155
1156 case RETURNVALUE_YOUNEEDPREMIUMACCOUNT:
1157 return "You need a premium account.";
1158
1159 case RETURNVALUE_YOUNEEDTOLEARNTHISSPELL:
1160 return "You must learn this spell first.";
1161
1162 case RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL:
1163 return "You have the wrong vocation to cast this spell.";
1164
1165 case RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL:
1166 return "You need to equip a weapon to use this spell.";
1167
1168 case RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE:
1169 return "You can not leave a pvp zone after attacking another player.";
1170
1171 case RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE:
1172 return "You can not enter a pvp zone after attacking another player.";
1173
1174 case RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE:
1175 return "This action is not permitted in a non pvp zone.";
1176
1177 case RETURNVALUE_YOUCANNOTLOGOUTHERE:
1178 return "You can not logout here.";
1179
1180 case RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL:
1181 return "You need a magic item to cast this spell.";
1182
1183 case RETURNVALUE_CANNOTCONJUREITEMHERE:
1184 return "You cannot conjure items here.";
1185
1186 case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS:
1187 return "You need to split your spears first.";
1188
1189 case RETURNVALUE_NAMEISTOOAMBIGUOUS:
1190 return "Player name is ambiguous.";
1191
1192 case RETURNVALUE_CANONLYUSEONESHIELD:
1193 return "You may use only one shield.";
1194
1195 case RETURNVALUE_NOPARTYMEMBERSINRANGE:
1196 return "No party members in range.";
1197
1198 case RETURNVALUE_YOUARENOTTHEOWNER:
1199 return "You are not the owner.";
1200
1201 case RETURNVALUE_NOSUCHRAIDEXISTS:
1202 return "No such raid exists.";
1203
1204 case RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING:
1205 return "Another raid is already executing.";
1206
1207 case RETURNVALUE_TRADEPLAYERFARAWAY:
1208 return "Trade player is too far away.";
1209
1210 case RETURNVALUE_YOUDONTOWNTHISHOUSE:
1211 return "You don't own this house.";
1212
1213 case RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE:
1214 return "Trade player already owns a house.";
1215
1216 case RETURNVALUE_TRADEPLAYERHIGHESTBIDDER:
1217 return "Trade player is currently the highest bidder of an auctioned house.";
1218
1219 case RETURNVALUE_YOUCANNOTTRADETHISHOUSE:
1220 return "You can not trade this house.";
1221
1222 case RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION:
1223 return "You don't have the required profession.";
1224
1225 default: // RETURNVALUE_NOTPOSSIBLE, etc
1226 return "Sorry, not possible.";
1227 }
1228}
1229
1230int64_t OTSYS_TIME()
1231{
1232 return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
1233}
1234
1235SpellGroup_t stringToSpellGroup(const std::string& value)
1236{
1237 std::string tmpStr = asLowerCaseString(value);
1238 if (tmpStr == "attack" || tmpStr == "1") {
1239 return SPELLGROUP_ATTACK;
1240 } else if (tmpStr == "healing" || tmpStr == "2") {
1241 return SPELLGROUP_HEALING;
1242 } else if (tmpStr == "support" || tmpStr == "3") {
1243 return SPELLGROUP_SUPPORT;
1244 } else if (tmpStr == "special" || tmpStr == "4") {
1245 return SPELLGROUP_SPECIAL;
1246 }
1247
1248 return SPELLGROUP_NONE;
1249}
1250