RPFW.0.0a Build 5.pwn 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072
  1. // Developers notes
  2. /* # Coding guideline
  3. * Add +1 at the end of array declarations to visually account for null bit.
  4. * Always log first.
  5. * Always notify discord last.
  6. * Always wait for other queries to finish before initiating one: "sql_wait(sqlHandle); // Wait for other queries to finish"
  7. * Delete GVar strings as soon as possible, but at least before the player quits or the gamemode exits, as per: https://forum.sa-mp.com/showthread.php?t=151076
  8. */
  9. /* # Style guide
  10. * GLOBAL_CONSTANT
  11. * local_variable
  12. * someFunction
  13. * SomeClass
  14. * playerid // Variable storing in-game player ID
  15. * player_id // Variable storing database player ID
  16. */
  17. /* # Adding a discord bot to your guild.
  18. https://discordapp.com/oauth2/authorize?client_id=%CLIENT_ID%&scope=bot&permissions=3072
  19. Where %CLEINT_ID% is the client id.
  20. */
  21. /* # To do:
  22. * Add geoiplib, to make connect messages fancier, and add a locate command.
  23. * Show underscores as spaces and convert spaces in changename input to undescores before regex chack.
  24. * Vip limit on characters.
  25. * See why sql_insert_id wont work.
  26. * Add all ADMIN_ECHO_CHANNEL messages to admin chat (not the real admin chat)
  27. */
  28. /// Global definitions
  29. // Colours
  30. /*
  31. #define COLOR_GREEN 0x33AA33AA
  32. #define COLOR_YELLOW 0xFFFF00AA
  33. #define COLOR_WHITE 0xFFFFFFAA
  34. #define COLOR_BLUE 0x0000BBAA
  35. #define COLOR_LIGHTBLUE 0x33CCFFAA
  36. #define COLOR_ORANGE 0xFF9900AA
  37. #define COLOR_RED 0xAA3333AA
  38. #define COLOR_LIME 0x10F441AA
  39. #define COLOR_NAVY 0x000080AA
  40. #define COLOR_AQUA 0xF0F8FFAA
  41. #define COLOR_CRIMSON 0xDC143CAA
  42. #define COLOR_BISQUE 0xFFE4C4AA
  43. #define COLOR_BLACK 0x000000AA
  44. #define COLOR_CHARTREUSE 0x7FFF00AA
  45. #define COLOR_BROWN 0XA52A2AAA
  46. #define COLOR_CORAL 0xFF7F50AA
  47. #define COLOR_GOLD 0xB8860BAA
  48. #define COLOR_GREENYELLOW 0xADFF2FAA
  49. #define COLOR_INDIGO 0x4B00B0AA
  50. #define COLOR_IVORY 0xFFFF82AA
  51. #define COLOR_LAWNGREEN 0x7CFC00AA
  52. #define COLOR_SEAGREEN 0x20B2AAAA
  53. #define COLOR_SEAGREEN 0x2E8B57AA
  54. #define COLOR_LIMEGREEN 0x32CD32AA //<--- Dark lime
  55. #define COLOR_MIDNIGHTBLUE 0X191970AA
  56. #define COLOR_MAROON 0x800000AA
  57. #define COLOR_OLIVE 0x808000AA
  58. #define COLOR_ORANGERED 0xFF4500AA
  59. #define COLOR_PINK 0xFFC0CBAA // - Light light pink
  60. #define COLOR_SPRINGGREEN 0x00FF7FAA
  61. #define COLOR_TOMATO 0xFF6347AA // - Tomato >:/ sounds wrong lol... well... :P
  62. #define COLOR_YELLOWGREEN 0x9ACD32AA //- like military green
  63. #define COLOR_MEDIUMAQUA 0x83BFBFAA
  64. #define COLOR_MEDIUMMAGENTA 0x8B008BAA // dark magenta ^^
  65. */
  66. #define COLOR_ACTIVEBORDER 0xB4B4B4FF
  67. #define COLOR_ACTIVECAPTION 0x99B4D1FF
  68. #define COLOR_ACTIVECAPTIONTEXT 0x000000FF
  69. #define COLOR_ALICEBLUE 0xF0F8FFFF
  70. #define COLOR_ANTIQUEWHITE 0xFAEBD7FF
  71. #define COLOR_APPWORKSPACE 0xABABABFF
  72. #define COLOR_AQUA 0x00FFFFFF
  73. #define COLOR_AQUAMARINE 0x7FFFD4FF
  74. #define COLOR_AZURE 0xF0FFFFFF
  75. #define COLOR_BEIGE 0xF5F5DCFF
  76. #define COLOR_BISQUE 0xFFE4C4FF
  77. #define COLOR_BLACK 0x000000FF
  78. #define COLOR_BLANCHEDALMOND 0xFFEBCDFF
  79. #define COLOR_BLUE 0x0000FFFF
  80. #define COLOR_BLUEVIOLET 0x8A2BE2FF
  81. #define COLOR_BROWN 0xA52A2AFF
  82. #define COLOR_BURLYWOOD 0xDEB887FF
  83. #define COLOR_BUTTONFACE 0xF0F0F0FF
  84. #define COLOR_BUTTONHIGHLIGHT 0xFFFFFFFF
  85. #define COLOR_BUTTONSHADOW 0xA0A0A0FF
  86. #define COLOR_CADETBLUE 0x5F9EA0FF
  87. #define COLOR_CHARTREUSE 0x7FFF00FF
  88. #define COLOR_CHOCOLATE 0xD2691EFF
  89. #define COLOR_CONTROL 0xF0F0F0FF
  90. #define COLOR_CONTROLDARK 0xA0A0A0FF
  91. #define COLOR_CONTROLDARKDARK 0x696969FF
  92. #define COLOR_CONTROLLIGHT 0xE3E3E3FF
  93. #define COLOR_CONTROLLIGHTLIGHT 0xFFFFFFFF
  94. #define COLOR_CONTROLTEXT 0x000000FF
  95. #define COLOR_CORAL 0xFF7F50FF
  96. #define COLOR_CORNFLOWERBLUE 0x6495EDFF
  97. #define COLOR_CORNSILK 0xFFF8DCFF
  98. #define COLOR_CRIMSON 0xDC143CFF
  99. #define COLOR_CYAN 0x00FFFFFF
  100. #define COLOR_DARKBLUE 0x00008BFF
  101. #define COLOR_DARKCYAN 0x008B8BFF
  102. #define COLOR_DARKGOLDENROD 0xB8860BFF
  103. #define COLOR_DARKGRAY 0xA9A9A9FF
  104. #define COLOR_DARKGREEN 0x006400FF
  105. #define COLOR_DARKKHAKI 0xBDB76BFF
  106. #define COLOR_DARKMAGENTA 0x8B008BFF
  107. #define COLOR_DARKOLIVEGREEN 0x556B2FFF
  108. #define COLOR_DARKORANGE 0xFF8C00FF
  109. #define COLOR_DARKORCHID 0x9932CCFF
  110. #define COLOR_DARKRED 0x8B0000FF
  111. #define COLOR_DARKSALMON 0xE9967AFF
  112. #define COLOR_DARKSEAGREEN 0x8FBC8BFF
  113. #define COLOR_DARKSLATEBLUE 0x483D8BFF
  114. #define COLOR_DARKSLATEGRAY 0x2F4F4FFF
  115. #define COLOR_DARKTURQUOISE 0x00CED1FF
  116. #define COLOR_DARKVIOLET 0x9400D3FF
  117. #define COLOR_DEEPPINK 0xFF1493FF
  118. #define COLOR_DEEPSKYBLUE 0x00BFFFFF
  119. #define COLOR_DEFAULT_CHAT 0xFFFFFFFF
  120. #define COLOR_DESKTOP 0x000000FF
  121. #define COLOR_DIMGRAY 0x696969FF
  122. #define COLOR_DODGERBLUE 0x1E90FFFF
  123. #define COLOR_FIREBRICK 0xB22222FF
  124. #define COLOR_FLBLUE 0x6495EDAA
  125. #define COLOR_FLORALWHITE 0xFFFAF0FF
  126. #define COLOR_FORESTGREEN 0x228B22FF
  127. #define COLOR_GAINSBORO 0xDCDCDCFF
  128. #define COLOR_GHOSTWHITE 0xF8F8FFFF
  129. #define COLOR_GOLD 0xFFD700FF
  130. #define COLOR_GOLDENROD 0xDAA520FF
  131. #define COLOR_GRADIENTACTIVECAPTION 0xB9D1EAFF
  132. #define COLOR_GRADIENTINACTIVECAPTION 0xD7E4F2FF
  133. #define COLOR_GRAY 0x808080FF
  134. #define COLOR_GRAYTEXT 0x808080FF
  135. #define COLOR_GREEN 0x008000FF
  136. #define COLOR_GREENYELLOW 0xADFF2FFF
  137. #define COLOR_GREY 0xAFAFAFAA
  138. #define COLOR_HIGHLIGHT 0x3399FFFF
  139. #define COLOR_HIGHLIGHTTEXT 0xFFFFFFFF
  140. #define COLOR_HONEYDEW 0xF0FFF0FF
  141. #define COLOR_HOTPINK 0xFF69B4FF
  142. #define COLOR_HOTTRACK 0x0066CCFF
  143. #define COLOR_INACTIVEBORDER 0xF4F7FCFF
  144. #define COLOR_INACTIVECAPTION 0xBFCDDBFF
  145. #define COLOR_INACTIVECAPTIONTEXT 0x434E54FF
  146. #define COLOR_INDIANRED 0xCD5C5CFF
  147. #define COLOR_INDIGO 0x4B0082FF
  148. #define COLOR_INFO 0xFFFFE1FF
  149. #define COLOR_INFOTEXT 0x000000FF
  150. #define COLOR_IVORY 0xFFFFF0FF
  151. #define COLOR_KHAKI 0xF0E68CFF
  152. #define COLOR_LAVENDER 0xE6E6FAFF
  153. #define COLOR_LAVENDERBLUSH 0xFFF0F5FF
  154. #define COLOR_LAWNGREEN 0x7CFC00FF
  155. #define COLOR_LEMONCHIFFON 0xFFFACDFF
  156. #define COLOR_LIGHTBLUE 0xADD8E6FF
  157. #define COLOR_LIGHTCORAL 0xF08080FF
  158. #define COLOR_LIGHTCYAN 0xE0FFFFFF
  159. #define COLOR_LIGHTGOLDENRODYELLOW 0xFAFAD2FF
  160. #define COLOR_LIGHTGRAY 0xD3D3D3FF
  161. #define COLOR_LIGHTGREEN 0x90EE90FF
  162. #define COLOR_LIGHTPINK 0xFFB6C1FF
  163. #define COLOR_LIGHTSALMON 0xFFA07AFF
  164. #define COLOR_LIGHTSEAGREEN 0x20B2AAFF
  165. #define COLOR_LIGHTSKYBLUE 0x87CEFAFF
  166. #define COLOR_LIGHTSLATEGRAY 0x778899FF
  167. #define COLOR_LIGHTSTEELBLUE 0xB0C4DEFF
  168. #define COLOR_LIGHTYELLOW 0xFFFFE0FF
  169. #define COLOR_LIME 0x00FF00FF
  170. #define COLOR_LIMEGREEN 0x32CD32FF
  171. #define COLOR_LINEN 0xFAF0E6FF
  172. #define COLOR_MAGENTA 0xFF00FFFF
  173. #define COLOR_MAROON 0x800000FF
  174. #define COLOR_MEDIUMAQUAMARINE 0x66CDAAFF
  175. #define COLOR_MEDIUMBLUE 0x0000CDFF
  176. #define COLOR_MEDIUMORCHID 0xBA55D3FF
  177. #define COLOR_MEDIUMPURPLE 0x9370DBFF
  178. #define COLOR_MEDIUMSEAGREEN 0x3CB371FF
  179. #define COLOR_MEDIUMSLATEBLUE 0x7B68EEFF
  180. #define COLOR_MEDIUMSPRINGGREEN 0x00FA9AFF
  181. #define COLOR_MEDIUMTURQUOISE 0x48D1CCFF
  182. #define COLOR_MEDIUMVIOLETRED 0xC71585FF
  183. #define COLOR_MENU 0xF0F0F0FF
  184. #define COLOR_MENUBAR 0xF0F0F0FF
  185. #define COLOR_MENUHIGHLIGHT 0x3399FFFF
  186. #define COLOR_MENUTEXT 0x000000FF
  187. #define COLOR_MIDNIGHTBLUE 0x191970FF
  188. #define COLOR_MINTCREAM 0xF5FFFAFF
  189. #define COLOR_MISTYROSE 0xFFE4E1FF
  190. #define COLOR_MOCCASIN 0xFFE4B5FF
  191. #define COLOR_NAVAJOWHITE 0xFFDEADFF
  192. #define COLOR_NAVY 0x000080FF
  193. #define COLOR_OLDLACE 0xFDF5E6FF
  194. #define COLOR_OLIVE 0x808000FF
  195. #define COLOR_OLIVEDRAB 0x6B8E23FF
  196. #define COLOR_ORANGE 0xFFA500FF
  197. #define COLOR_ORANGERED 0xFF4500FF
  198. #define COLOR_ORCHID 0xDA70D6FF
  199. #define COLOR_PALEGOLDENROD 0xEEE8AAFF
  200. #define COLOR_PALEGREEN 0x98FB98FF
  201. #define COLOR_PALETURQUOISE 0xAFEEEEFF
  202. #define COLOR_PALEVIOLETRED 0xDB7093FF
  203. #define COLOR_PAPAYAWHIP 0xFFEFD5FF
  204. #define COLOR_PEACHPUFF 0xFFDAB9FF
  205. #define COLOR_PERU 0xCD853FFF
  206. #define COLOR_PINK 0xFFC0CBFF
  207. #define COLOR_PLUM 0xDDA0DDFF
  208. #define COLOR_POWDERBLUE 0xB0E0E6FF
  209. #define COLOR_PURPLE 0x800080FF
  210. #define COLOR_RED 0xFF0000FF
  211. #define COLOR_ROSYBROWN 0xBC8F8FFF
  212. #define COLOR_ROYALBLUE 0x4169E1FF
  213. #define COLOR_SADDLEBROWN 0x8B4513FF
  214. #define COLOR_SALMON 0xFA8072FF
  215. #define COLOR_SANDYBROWN 0xF4A460FF
  216. #define COLOR_SCROLLBAR 0xC8C8C8FF
  217. #define COLOR_SEAGREEN 0x2E8B57FF
  218. #define COLOR_SEASHELL 0xFFF5EEFF
  219. #define COLOR_SIENNA 0xA0522DFF
  220. #define COLOR_SILVER 0xC0C0C0FF
  221. #define COLOR_SKYBLUE 0x87CEEBFF
  222. #define COLOR_SLATEBLUE 0x6A5ACDFF
  223. #define COLOR_SLATEGRAY 0x708090FF
  224. #define COLOR_SNOW 0xFFFAFAFF
  225. #define COLOR_SPRINGGREEN 0x00FF7FFF
  226. #define COLOR_STEELBLUE 0x4682B4FF
  227. #define COLOR_TAN 0xD2B48CFF
  228. #define COLOR_TEAL 0x008080FF
  229. #define COLOR_THISTLE 0xD8BFD8FF
  230. #define COLOR_TOMATO 0xFF6347FF
  231. #define COLOR_TRANSPARENT 0xFFFFFF00
  232. #define COLOR_TURQUOISE 0x40E0D0FF
  233. #define COLOR_VIOLET 0xEE82EEFF
  234. #define COLOR_WHEAT 0xF5DEB3FF
  235. #define COLOR_WHITE 0xFFFFFFFF
  236. #define COLOR_WHITESMOKE 0xF5F5F5FF
  237. #define COLOR_WINDOW 0xFFFFFFFF
  238. #define COLOR_WINDOWFRAME 0x646464FF
  239. #define COLOR_WINDOWTEXT 0x000000FF
  240. #define COLOR_YELLOW 0xFFFF00FF
  241. #define COLOR_YELLOWGREEN 0x9ACD32FF
  242. #define STEALTH_ORANGE 0xFF880000
  243. #define STEALTH_OLIVE 0x66660000
  244. #define STEALTH_GREEN 0x33DD1100
  245. #define STEALTH_PINK 0xFF22EE00
  246. #define STEALTH_BLUE 0x0077BB00
  247. // Color groups
  248. #define COLOR_DEFAULT_COMMAND_OUTPUT 0xFFFFFFFF // White
  249. #define COLOR_NOTICE 0xAFAFAFAA // Grey
  250. #define COLOR_WARNING_MESSAGE 0xFFA500FF // Orange? (Looks more yellow to me)
  251. #define COLOR_ERROR_MESSAGE 0xFF0000FF // Red
  252. #define COLOR_GLOBAL_CHAT 0xFFFFFFFF // White
  253. #define COLOR_PM_CHAT 0xFFFF00FF // Yellow
  254. #define COLOR_VIP_CHAT 0x800080FF // Purple
  255. #define COLOR_CREW_CHAT 0xFF9900AA // Orange
  256. #define COLOR_ADMIN_CHAT 0xB8860BAA // Gold
  257. // SQL datatypes
  258. #define SQL_INTERGER_LENGTH 10
  259. #define TIMESTAMP_LENGTH 19 // 1999-01-08 04:05:06 [5 + 4 + 2 + 2 +2 + 2 + 2]
  260. #define SQL_FLOAT_LENGTH 16
  261. #define HASH_LENGTH 128
  262. #define IP_LEGNTH 45
  263. #define REASON_LENGTH 121
  264. // Log levels
  265. #define LOGLEVEL_CHAT -2
  266. #define LOGLEVEL_COMMAND -1
  267. #define LOGLEVEL_DEBUG 0
  268. #define LOGLEVEL_INFO 1
  269. #define LOGLEVEL_NOTICE 2
  270. #define LOGLEVEL_WARNING 3
  271. #define LOGLEVEL_ERROR 4
  272. #define LOGLEVEL_CRITICAL 5
  273. #define LOGLEVEL_PANIC 6
  274. // Userlevels
  275. enum{ // Noted values as comments, for database reference.
  276. UNREGISTERED_PLAYER, // 0
  277. REGISTERED_PLAYER, // 1
  278. REGULAR_PLAYER, // 2
  279. VIP_PLAYER, // 3
  280. MODERATOR_CREW, // 4
  281. VETERAN_CREW, // 5 Inactive admins and management that are allowed to keep their rights.
  282. ADMIN_CREW, // 6
  283. UNDERCOVER_ADMIN_CREW, // 7
  284. MANAGEMENT_CREW, // 8
  285. UNDRECOVER_MANAGEMENT_CREW, // 9
  286. FOUNDER_PLAYER, // 10
  287. UNDERCOVER_FOUNDER_PLAYER // 11
  288. }
  289. // Discord channels
  290. enum{
  291. ECHO_CHANNEL,
  292. MAIN_CHANNEL,
  293. ADMIN_ECHO_CHANNEL,
  294. ADMIN_CHANNEL,
  295. MANAGEMENT_CHANNEL
  296. }
  297. // Chats
  298. enum{
  299. CHAT_LOCAL, // Has to be 0, else a gVar would have to be created OnJoin, this conserves memory when no chatmode is set (which will be the case most likely)
  300. CHAT_WHISPER,
  301. CHAT_LOW,
  302. CHAT_ACTION,
  303. CHAT_SHOUT,
  304. CHAT_OC,
  305. CHAT_GLOBAL,
  306. CHAT_CALL,
  307. CHAT_SMS,
  308. CHAT_RADIO,
  309. CHAT_PM,
  310. CHAT_GANG,
  311. CHAT_GANG_OC,
  312. CHAT_FACTION,
  313. CHAT_FACTION_OC,
  314. CHAT_VIP,
  315. CHAT_CREW,
  316. CHAT_ADMIN,
  317. CHAT_MANAGEMENT,
  318. CHAT_PARTYLINE
  319. }
  320. // Dialogs
  321. enum{
  322. DIALOG_CHANGENAME,
  323. DIALOG_REGISTER,
  324. DIALOG_ACCOUNT_CREATED,
  325. DIALOG_LOGIN,
  326. DIALOG_CHANGE_USERNAME,
  327. DIALOG_CHARACTERS,
  328. DIALOG_LOGIN_FAILED,
  329. DIALOG_DELETE_CHARACTER
  330. }
  331. // Environment settings THESE SHOULD ALL BE READ FROM A CONFIG FILE!
  332. static bool:scriptDebug = true; // Debug setting
  333. #define MODE_NAME "0.0a Build 5"
  334. #define SERVER_NAME "Bone County RPG"
  335. #define PG_HOST "127.0.0.1"
  336. #define PG_ROLE "rpfw-dev"
  337. #define PG_PASS "nJd&1k$0fs"
  338. #define PG_DB "rpfw-dev"
  339. #define PG_PORT 5432
  340. #define DISCORD_HOME_GUILD_ID "666077037470941184" // Emerald City Roleplay
  341. #define DISCORD_ECHO_CHANNEL_ID "677855051166777344" // #bcrp-echo
  342. //#define DISCORD_ECHO_CHANNEL_ID "666078187079598080" // #ecrp-echo
  343. #define DISCORD_MAIN_CHANNEL_ID "677855315898793984" // #bcrp
  344. //#define DISCORD_MAIN_CHANNEL_ID "667396220402270228" // #development
  345. #define DISCORD_ADMIN_ECHO_CHANNEL_ID "672841892169383936" // #admin-echo
  346. #define DISCORD_ADMIN_CHANNEL_ID "667396026058932236" // #admins
  347. #define DISCORD_MANAGEMENT_CHANNEL_ID "666091376240361492" // #management
  348. // Game-mode limits
  349. #define MAX_CHARACTERS_PER_USER 20 // Maximum of MAX_CHARACTERS_PER_USER_DIGITS digits (Due to SQL query string length)
  350. #define MAX_CHARACTERS_PER_USER_DIGITS 3 // TODO user sizeof() and possibly move to OnGamemodeInit
  351. // SQL plugin
  352. new SQL:sqlHandle;
  353. // discord-connector
  354. //static DCC_Guild:homeGuild; // Discord guild controlled by game community.
  355. static DCC_Channel:echoChannel; // Public echo channel.
  356. static DCC_Channel:mainChannel; // Channel for communirty notifications.
  357. static DCC_Channel:adminEchoChannel; // Admin echo channel.
  358. static DCC_Channel:adminChannel; // Channel for admin notifications.
  359. static DCC_Channel:managementChannel; // Channel for management notifications.
  360. DiscordEcho(const message[], messageLevel){ // Write to echo channels.
  361. switch(messageLevel) // Output facilities.
  362. {
  363. case ECHO_CHANNEL: { // Public echo message
  364. DiscordSendChannelMessage(echoChannel, message);
  365. DiscordSendChannelMessage(adminEchoChannel, message); // Also send to admin echo channel, so admin can see everything in one channel without needing to constantly switch. Also handy as log on conflicts/complaints.
  366. }
  367. case MAIN_CHANNEL: {DiscordSendChannelMessage(mainChannel, message);} // Public notification
  368. case ADMIN_ECHO_CHANNEL: {DiscordSendChannelMessage(adminEchoChannel, message);} // Admin echo message
  369. case ADMIN_CHANNEL: {DiscordSendChannelMessage(adminChannel, message);} // Admin notification
  370. case MANAGEMENT_CHANNEL: {DiscordSendChannelMessage(managementChannel, message);} // Management notification
  371. }
  372. return 0;
  373. }
  374. // Natives
  375. native WP_Hash(buffer[], len, const str[]); // https://forum.sa-mp.com/showthread.php?t=570945
  376. //native Float:loadavg(); // https://forum.sa-mp.com/showthread.php?t=260206 LINUX ONLY
  377. // Includes
  378. #include <a_samp> // https://sa-mp.com
  379. #include <sql> // https://github.com/udan11/samp-plugin-sql (Fastest player in town and only one supporting postgreSQL) | Examples: https://pastebin.com/67y2nq2n https://github.com/udan11/samp-plugin-sql/issues/10
  380. #include <sscanf2> // Newest version: https://github.com/maddinat0r/sscanf/releases | Better readme: https://github.com/Y-Less/sscanf
  381. #include <strlib> // https://github.com/oscar-broman/strlib
  382. #include <Pawn.Regex> // https://github.com/urShadow/Pawn.Regex
  383. #include <Pawn.CMD> // https://github.com/urShadow/Pawn.CMD (Fastest player in town)
  384. #include <gvar> // https://github.com/samp-incognito/samp-gvar-plugin
  385. #include <discord-connector> // https://github.com/maddinat0r/samp-discord-connector (Only player in town)
  386. //#include <streamer> // https://github.com/samp-incognisto/samp-streamer-plugin NOT USED YET
  387. /// Middle-ware
  388. // Logging
  389. logger(const log_level, const message[]){ // Write to logging facility
  390. // Do not log commands or debug when script debugging is turned off.
  391. if(scriptDebug == false){ // Debug is off
  392. if(log_level == LOGLEVEL_COMMAND || log_level == LOGLEVEL_DEBUG){ // Command or debug message
  393. return 0; // Stop and do not log
  394. }
  395. }
  396. // Messagelevel tag
  397. new human_readable_log_level[8 + 1];
  398. switch(log_level){ // Assign log level.
  399. case LOGLEVEL_CHAT: human_readable_log_level = "chat";
  400. case LOGLEVEL_COMMAND: human_readable_log_level = "command";
  401. case LOGLEVEL_DEBUG: human_readable_log_level = "debug";
  402. case LOGLEVEL_INFO: human_readable_log_level = "info";
  403. case LOGLEVEL_NOTICE: human_readable_log_level = "notice";
  404. case LOGLEVEL_WARNING: human_readable_log_level = "warning";
  405. case LOGLEVEL_ERROR: human_readable_log_level = "error";
  406. case LOGLEVEL_CRITICAL: human_readable_log_level = "critical";
  407. case LOGLEVEL_PANIC: human_readable_log_level = "panic";
  408. }
  409. printf("[%s] %s", human_readable_log_level, message); // Print to STDOUT.
  410. return 0;
  411. }
  412. /*// SQL plugin (BROKEN, look at it again after I have more then a week of experience)
  413. //forward sqlQuery(SQL:handle, query[]);
  414. //public sqlQuery(SQL:handle, query[]){
  415. sqlQuery(SQL:handle, query[]){
  416. //sql_wait(handle); // Wait for all queries to finish.
  417. new Result:result = sql_query(handle, query);
  418. if(sql_error(result)){printf("SQL error");} // Did not work during a test with a faulty statement.
  419. return result;
  420. }*/
  421. // discord-connector
  422. DiscordSendChannelMessage(DCC_Channel:channel, const message[]){
  423. if(scriptDebug){ // Log middle-ware event in debugging mode only
  424. new channel_name[100 + 1]; // Default value from tutorial
  425. DCC_GetChannelName(channel, channel_name);
  426. new logMessage[26 + 100 + 2000 + 1]; // Discord max message length = 2000
  427. format(logMessage, sizeof(logMessage), "Send to discord channel %s: %s", channel_name, message);
  428. logger(LOGLEVEL_DEBUG, logMessage); // Actually log the message
  429. }
  430. DCC_SendChannelMessage(channel, message); // Call discord-connector DISABLE THIS TO STOP ALL OUTGOING DISCORD MESSAGES
  431. }
  432. /// Game-mode
  433. // Account functions
  434. forward changeName(playerid, const name[]);
  435. public changeName(playerid, const name[]){ // Check if name is valid and force change if needed.
  436. // Prevent regex error when comparing against null
  437. if(isempty(name)){ // No name entered
  438. ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "You must enter a name.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", ""); // Force RP name.
  439. return 0;
  440. }
  441. // Check name
  442. new Regex:r = Regex_New("^[A-Z][a-z]{1,}([A-Z][a-z]{1,})?(_([A-Z][a-z]{1,}([A-Z][a-z]{1,})?|[A-Z]\\.(_[A-Z]\\.)?))?_[A-Z][a-z]{1,}([A-Z][a-z]{1,})?$"); // Regex name filter
  443. new isValidName = Regex_Check(name, r); // Validate name to filter
  444. Regex_Delete(r);
  445. if(!isValidName){ // Invalid role-play name
  446. ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Please pick a realistic name, separate first and last name with an underscore.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", ""); // Force RP name.
  447. }
  448. else { // Valid role-play name
  449. // Check for name in use
  450. /*new escaped_name[MAX_PLAYER_NAME + 1]; // TODO should be more, to account for escape characters.
  451. sql_escape_string(sqlHandle, name, escaped_name, sizeof(escaped_name)); // Escape player name BROKEN argument type mismatch on argument 2, but name is a sting...*/
  452. new character_query[40 + MAX_PLAYER_NAME + 1], Result:character_result = sql_query(sqlHandle, character_query);
  453. format(character_query, sizeof(character_query), "SELECT id FROM character WHERE name = '%s'", name);
  454. if(sql_num_rows(character_result) > 0){ // Name taken
  455. ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Name already taken. Please pick an original name.", "Change", ""); // Force RP name.
  456. }
  457. else{ // Name free
  458. // Notify all players
  459. new message[11 + 4 + MAX_PLAYER_NAME + 1];
  460. format(message, sizeof(message), "* [%i] %s joined.", playerid, fromPlayerName(name));
  461. SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players
  462. DiscordEcho(message, ECHO_CHANNEL); // Notify discord public echo
  463. new client_connect_username[MAX_PLAYER_NAME + 1], admin_message[39 + 1];
  464. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name
  465. if(GetPlayerState(playerid) == PLAYER_STATE_NONE){ // Not spawned: character creation (Creating a character without registering)
  466. // Notify admins
  467. format(admin_message, sizeof(admin_message), "* [%i] %s temporary charcter, created by: %s", playerid, fromPlayerName(name), client_connect_username);
  468. // TODO inform in-game adminchat
  469. DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
  470. // Do nothing let continue to spawn, after spawn character will be saved to database.
  471. }
  472. else{ // Spawnedplayer: Creating permanent or renaming an existing permanent character
  473. // Notify admins
  474. format(admin_message, sizeof(admin_message), "* [%i] %s created by: %s", playerid, fromPlayerName(name), client_connect_username);
  475. // TODO inform in-game adminchat
  476. DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
  477. // Send to skin selection
  478. ForceClassSelection(playerid);
  479. TogglePlayerSpectating(playerid, true);
  480. TogglePlayerSpectating(playerid, false);
  481. }
  482. // Change name (No way yet to change name ingame)
  483. /*if(GetGVarInt("userlevel", playerid) > 1){ // Registered player.
  484. // Get username
  485. new username[MAX_PLAYER_NAME + 1], message[75 + MAX_PLAYER_NAME + 1];
  486. GetGVarString("username", username, sizeof(username), playerid);
  487. // Update or create character name in database
  488. new callback[1];
  489. new Result:result;
  490. new query[51 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
  491. format(query, sizeof(query), "UPDATE \"user\"(name) VALUES('%s') WHERE username == '%s'", name, username);
  492. sql_wait(sqlHandle); // Wait for other queries to finish
  493. result = sql_query(sqlHandle, query, callback = "", "r");
  494. // Inform user
  495. format(message, sizeof(message), "SERVER: Your username remains unchanged, next time connect as: %s", client_connect_username);
  496. SendClientMessage(playerid, COLOR_WHITE, message);
  497. }*/
  498. SetPlayerName(playerid, name); // Change name in-game
  499. }
  500. }
  501. return 0;
  502. }
  503. forward register(playerid);
  504. public register(playerid){ // Register player in database
  505. if(GetPlayerState(playerid) == PLAYER_STATE_NONE){ // Not spawned
  506. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "ERROR: You need to have spawned to register.");
  507. }
  508. else{ // Spawned player
  509. if(GetGVarInt("userlevel", playerid) != UNREGISTERED_PLAYER){ // Registered player.
  510. new message[35 + MAX_PLAYER_NAME + 1];
  511. format(message, sizeof(message), "ERROR: You are already registered"); //, %s.",
  512. SendClientMessage(playerid, COLOR_WHITE, message);
  513. }
  514. else{ // Unregistered player.
  515. ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password", "Please pick a strong and safe password, that you are able to remember.", "Continue", "Cancel"); // Password prompt
  516. }
  517. }
  518. }
  519. forward createCharacterRecord(playerid, id);
  520. public createCharacterRecord(playerid, id){ // TODO do not force for registered players
  521. new playername[MAX_PLAYER_NAME + 1], character_query[94 + SQL_INTERGER_LENGTH + MAX_PLAYER_NAME + 3 + 1];
  522. GetPlayerName(playerid, playername, sizeof(playername));
  523. format(character_query, sizeof(character_query), "INSERT INTO character(user_id, name, skin_id) VALUES(%i, '%s', %i)", id, playername, GetPlayerSkin(playerid));
  524. sql_query(sqlHandle, character_query);
  525. }
  526. forward authenticate(playerid);
  527. public authenticate(playerid){
  528. ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Sign in", "Enter your password to log on", "Log in", "Cancel"); // Show password confirmation dialog
  529. }
  530. forward characterSelection(playerid);
  531. public characterSelection(playerid){
  532. if(GetGVarInt("userlevel", playerid) < REGISTERED_PLAYER){ // Unregistered player (Or worse?)
  533. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "SERVER: Register first with: /my account register");
  534. }
  535. else{ // Registered player
  536. print("Character selection registered player.");
  537. // If spawned, save current character
  538. if(GetPlayerState(playerid) != PLAYER_STATE_NONE){
  539. savePlayerState(playerid);
  540. }
  541. new id = getUserID(playerid);
  542. new character_query[75 + SQL_INTERGER_LENGTH + MAX_CHARACTERS_PER_USER_DIGITS + 1];
  543. format(character_query, sizeof(character_query), "SELECT id, name FROM character WHERE user_id = %i LIMIT %i", id, MAX_CHARACTERS_PER_USER);
  544. new Result:character_result = sql_query(sqlHandle, character_query);
  545. new character_string[1024 + 1], character_array[MAX_CHARACTERS_PER_USER];
  546. for(new i = 0; i < sql_num_rows(character_result); i++){
  547. new name[MAX_PLAYER_NAME + 1];
  548. sql_get_field_assoc_ex(character_result, i, "name", name, sizeof(name));
  549. format(character_string, sizeof(character_string), "%s%s\n", character_string, name);
  550. character_array[i] = sql_get_field_assoc_int_ex(character_result, i, "id");
  551. }
  552. print(character_string);
  553. new character_array_string[MAX_CHARACTERS_PER_USER * SQL_INTERGER_LENGTH + 1];
  554. strfrombin(character_array_string, character_array); // Convert array to string for use with GVar
  555. SetGVarString("character_array_string", character_array_string, playerid);
  556. print("About to show dialog");
  557. ShowPlayerDialog(playerid, DIALOG_CHARACTERS, DIALOG_STYLE_LIST, "Characters", character_string, "Spawn", "New");
  558. }
  559. }
  560. forward savePlayerState(playerid);
  561. public savePlayerState(playerid){
  562. new playername[MAX_PLAYER_NAME + 1], escaped_playername[MAX_PLAYER_NAME + 1], character_query[183 + SQL_INTERGER_LENGTH + 3 + 3 + 1 + SQL_FLOAT_LENGTH + SQL_FLOAT_LENGTH + SQL_FLOAT_LENGTH + SQL_FLOAT_LENGTH + MAX_PLAYER_NAME + 1]; // Should be longer to allow for escaped characters
  563. GetPlayerName(playerid, playername, sizeof(playername));
  564. sql_escape_string(sqlHandle, playername, escaped_playername, sizeof(escaped_playername)); // Escape player name
  565. new Float:health, Float:armour, Float:x, Float:y, Float:z, Float:Angle;
  566. // Unrealiable TODO change to gvars
  567. GetPlayerHealth(playerid, health);
  568. GetPlayerArmour(playerid, armour);
  569. GetPlayerPos(playerid, x, y, z);
  570. GetPlayerFacingAngle(playerid, Angle);
  571. format(character_query, sizeof(character_query), "UPDATE character SET (cash, health, armour, jailed, pos_x, pos_y, pos_z, rotation) = (%i, '%f', '%f', %i, '%f', '%f', '%f', '%f') WHERE name = '%s'", GetPlayerMoney(playerid), health, armour, 0, x, y, z, Angle, escaped_playername);
  572. sql_query(sqlHandle, character_query);
  573. }
  574. forward kickPlayer(playerid, kickerid, const reason[], banned);
  575. public kickPlayer(playerid, kickerid, const reason[], banned){ // Kick a player
  576. // Issuer of kick
  577. new kickername[MAX_PLAYER_NAME + 1], kicker_id;
  578. if(kickerid == -1){ // Not kicked by in-game player
  579. kickername = "SERVER";
  580. kicker_id = -1;
  581. }
  582. else{ // Kicked by in-game player
  583. GetPlayerName(kickerid, kickername, sizeof(kickername));
  584. kicker_id = getUserID(kickerid);
  585. }
  586. // Action
  587. new action[16 + SQL_INTERGER_LENGTH + 1];
  588. if(banned){
  589. if(banned < 1){ // Permanent ban
  590. action = "banned permanently";
  591. }
  592. else{ // Temporary ban
  593. format(action, sizeof(action), "banned for %i days", banned);
  594. }
  595. }
  596. else{
  597. action = "kicked";
  598. }
  599. // Notify all players
  600. new playername[MAX_PLAYER_NAME + 1], message[16 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 16 + REASON_LENGTH + 1], adminMessage[17 + 4 + MAX_PLAYER_NAME + 16 + MAX_PLAYER_NAME + REASON_LENGTH + 1]; // Max samp chat message length 128 - 8 for "/kick ? ".
  601. GetPlayerName(playerid, playername, sizeof(playername));
  602. format(message, sizeof(message), "* [%i] %s %s, reason: %s", playerid, fromPlayerName(playername), action, reason);
  603. format(adminMessage, sizeof(adminMessage), "* [%i] %s %s %s, reason: %s", kickerid, strreplace(kickername, "_", " "), action, fromPlayerName(playername), reason);
  604. logger(LOGLEVEL_INFO, message); // Log event
  605. SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players.
  606. DiscordEcho(message, ECHO_CHANNEL);
  607. //TODO notify all in-game admins
  608. DiscordEcho(adminMessage, ADMIN_ECHO_CHANNEL);
  609. // Inform player (Redundant, but some people are blind or stupid)
  610. new player_message[30 + 6 + MAX_PLAYER_NAME + REASON_LENGTH + 1]; // Max samp chat message length 128 - 8 for "/kick ? ".
  611. format(player_message, sizeof(player_message), "SERVER: You have been %s, reason: %s", action, strreplace(kickername, "_", " "), reason);
  612. SendClientMessage(playerid, COLOR_ERROR_MESSAGE, player_message); // Notify player
  613. if(banned){
  614. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "You may appeal this ban via the forum or Discord.");
  615. }
  616. new ip_id = getIPID(playerid);
  617. // Crearte IP kick record
  618. new ip_query[61 + 4 + REASON_LENGTH + 4 + 1];
  619. format(ip_query, sizeof(ip_query), "INSERT INTO ip_kick(ip_id, reason, kicker_id) VALUES(%i, '%s', %i)", ip_id, reason, kicker_id);
  620. sql_query(sqlHandle, ip_query);
  621. // Get IP kick record
  622. new ip_id_query[61 + 4 + REASON_LENGTH + 4 + 1];
  623. format(ip_id_query, sizeof(ip_id_query), "SELECT id FROM ip_kick(ip_id) WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id, reason, kicker_id);
  624. new Result:ip_id_result = sql_query(sqlHandle, ip_id_query);
  625. new ip_kick_id = sql_get_field_assoc_int(Result:ip_id_result, "id");
  626. // User kick record
  627. new user_query[79 + 4 + REASON_LENGTH + 4 + 1];
  628. format(user_query, sizeof(user_query), "INSERT INTO user_kick(user_id, reason, kicker_id, ip_kick_id) VALUES(%i, '%s', %i, %i)", getUserID(playerid), reason, kicker_id, ip_kick_id);
  629. sql_query(sqlHandle, user_query);
  630. SetTimerEx("Kick", 50, false, "i", playerid); // Give the message 50 milliseconds to reach the player before kicking.
  631. }
  632. forward banExpiration(days);
  633. public banExpiration(days){
  634. if(days < 1){ // Permanent ban
  635. return 0;
  636. }
  637. else{ // Temporary ban
  638. new year, month, day, hour, minute, second, expires[TIMESTAMP_LENGTH + 1];
  639. getdate(year, month, day);
  640. gettime(hour, minute, second);
  641. day = day + days;
  642. format(expires, sizeof(expires), "%i-%i-%i %i:%i:%i", year, month, day, hour, minute, second);
  643. return year, month, day, hour, minute, second;
  644. }
  645. }
  646. forward banPlayer(playerid, bannerid, const reason[], days);
  647. public banPlayer(playerid, bannerid, const reason[], days){ // Ban & kick a player
  648. // Issuer of ban
  649. new banner_id;
  650. if(bannerid < 0){ // Not banned by in-game player
  651. banner_id = bannerid;
  652. }
  653. else{ // Banned by in-game player
  654. banner_id = getUserID(bannerid);
  655. }
  656. new ip_id = getIPID(playerid);
  657. // Create IP ban record
  658. new ip_query[81 + SQL_INTERGER_LENGTH + TIMESTAMP_LENGTH + REASON_LENGTH + SQL_INTERGER_LENGTH + SQL_INTERGER_LENGTH + 1];
  659. format(ip_query, sizeof(ip_query), "INSERT INTO ip_ban(ip_id, expires, reason, banner_id) VALUES(%i, %s, %s, %i) WHERE id = %i", ip_id, banExpiration(days), reason, banner_id, getUserID(playerid));
  660. sql_query(sqlHandle, ip_query);
  661. // Get IP ban record ID
  662. new ban_id_query[61 + MAX_PLAYER_NAME + 1];
  663. format(ban_id_query, sizeof(ban_id_query), "SELECT id FROM ip_ban WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id);
  664. new Result:ban_id_result = sql_query(sqlHandle, ban_id_query);
  665. new ip_ban_id = sql_get_field_assoc_int(ban_id_result, "id");
  666. // User ban record (As banned players get only kicked, noneed to check for active ban)
  667. if(GetGVarInt("userlevel", playerid) > 1){ // Registered player
  668. new user_query[97 + SQL_INTERGER_LENGTH + TIMESTAMP_LENGTH + REASON_LENGTH + SQL_INTERGER_LENGTH + SQL_INTERGER_LENGTH + 1];
  669. format(user_query, sizeof(user_query), "INSERT INTO user_ban(user_id, expires, reason, banner_id, ip_ban_id) VALUES(%i, %s, %s, %i, %i) WHERE id = %i", ip_id, banExpiration(days), reason, banner_id, ip_ban_id);
  670. sql_query(sqlHandle, user_query);
  671. }
  672. // Kick player
  673. kickPlayer(playerid, bannerid, reason, days);
  674. }
  675. // Database functions
  676. forward getIPID(playerid);
  677. public getIPID(playerid){
  678. new player_ip[IP_LEGNTH + 1], query[37 + IP_LEGNTH + 1]; // Create varaibles
  679. GetPlayerIp(playerid, player_ip, sizeof(player_ip)); // Polulate player_ip variable
  680. format(query, sizeof(query), "SELECT id FROM ip WHERE address = '%s'", player_ip); // Format query string
  681. new Result:result = sql_query(sqlHandle, query); // Execute query
  682. return sql_get_field_assoc_int(result, "id"); // Get id from result
  683. }
  684. forward getUserID(playerid);
  685. public getUserID(playerid){
  686. // SQL escape username
  687. new client_connect_username[MAX_PLAYER_NAME + 1], escaped_username[MAX_PLAYER_NAME + 1]; // TODO escaped_username should be longer to account for escape characters (Also increate database column size!)
  688. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name
  689. sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username)); // Escape player name
  690. // Get user ID
  691. new userid_query[47 + MAX_PLAYER_NAME + 1];
  692. format(userid_query, sizeof(userid_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
  693. new Result:result = sql_query(sqlHandle, userid_query);
  694. return sql_get_field_assoc_int(result, "id");
  695. }
  696. // Foo functions TODO: Rename this description.
  697. forward deleteAllGVars(playerid);
  698. public deleteAllGVars(playerid){
  699. // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
  700. DeleteGVar("client_connect_username", playerid); // From OnPlayerConnect
  701. DeleteGVar("userlevel", playerid); // From OnPlayerConnect
  702. DeleteGVar("hash", playerid); // From DIALOG_REGISTER
  703. DeleteGVar("character_array_string", playerid); // From OnDialogResponse
  704. DeleteGVar("authentication_count", playerid); // From DIALOG_LOGIN
  705. DeleteGVar("chatmode", playerid); // FROM cmd:my
  706. }
  707. // Chat functions
  708. fromPlayerName(const name[]);
  709. fromPlayerName(const name[]){ // Convert name from player name
  710. new output[MAX_PLAYER_NAME + 1];
  711. strfromliteral(output, name);
  712. strreplace(output, "_", " ");
  713. return output;
  714. }
  715. forward sendToChat(playerid, target, text[], receiver_id);
  716. public sendToChat(playerid, target, text[], receiver_id){
  717. strtrim(text); // Trim edge whitespaces
  718. if(isempty(text)){
  719. return 0; // Fail if text is empty
  720. }
  721. new playername[MAX_PLAYER_NAME + 1];
  722. GetPlayerName(playerid, playername, sizeof(playername));
  723. new chat_format, chat_color, chat_range, chat_userlevel, chat_character[1 + 1], chat_name[13 + 1], discord_channel;
  724. switch(target){
  725. case CHAT_WHISPER:{
  726. chat_format = 2;
  727. chat_range = 3;
  728. chat_name = "whispers";
  729. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  730. discord_channel = ADMIN_ECHO_CHANNEL;
  731. }
  732. case CHAT_LOW:{
  733. chat_format = 2;
  734. chat_range = 10;
  735. chat_name = "speaks softly";
  736. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  737. discord_channel = ADMIN_ECHO_CHANNEL;
  738. }
  739. case CHAT_LOCAL:{
  740. chat_format = 1;
  741. chat_range = 20;
  742. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  743. discord_channel = ADMIN_ECHO_CHANNEL;
  744. }
  745. case CHAT_SHOUT:{
  746. chat_format = 2;
  747. chat_range = 50;
  748. chat_name = "shouts";
  749. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  750. discord_channel = ADMIN_ECHO_CHANNEL;
  751. }
  752. case CHAT_ACTION:{
  753. chat_format = 0;
  754. chat_range = 20;
  755. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  756. discord_channel = ADMIN_ECHO_CHANNEL;
  757. }
  758. case CHAT_OC:{
  759. chat_format = 3;
  760. chat_range = 20;
  761. chat_character = "'";
  762. chat_name = "OOC";
  763. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  764. discord_channel = ADMIN_ECHO_CHANNEL;
  765. }
  766. case CHAT_GLOBAL:{
  767. chat_format = 3;
  768. chat_character = "`";
  769. chat_name = "Global";
  770. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  771. discord_channel = ECHO_CHANNEL;
  772. }
  773. case CHAT_GANG:{
  774. chat_format = 3;
  775. chat_character = "~";
  776. chat_name = "Gang";
  777. chat_color = COLOR_CREW_CHAT;
  778. // TODO: Output to gang Discord channel
  779. discord_channel = ADMIN_ECHO_CHANNEL;
  780. }
  781. case CHAT_GANG_OC:{
  782. chat_format = 3;
  783. chat_character = "$";
  784. chat_name = "Gang OC";
  785. chat_color = COLOR_CREW_CHAT;
  786. // TODO: Output to gang Discord channel
  787. discord_channel = ADMIN_ECHO_CHANNEL;
  788. }
  789. case CHAT_FACTION:{
  790. chat_format = 3;
  791. chat_character = "!";
  792. chat_name = "Faction";
  793. chat_color = COLOR_CREW_CHAT;
  794. // TODO: Output to faction Discord channel
  795. discord_channel = ADMIN_ECHO_CHANNEL;
  796. }
  797. case CHAT_FACTION_OC:{
  798. chat_format = 3;
  799. chat_character = "%";
  800. chat_name = "Faction OC";
  801. chat_color = COLOR_CREW_CHAT;
  802. // TODO: Output to faction Discord channel
  803. discord_channel = ADMIN_ECHO_CHANNEL;
  804. }
  805. case CHAT_CREW:{
  806. chat_format = 3;
  807. chat_character = "@";
  808. chat_name = "Crew";
  809. chat_color = COLOR_CREW_CHAT;
  810. chat_userlevel = MODERATOR_CREW;
  811. discord_channel = ADMIN_ECHO_CHANNEL;
  812. }
  813. case CHAT_ADMIN:{
  814. chat_format = 3;
  815. chat_character = "#";
  816. chat_name = "Admin";
  817. chat_color = COLOR_ADMIN_CHAT;
  818. chat_userlevel = ADMIN_CREW;
  819. discord_channel = ADMIN_ECHO_CHANNEL;
  820. }
  821. case CHAT_MANAGEMENT:{
  822. chat_format = 3;
  823. chat_character = "&";
  824. chat_name = "Management";
  825. chat_color = COLOR_ADMIN_CHAT;
  826. chat_userlevel = MANAGEMENT_CREW;
  827. discord_channel = MANAGEMENT_CHANNEL;
  828. }
  829. case CHAT_VIP:{
  830. chat_format = 3;
  831. chat_character = "^";
  832. chat_name = "VIP";
  833. chat_color = COLOR_VIP_CHAT;
  834. chat_userlevel = VIP_PLAYER;
  835. discord_channel = ADMIN_ECHO_CHANNEL;
  836. //TODO: Add VIP discord channel.
  837. }
  838. case CHAT_PARTYLINE:{
  839. chat_format = -1;
  840. chat_character = "*";
  841. chat_color = COLOR_DEFAULT_COMMAND_OUTPUT;
  842. chat_userlevel = ADMIN_CREW;
  843. discord_channel = ECHO_CHANNEL;
  844. }
  845. case CHAT_PM:{
  846. chat_format = 3;
  847. chat_character = ">";
  848. chat_name = "PM";
  849. chat_color = COLOR_PM_CHAT;
  850. discord_channel = ADMIN_ECHO_CHANNEL;
  851. }
  852. case CHAT_CALL:{
  853. chat_format = 3;
  854. chat_character = "+";
  855. chat_name = "Call";
  856. chat_color = COLOR_PM_CHAT;
  857. discord_channel = ADMIN_ECHO_CHANNEL;
  858. }
  859. case CHAT_SMS:{
  860. chat_format = 3;
  861. chat_character = "-";
  862. chat_name = "SMS";
  863. chat_color = COLOR_PM_CHAT;
  864. discord_channel = ADMIN_ECHO_CHANNEL;
  865. }
  866. case CHAT_RADIO:{
  867. chat_format = 3;
  868. chat_character = "=";
  869. chat_name = "Radio";
  870. chat_color = COLOR_PM_CHAT;
  871. discord_channel = ADMIN_ECHO_CHANNEL;
  872. }
  873. }
  874. // Format chat message
  875. new message[9 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1];
  876. switch(chat_format){
  877. case -1: format(message, sizeof(message), "%s %s", chat_character, text);
  878. case 0: format(message, sizeof(message), "[%i] %s %s", playerid, fromPlayerName(playername), text);
  879. case 1: format(message, sizeof(message), "[%i] %s: %s", playerid, fromPlayerName(playername), text);
  880. case 2: format(message, sizeof(message), "[%i] %s %s: %s", playerid, fromPlayerName(playername), chat_name, text);
  881. case 3: format(message, sizeof(message), "%s (%s) [%i] %s: %s", chat_character, chat_name, playerid, fromPlayerName(playername), text);
  882. }
  883. // Authorisation
  884. if(chat_userlevel && chat_userlevel > GetGVarInt("userlevel", playerid)){ // User not privilged to read chat
  885. if(target == CHAT_CREW){ // Show the user the message was sent to crew chat.
  886. SendClientMessage(playerid, chat_color, message);
  887. }
  888. else{
  889. SendClientMessage(playerid, COLOR_CREW_CHAT, "ERROR: You are not authorized to speak in this chat.");
  890. return 0; // Fail to send the message
  891. }
  892. }
  893. // Send TODO: Add checks for muted.
  894. if(chat_range){ // Ranged chats
  895. logger(LOGLEVEL_CHAT, message); // Log event
  896. // Show message to players in range
  897. new Float:x, Float:y, Float:z;
  898. GetPlayerPos(playerid, x, y, z);
  899. for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
  900. if(IsPlayerConnected(receiver_id)){
  901. new Float:distance = GetPlayerDistanceFromPoint(recipient_id, x, y, z);
  902. if(distance <= chat_range){ // Player nearby
  903. SendClientMessage(recipient_id, chat_color, message);
  904. }
  905. }
  906. }
  907. DiscordEcho(message, discord_channel);
  908. }
  909. else{ // Global chats
  910. if(target == CHAT_GLOBAL){ // Public chat
  911. logger(LOGLEVEL_CHAT, message); // Log event
  912. SendClientMessageToAll(chat_color, message);
  913. }
  914. else if(target == CHAT_GANG || target == CHAT_GANG_OC || target == CHAT_FACTION || target == CHAT_FACTION_OC || target == CHAT_CALL || target == CHAT_SMS || target == CHAT_RADIO ){
  915. // TODO
  916. // TODO sendToAdmins();
  917. // TODO send to specific discord channel
  918. }
  919. else if(target == CHAT_PM){
  920. new output[sizeof(message)];
  921. strdel(message, 0, 1);
  922. format(output, sizeof(output), "<%s", message);
  923. SendClientMessage(receiver_id, chat_color, output);
  924. new receipt[12 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1];
  925. format(receipt, sizeof(receipt), "%s (%s) to [%i] %s: %s", chat_character, chat_name, receiver_id, fromPlayerName(playername), text);
  926. SendClientMessage(playerid, chat_color, receipt);
  927. new admin_message[15 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 4 + 128 + 1];
  928. format(admin_message, sizeof(admin_message), "%s (%s) [%i] to %s [%i]: %s", chat_character, chat_name, playerid, fromPlayerName(playername), receiver_id, text);
  929. logger(LOGLEVEL_CHAT, admin_message); // Log event
  930. sendToAdmins(chat_color, admin_message);
  931. DiscordEcho(message, discord_channel);
  932. }
  933. else{ // Chat with select users
  934. for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
  935. if(IsPlayerConnected(recipient_id)){
  936. if(GetGVarInt("userlevel", recipient_id) >= chat_userlevel){ // Privileged
  937. logger(LOGLEVEL_CHAT, message); // Log event
  938. SendClientMessage(recipient_id, chat_color, message);
  939. DiscordEcho(message, discord_channel);
  940. }
  941. }
  942. }
  943. }
  944. }
  945. return 1;
  946. }
  947. forward sendToAdmins(chat_color, const message[]);
  948. public sendToAdmins(chat_color, const message[]){
  949. for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){
  950. if(IsPlayerConnected(recipient_id)){
  951. if(GetGVarInt("userlevel", recipient_id) >= ADMIN_CREW){ // Privileged
  952. SendClientMessage(recipient_id, chat_color, message);
  953. }
  954. }
  955. }
  956. }
  957. forward sendPM(playerid, text[], recipient_id);
  958. public sendPM(playerid, text[], recipient_id){
  959. if (recipient_id == INVALID_PLAYER_ID){
  960. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "ERROR: Player not found.");
  961. }
  962. else{
  963. sendToChat(playerid, CHAT_PM, text, recipient_id);
  964. }
  965. }
  966. // Commands
  967. public OnPlayerCommandPerformed(playerid, cmd[], params[], result, flags){
  968. if(result == -1){
  969. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "SERVER: Unknown command.");
  970. return 0;
  971. }
  972. return 1;
  973. }
  974. public PC_OnInit(){ // TODO
  975. return 1; // Remove this once stuff is in place.
  976. }
  977. // Command commands
  978. cmd:help(playerid, params[]){
  979. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "SERVER: Ask questions in the chat, on Discord or the forum.\nTo list all commands: /cmds");
  980. return 1;
  981. }
  982. alias:help("h");
  983. cmd:cmds(playerid, params[]){
  984. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Command help syntax: \"/\" = start of cmd, \"()\" = cmd aliasses, \"|\" = or, \"<>\" = required parameter, \"[]\" = optional parameter, \",\" = next item.");
  985. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Command help syntax example: /command (/alias | /other_alias) <parameter> [optional_parameter], /next_command");
  986. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "General commands: /help (/h), /cmds (/cmd | /commands), /my (/myself | /mine), /v (/vehivle | /veh), /p (/player)");
  987. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Chat commands: /me (/emote | /action), /w (/whisper), /low (/soft), /l (/local), /s (/shout | /scream), /o (' | /oc | /ooc)");
  988. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Chat commands: /g (` | /global | /public), /gc (~) /fc (!), /vc (&), /cc (@), /my chatmode <value>");
  989. return 0;
  990. }
  991. alias:cmds("commands", "cmd");
  992. // User commands
  993. cmd:register(playerid, params[]){
  994. register(playerid);
  995. }
  996. cmd:my(playerid, params[]){
  997. // No option specified
  998. if(strlen(params) < 1){
  999. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Usage: /my <option> <value>");
  1000. return SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Options: account chatmode");
  1001. }
  1002. // Options
  1003. if(!strcmp(params, "account", true, 7)){ // /my account
  1004. strdel(params, 0, 8); // Remove first 8 characters, "account ", from the string.
  1005. // Values
  1006. if (isequal(params, "register", .ignorecase = true)){ // /my account register
  1007. register(playerid); // Call public register function
  1008. }
  1009. else if (isequal(params, "characters", .ignorecase = true)){ // /my account characters
  1010. characterSelection(playerid);
  1011. }
  1012. else if (isequal(params, "deletecharacter", .ignorecase = true)){ // /my account characters
  1013. ShowPlayerDialog(playerid, DIALOG_DELETE_CHARACTER, DIALOG_STYLE_MSGBOX, "Charater deletion", "You are about to permanently delete this charater and all it's assets.\n This can NOT be undone.", "Destroy", "Abort");
  1014. }
  1015. else{ // Invalid value
  1016. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Usage: /my account <value>");
  1017. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Values: register characters deletecharacter");
  1018. }
  1019. }
  1020. else if(!strcmp(params, "chatmode", true, 8)){ // /my chatmode
  1021. strdel(params, 0, 9); // Remove first 9 characters, "account ", from the string.
  1022. // Values
  1023. if (isequal(params, "whisper", .ignorecase = true)){
  1024. SetGVarInt("chatmode", CHAT_WHISPER, playerid);
  1025. }
  1026. else if (isequal(params, "low", .ignorecase = true)){
  1027. SetGVarInt("chatmode", CHAT_LOW, playerid);
  1028. }
  1029. else if (isequal(params, "local", .ignorecase = true)){
  1030. SetGVarInt("chatmode", CHAT_LOCAL, playerid);
  1031. }
  1032. else if (isequal(params, "shout", .ignorecase = true)){
  1033. SetGVarInt("chatmode", CHAT_SHOUT, playerid);
  1034. }
  1035. else if (isequal(params, "oc", .ignorecase = true)){
  1036. SetGVarInt("chatmode", CHAT_OC, playerid);
  1037. }
  1038. else if (isequal(params, "global", .ignorecase = true)){
  1039. SetGVarInt("chatmode", CHAT_GLOBAL, playerid);
  1040. }
  1041. else if (isequal(params, "gang", .ignorecase = true)){
  1042. SetGVarInt("chatmode", CHAT_GANG, playerid);
  1043. }
  1044. else if (isequal(params, "faction", .ignorecase = true)){
  1045. SetGVarInt("chatmode", CHAT_FACTION, playerid);
  1046. }
  1047. else if (isequal(params, "vip", .ignorecase = true)){
  1048. SetGVarInt("chatmode", CHAT_VIP, playerid);
  1049. }
  1050. else if (isequal(params, "crew", .ignorecase = true)){
  1051. SetGVarInt("chatmode", CHAT_CREW, playerid);
  1052. }
  1053. else if (isequal(params, "admin", .ignorecase = true)){
  1054. SetGVarInt("chatmode", CHAT_ADMIN, playerid);
  1055. }
  1056. else if (isequal(params, "management", .ignorecase = true)){
  1057. SetGVarInt("chatmode", CHAT_MANAGEMENT, playerid);
  1058. }
  1059. else{ // Invalid value
  1060. printf("else isequals register");
  1061. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Usage: /my chatmode <value>");
  1062. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Values: whisper low local shout oc global gang faction vip crew admin management");
  1063. }
  1064. }
  1065. else{ // Invalid option
  1066. printf("else !strcmp account");
  1067. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "/my options: account chatmode");
  1068. }
  1069. return 0;
  1070. }
  1071. alias:my("myself", "mine");
  1072. // Chat commands
  1073. cmd:w(playerid, params[]){
  1074. sendToChat(playerid, CHAT_WHISPER, params, 0);
  1075. return 1;
  1076. }
  1077. alias:w("whisper");
  1078. cmd:low(playerid, params[]){
  1079. sendToChat(playerid, CHAT_LOW, params, 0);
  1080. return 1;
  1081. }
  1082. alias:low("soft");
  1083. cmd:l(playerid, params[]){
  1084. sendToChat(playerid, CHAT_LOCAL, params, 0);
  1085. return 1;
  1086. }
  1087. alias:l("local");
  1088. cmd:me(playerid, params[]){
  1089. sendToChat(playerid, CHAT_ACTION, params, 0);
  1090. return 1;
  1091. }
  1092. alias:me("emote", "action");
  1093. cmd:s(playerid, params[]){
  1094. sendToChat(playerid, CHAT_SHOUT, params, 0);
  1095. return 1;
  1096. }
  1097. alias:s("shout", "scream");
  1098. cmd:o(playerid, params[]){
  1099. sendToChat(playerid, CHAT_OC, params, 0);
  1100. return 1;
  1101. }
  1102. alias:o("oc", "ooc");
  1103. cmd:g(playerid, params[]){
  1104. sendToChat(playerid, CHAT_GLOBAL, params, 0);
  1105. return 1;
  1106. }
  1107. alias:g("global", "public");
  1108. cmd:gc(playerid, params[]){
  1109. sendToChat(playerid, CHAT_GANG, params, 0);
  1110. return 1;
  1111. }
  1112. cmd:fc(playerid, params[]){
  1113. sendToChat(playerid, CHAT_FACTION, params, 0);
  1114. return 1;
  1115. }
  1116. cmd:vc(playerid, params[]){
  1117. sendToChat(playerid, CHAT_VIP, params, 0);
  1118. return 1;
  1119. }
  1120. cmd:cc(playerid, params[]){
  1121. sendToChat(playerid, CHAT_CREW, params, 0);
  1122. return 1;
  1123. }
  1124. cmd:pm(playerid, params[]){
  1125. new recipient_id, message[123 + 1];
  1126. if (sscanf(params, "u s", recipient_id, message)){
  1127. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Usage: /pm <playerid> <message>");
  1128. }
  1129. else{
  1130. sendPM(playerid, message, recipient_id);
  1131. }
  1132. return 1;
  1133. }
  1134. alias:pm("msg", "dm");
  1135. // Vehicle commands
  1136. cmd:v(playerid, params[]){
  1137. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "health/armor [AMOUNT]");
  1138. return 1;
  1139. }
  1140. alias:v("vehicle", "veh");
  1141. // Player commands
  1142. cmd:p(playerid, params[]){
  1143. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "health/armor [AMOUNT]");
  1144. return 1;
  1145. }
  1146. alias:p("player");
  1147. // a_samp events
  1148. public OnGameModeInit(){
  1149. new message[36 + 22 + 1];
  1150. format(message, sizeof(message), "* Global game-mode initialization: v%s", MODE_NAME);
  1151. logger(LOGLEVEL_NOTICE, "* Global game-mode initialization."); // Log event.
  1152. // Hardcoded settings
  1153. ShowPlayerMarkers(PLAYER_MARKERS_MODE_STREAMED); // Player radar blip markers only visible to nearby players.
  1154. // Set mode name
  1155. SetGameModeText(SERVER_NAME);
  1156. // SQL log level
  1157. if(scriptDebug){
  1158. sql_debug(LOG_ALL, LOG_ALL); // Log everything everywhere.
  1159. }
  1160. else{
  1161. sql_debug(LOG_INFO, LOG_WARNING); // Loglevel info for file and worning for console.
  1162. }
  1163. // Connect to database
  1164. sqlHandle = SQL:sql_connect(SQL_HANDLER_POSTGRESQL, PG_HOST, PG_ROLE, PG_PASS, PG_DB, PG_PORT);
  1165. printf("sqlconnection = %d", _:sqlHandle);
  1166. if(!sql_ping(sqlHandle)){
  1167. print( " + Database connection" );
  1168. }
  1169. else{
  1170. print( "Database connection failed" );
  1171. }
  1172. // Initialize Discord
  1173. //homeGuild = DCC_GetGuildId(DISCORD_HOME_GUILD_ID); // Set home guild ID. NOT NEEDED FOR NOW AND BROKEN
  1174. echoChannel = DCC_FindChannelById(DISCORD_ECHO_CHANNEL_ID); // Set main echo channel ID.
  1175. mainChannel = DCC_FindChannelById(DISCORD_MAIN_CHANNEL_ID); // Set main notification channel ID.
  1176. adminEchoChannel = DCC_FindChannelById(DISCORD_ADMIN_ECHO_CHANNEL_ID); // Set admin echo channel ID.
  1177. adminChannel = DCC_FindChannelById(DISCORD_ADMIN_CHANNEL_ID); // Set admin notification channel ID.
  1178. managementChannel = DCC_FindChannelById(DISCORD_MANAGEMENT_CHANNEL_ID); // Set management notification channel ID.
  1179. DiscordEcho(message, ECHO_CHANNEL); // Notify Discord
  1180. //DiscordEcho(message, MAIN_CHANNEL); // TODO Enable after we are stable
  1181. // Hobo's with a cane (0 ammo value makes them lose the cane as soon as they switch weapon)
  1182. // Only homeless skins, as players should slowly class up in society.
  1183. AddPlayerClass(134, -184.7607, 950.5010, 16.7740, 358.3032, 15, 0, 0, 0, 0, false); // Fort Carson West boulevard right curb.
  1184. AddPlayerClass(10, -184.7607, 950.5010, 16.7740, 358.3032, 15, 0, 0, 0, 0, false); // Fort Carson West boulevard right curb.
  1185. AddPlayerClass(78, 111.0115, 1189.2029, 18.1627, 89.0095, 15, 0, 0, 0, 0, false); // Fort Carson South boulevard left curb.
  1186. AddPlayerClass(129, 111.0115, 1189.2029, 18.1627, 89.0095, 15, 0, 0, 0, 0, false); // Fort Carson South boulevard left curb.
  1187. AddPlayerClass(162, -109.4227, 1242.4860, 16.8223, 183.5798, 15, 0, 0, 0, 0, false); // Fort Carson East boulevard left curb.
  1188. AddPlayerClass(77, -109.4227, 1242.4860, 16.8223, 183.5798, 15, 0, 0, 0, 0, false); // Fort Carson East boulevard left curb.
  1189. AddPlayerClass(79, -201.5379, 948.1683, 15.9131, 359.9720, 15, 0, 0, 0, 0, false); // Fort Carson West boulevard left curb.
  1190. AddPlayerClass(196, -201.5379, 948.1683, 15.9131, 359.9720, 15, 0, 0, 0, 0, false); // Fort Carson West boulevard left curb.
  1191. AddPlayerClass(239, 62.4694, 1205.0531, 18.8153, 89.9380, 15, 0, 0, 0, 0, false); // Fort Carson South boulevard right curb.
  1192. AddPlayerClass(89, 62.4694, 1205.0531, 18.8153, 89.9380, 15, 0, 0, 0, 0, false); // Fort Carson South boulevard right curb.
  1193. AddPlayerClass(135, -126.0831, 1242.5745, 18.6138, 183.2986, 15, 0, 0, 0, 0, false); // Fort Carson East boulevard right curb.
  1194. AddPlayerClass(197, -126.0831, 1242.5745, 18.6138, 183.2986, 15, 0, 0, 0, 0, false); // Fort Carson East boulevard right curb.
  1195. return 1;
  1196. }
  1197. public OnGameModeExit(){
  1198. // Cycle every player
  1199. for(new playerid, a = GetMaxPlayers(); playerid < a; playerid++){
  1200. if(IsPlayerConnected(playerid))
  1201. {
  1202. deleteAllGVars(playerid); // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
  1203. // Set name back to username
  1204. new client_connect_username[MAX_PLAYER_NAME + 1];
  1205. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
  1206. //SetPlayerName(playerid, client_connect_username); // Change name in-game back to username, for login after restart PROBLEM: Crashses the server on GMX.
  1207. // TODO think of somthing for the usernames, kickign every player, or accapting character names as usersnames.
  1208. }
  1209. }
  1210. logger(LOGLEVEL_NOTICE, "* Global game-mode termination."); // Log event
  1211. sql_wait(sqlHandle); // Wait for queries to finish.
  1212. sql_disconnect(sqlHandle); // Disconnect from database.
  1213. DiscordEcho("* Global game-mode termination.", ECHO_CHANNEL); // Notify discord
  1214. //DiscordEcho("* Global game-mode termination.", MAIN_CHANNEL); // Enable when stable
  1215. return 1;
  1216. }
  1217. public OnPlayerRequestClass(playerid, classid){ // Skin selection before spawn.
  1218. if(scriptDebug){
  1219. new playername[MAX_PLAYER_NAME + 1], message[22 + 4 + MAX_PLAYER_NAME + 1];
  1220. GetPlayerName(playerid, playername, sizeof(playername));
  1221. format(message, sizeof(message), "* [%i] %s Class selection.", playerid, fromPlayerName(playername));
  1222. logger(LOGLEVEL_DEBUG, message); // Log event.
  1223. }
  1224. SetPlayerPos(playerid, -185.5514, 944.2042, 15.9337); // In front of Fort Carson city limits sign.
  1225. SetPlayerFacingAngle(playerid, 182.7345); // Charater looks toward the camera.
  1226. SetPlayerCameraPos(playerid, -185.5514, 939.0957, 15.6594); // Further in front of the Fort Carson city limits sign.
  1227. SetPlayerCameraLookAt(playerid, -185.5514, 944.2042, 15.9337); // In front of Fort Carson city limits sign.
  1228. return 1; // Must return one, or skin selection breaks
  1229. }
  1230. public OnPlayerConnect(playerid){
  1231. // Create variables
  1232. new playername[MAX_PLAYER_NAME + 1], playerip[IP_LEGNTH + 1];
  1233. // Populate variables
  1234. GetPlayerName(playerid, playername, sizeof(playername));
  1235. GetPlayerIp(playerid, playerip, sizeof(playerip));
  1236. // Global connection notification
  1237. new admin_message[23 + MAX_PLAYER_NAME + IP_LEGNTH + 1];
  1238. format(admin_message, sizeof(admin_message), "* [%d] %s (IP: %s) connected.", playerid, fromPlayerName(playername), playerip);
  1239. logger(LOGLEVEL_INFO, admin_message); // Log event
  1240. sendToAdmins(COLOR_NOTICE, admin_message); // Notify all admins.
  1241. DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL); // Notify Discord admin echo.
  1242. // Create IP record or update connection attempts
  1243. new ip_query[109 + IP_LEGNTH + 1];
  1244. format(ip_query, sizeof(ip_query), "INSERT INTO ip(address) VALUES('%s') ON CONFLICT (address) DO UPDATE SET connections = ip.connections+1", playerip);
  1245. sql_query(sqlHandle, ip_query);
  1246. // Check if IP is banned
  1247. new ip_id = getIPID(playerid);
  1248. new ban_query[72 + 1];
  1249. format(ban_query, sizeof(ban_query), "SELECT id, reason FROM ip_ban WHERE id = %i AND expires > NOW()::timestamp", ip_id);
  1250. new Result:ban_result = sql_query(sqlHandle, ban_query);
  1251. if(sql_num_rows(ban_result) > 0){ // Banned
  1252. new kick_message[8 + REASON_LENGTH + 1];
  1253. format(kick_message, sizeof(kick_message), "Banned: %s", sql_get_field_assoc_int(ban_result, "reason"));
  1254. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "You may appeal your ban via the forum or Discord.");
  1255. kickPlayer(playerid, -1, kick_message, 1);
  1256. }
  1257. // Get user record
  1258. new escaped_username[MAX_PLAYER_NAME + 1], user_query[47 + MAX_PLAYER_NAME + 1]; // Should be longer to allow for escaped characters
  1259. sql_escape_string(sqlHandle, playername, escaped_username, sizeof(escaped_username)); // Escape player name
  1260. format(user_query, sizeof(user_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
  1261. //new Result:result = sqlQuery(sqlHandle, user_query); // Middleware broken.
  1262. new Result:result = sql_query(sqlHandle, user_query);
  1263. SetGVarString("client_connect_username", playername, playerid); // Used by register, DIALOG_LOGIN, getUserID & OnGameModeExit
  1264. if(sql_num_rows(result) == 0){ // Unkown user
  1265. SetGVarInt("userlevel", UNREGISTERED_PLAYER, playerid); // Set userlevel unregistered
  1266. changeName(playerid, playername); // Check, force and set role-play name
  1267. }
  1268. else{ // Known user
  1269. ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Sign in", "Enter your password to log on", "Log in", "Cancel"); // Show password confirmation dialog
  1270. }
  1271. //return 1;
  1272. }
  1273. public OnPlayerDisconnect(playerid, reason){
  1274. savePlayerState(playerid); // Save character
  1275. deleteAllGVars(playerid); // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076
  1276. new playername[MAX_PLAYER_NAME + 1], message[40 + 4 + MAX_PLAYER_NAME + 1];
  1277. GetPlayerName(playerid, playername, sizeof(playername));
  1278. switch(reason){
  1279. case 0: format(message, sizeof(message), "* [%i] %s disconnected. (Lost Connection)", playerid, fromPlayerName(playername));
  1280. case 1: format(message, sizeof(message), "* [%i] %s disconnected. (Leaving)", playerid, fromPlayerName(playername));
  1281. case 2: format(message, sizeof(message), "* [%i] %s disconnected. (Kicked)", playerid, fromPlayerName(playername)); // Leave this in place for RCON kicks.
  1282. }
  1283. logger(LOGLEVEL_INFO, message); // Log event
  1284. SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players.
  1285. DiscordEcho(message, ECHO_CHANNEL); // Notify discord.
  1286. return 1;
  1287. }
  1288. public OnPlayerSpawn(playerid){
  1289. if(scriptDebug){ // Log event in case of debugging.
  1290. new playername[MAX_PLAYER_NAME + 1], message[MAX_PLAYER_NAME + 14 + 1];
  1291. GetPlayerName(playerid, playername, sizeof(playername));
  1292. format(message, sizeof(message), "* [%i] %s spawned.", playerid, fromPlayerName(playername));
  1293. logger(LOGLEVEL_DEBUG, message); // Log event.
  1294. }
  1295. if(GetGVarInt("userlevel", playerid) == UNREGISTERED_PLAYER){ // Unregistered player.
  1296. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "SERVER: To reserve your name, save your character, statistics and money, type: /register");
  1297. }
  1298. return 1;
  1299. }
  1300. public OnPlayerDeath(playerid, killerid, reason){
  1301. new playername[MAX_PLAYER_NAME + 1], message[15 + MAX_PLAYER_NAME + 1];
  1302. GetPlayerName(playerid, playername, sizeof(playername));
  1303. format(message, sizeof(message), "* [%d] %s died.", playerid, fromPlayerName(playername)); // TODO Add killerid & reason.
  1304. logger(LOGLEVEL_DEBUG, message); // Log event
  1305. DiscordEcho(message, ADMIN_ECHO_CHANNEL); // Notify Discord admin echo.
  1306. //ResetPlayerMoney(playerid); TODO test if required
  1307. //SpawnPlayer(playerid);
  1308. return 1;
  1309. }
  1310. public OnVehicleSpawn(vehicleid) // TODO for 0.0a
  1311. {
  1312. return 1;
  1313. }
  1314. public OnVehicleDeath(vehicleid, killerid) // TODO for 0.0a
  1315. {
  1316. return 1;
  1317. }
  1318. public OnPlayerText(playerid, text[]){
  1319. new shortcut_message[128 + 1];
  1320. strfromliteral(shortcut_message, text);
  1321. strdel(shortcut_message, 0, 1); // Remove chat character from text
  1322. switch(strgetfirstc(text)){
  1323. case '`': {
  1324. if(GetGVarInt("chatmode", playerid) == CHAT_LOCAL){
  1325. sendToChat(playerid, CHAT_GLOBAL, shortcut_message, 0);
  1326. }
  1327. else if (GetGVarInt("chatmode", playerid) == CHAT_GLOBAL){
  1328. sendToChat(playerid, CHAT_LOCAL, shortcut_message, 0);
  1329. }
  1330. else{
  1331. sendToChat(playerid, CHAT_GLOBAL, shortcut_message, 0);
  1332. }
  1333. }
  1334. case '~': {sendToChat(playerid, CHAT_GANG, shortcut_message, 0);}
  1335. case '!': {sendToChat(playerid, CHAT_FACTION, shortcut_message, 0);}
  1336. case '@': {sendToChat(playerid, CHAT_CREW, shortcut_message, 0);}
  1337. case '#': {sendToChat(playerid, CHAT_ADMIN, shortcut_message, 0);}
  1338. case '$': {sendToChat(playerid, CHAT_GANG_OC, shortcut_message, 0);}
  1339. case '%': {sendToChat(playerid, CHAT_FACTION_OC, shortcut_message, 0);}
  1340. case '^': {sendToChat(playerid, CHAT_MANAGEMENT, shortcut_message, 0);}
  1341. //case '$': {sendToChat(playerid, CHAT_MANAGEMENT, shortcut_message[]);}
  1342. case '&': {sendToChat(playerid, CHAT_VIP, shortcut_message, 0);}
  1343. case '*': {sendToChat(playerid, CHAT_PARTYLINE, shortcut_message, 0);}
  1344. case '+': {sendToChat(playerid, CHAT_CALL, shortcut_message, 0);}
  1345. case '-': {sendToChat(playerid, CHAT_SMS, shortcut_message, 0);}
  1346. case '=': {sendToChat(playerid, CHAT_RADIO, shortcut_message, 0);}
  1347. case '>': {
  1348. new recipient_id, message[125 + 1];
  1349. if (sscanf(shortcut_message, "u s", recipient_id, message)){
  1350. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "Usage: > <playerid> <message>");
  1351. }
  1352. else{
  1353. sendPM(playerid, message, recipient_id);
  1354. }
  1355. }
  1356. default:{
  1357. new chatmode = GetGVarInt("chatmode", playerid);
  1358. if(!chatmode){ // Default is local chat
  1359. sendToChat(playerid, CHAT_LOCAL, text, 0);
  1360. }
  1361. else{ // Send to preferred chat
  1362. printf("chatmode: %i", chatmode);
  1363. sendToChat(playerid, chatmode, text, 0);
  1364. }
  1365. }
  1366. }
  1367. return 0; // Return 1 for default behavior, return 0 to disable default output.
  1368. }
  1369. public OnPlayerCommandText(playerid, cmdtext[]){
  1370. if(scriptDebug){ // Log event in case of debugging.
  1371. new playername[MAX_PLAYER_NAME + 1], message[5 + 4 + MAX_PLAYER_NAME + 128 + 1]; // 128 = samp text input limit.
  1372. GetPlayerName(playerid, playername, sizeof(playername));
  1373. format(message, sizeof(message), "[%d] %s: %s", playerid, fromPlayerName(playername), cmdtext);
  1374. logger(LOGLEVEL_COMMAND, message); // Log event
  1375. }
  1376. return 0;
  1377. }
  1378. public OnPlayerEnterVehicle(playerid, vehicleid, ispassenger) // TODO for 0.0a
  1379. {
  1380. return 1;
  1381. }
  1382. public OnPlayerExitVehicle(playerid, vehicleid) // TODO for 0.0a
  1383. {
  1384. return 1;
  1385. }
  1386. public OnPlayerStateChange(playerid, newstate, oldstate) // TODO for 0.0a
  1387. {
  1388. return 1;
  1389. }
  1390. public OnPlayerEnterCheckpoint(playerid) // TODO for 0.0a
  1391. {
  1392. return 1;
  1393. }
  1394. public OnPlayerLeaveCheckpoint(playerid) // TODO for 0.0a
  1395. {
  1396. return 1;
  1397. }
  1398. public OnPlayerEnterRaceCheckpoint(playerid) // TODO for 0.0a
  1399. {
  1400. return 1;
  1401. }
  1402. public OnPlayerLeaveRaceCheckpoint(playerid) // TODO for 0.0a
  1403. {
  1404. return 1;
  1405. }
  1406. public OnRconCommand(cmd[]){ // The website and some cronjobs do RCON commands. TODO create filter not to show some commands to preven spam
  1407. new message[8 + 128 + 1]; // Max samp message legnth = 128.
  1408. format(message, sizeof(message), "* RCON: %s", cmd);
  1409. logger(LOGLEVEL_NOTICE, message); // Log event
  1410. DiscordEcho(message, MANAGEMENT_CHANNEL);
  1411. return 1;
  1412. }
  1413. public OnPlayerRequestSpawn(playerid){ // After picking a skin in class selection.
  1414. new playername[MAX_PLAYER_NAME + 1], message[14 + MAX_PLAYER_NAME + 1];
  1415. GetPlayerName(playerid, playername, sizeof(playername));
  1416. if(scriptDebug){ // Only log spawns when debuggins script.
  1417. format(message, sizeof(message), "* [%d] %s Chose a character.", playerid, fromPlayerName(playername));
  1418. logger(LOGLEVEL_DEBUG, message); // Log event
  1419. }
  1420. if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){ // Registered player
  1421. createCharacterRecord(playerid, getUserID(playerid)); // Save character
  1422. new client_connect_username[MAX_PLAYER_NAME + 1], admin_message[25 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
  1423. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name
  1424. format(admin_message, sizeof(admin_message), "[%i] %s has been created by %s.", playerid, fromPlayerName(playername), client_connect_username);
  1425. DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL);
  1426. }
  1427. return 1;
  1428. }
  1429. public OnObjectMoved(objectid) // TODO for 0.0a
  1430. {
  1431. return 1;
  1432. }
  1433. public OnPlayerObjectMoved(playerid, objectid) // TODO for 0.0a
  1434. {
  1435. return 1;
  1436. }
  1437. public OnPlayerPickUpPickup(playerid, pickupid) // TODO for 0.0a
  1438. {
  1439. return 1;
  1440. }
  1441. public OnVehicleMod(playerid, vehicleid, componentid) // TODO for 0.0a
  1442. {
  1443. return 1;
  1444. }
  1445. public OnVehiclePaintjob(playerid, vehicleid, paintjobid) // TODO for 0.0a
  1446. {
  1447. return 1;
  1448. }
  1449. public OnVehicleRespray(playerid, vehicleid, color1, color2) // TODO for 0.0a
  1450. {
  1451. return 1;
  1452. }
  1453. public OnPlayerSelectedMenuRow(playerid, row) // TODO for 0.0a
  1454. {
  1455. return 1;
  1456. }
  1457. public OnPlayerExitedMenu(playerid) // TODO for 0.0a
  1458. {
  1459. return 1;
  1460. }
  1461. public OnPlayerInteriorChange(playerid, newinteriorid, oldinteriorid) // TODO for 0.0a
  1462. {
  1463. return 1;
  1464. }
  1465. public OnPlayerKeyStateChange(playerid, newkeys, oldkeys) // TODO for 0.0a
  1466. {
  1467. return 1;
  1468. }
  1469. public OnRconLoginAttempt(ip[], password[], success){
  1470. new message[30 + IP_LEGNTH + 1];
  1471. if(success){
  1472. format(message, sizeof(message), "* [%s] authenticated via RCON.", ip);
  1473. }
  1474. else{
  1475. format(message, sizeof(message), "* [%s] invalid RCON authentication.", ip);
  1476. }
  1477. logger(LOGLEVEL_NOTICE, message); // Log event
  1478. DiscordEcho(message, MANAGEMENT_CHANNEL); // Notify Discord management.
  1479. return 1;
  1480. }
  1481. public OnPlayerUpdate(playerid) // Don't use for now.
  1482. {
  1483. return 1;
  1484. }
  1485. public OnPlayerStreamIn(playerid, forplayerid){
  1486. new playername[MAX_PLAYER_NAME + 1], forPlayerName[MAX_PLAYER_NAME + 1], message[30 + 4 + MAX_PLAYER_NAME + 4 + MAX_PLAYER_NAME + 1];
  1487. GetPlayerName(playerid, playername, sizeof(playername));
  1488. GetPlayerName(forplayerid, forPlayerName, sizeof(forPlayerName));
  1489. format(message, sizeof(message), "* [%d] %s is now streamed in for [%d] %s.", playerid, fromPlayerName(playername), forplayerid, strreplace(forPlayerName, "_", " "));
  1490. logger(LOGLEVEL_DEBUG, message); // Log event
  1491. /*format(message, sizeof(message), "[%d] is now streamed in for you.", playerid);
  1492. SendClientMessage(forplayerid, 0xFFFFFFFF, string);*/
  1493. // Is this when they reach eachoters drawdistance, or when they sync?
  1494. return 1;
  1495. }
  1496. public OnPlayerStreamOut(playerid, forplayerid) // TODO for 0.0a
  1497. {
  1498. return 1;
  1499. }
  1500. public OnVehicleStreamIn(vehicleid, forplayerid) // TODO for 0.0a
  1501. {
  1502. return 1;
  1503. }
  1504. public OnVehicleStreamOut(vehicleid, forplayerid) // TODO for 0.0a
  1505. {
  1506. return 1;
  1507. }
  1508. public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]){
  1509. switch(dialogid){
  1510. case DIALOG_CHANGENAME:{
  1511. if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){ // Registered player
  1512. if(!response){ // User aborted
  1513. return 0; // Allow escaping dialogm without forcing to continue
  1514. }
  1515. }
  1516. changeName(playerid, inputtext); // Forced name check and change
  1517. }
  1518. case DIALOG_REGISTER:{
  1519. if(response){ // If they clicked 'Yes' or pressed enter
  1520. // Check password complexity
  1521. new Regex:r = Regex_New("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[0-9!@#\\$%\\^&\\*\\(\\)\\-\\_=+[{\\]}\\\\|;:'\",<.>\\/?]).{8,}$"); // Regex password filter
  1522. new isSecurePassword = Regex_Check(inputtext, r); // Validate name to filter
  1523. Regex_Delete(r);
  1524. if(!isSecurePassword){ // Insecure password
  1525. ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Insecure password", "Enter a secure password, containg at least:\n\n * A lowercase letter (a-z)\n * A capiral letter (A-Z)\n * A number (0-9).\n * A character that is not a letter (0-9!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?).\n * 8 characters.", "Register", "Cancel"); // Show password requirements.
  1526. }
  1527. else { // Secure password
  1528. // Hash password
  1529. new buffer[HASH_LENGTH + 1], hash[HASH_LENGTH + 1];
  1530. WP_Hash(buffer, sizeof(buffer), inputtext);
  1531. GetGVarString("hash", hash, sizeof(hash), playerid);
  1532. if(isempty(hash)){ // First password dialog
  1533. SetGVarString("hash", buffer, playerid); // Save password has a GVar
  1534. ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password confirmation", "Repeat your password to confirm.", "Register", "Cancel"); // Show password confirmation dialog
  1535. }
  1536. else{ // Password confirmation
  1537. if(!isequal(hash, buffer)){ // Password does not match confirmation
  1538. DeleteGVar("hash", playerid);
  1539. SendClientMessage(playerid, COLOR_RED, "ERROR: Password does not match password confirmation.");
  1540. ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password", "Enter the same password twice.", "Continue", "Cancel"); // Password prompt
  1541. }
  1542. else{ // Password matches confirmation
  1543. DeleteGVar("hash", playerid);
  1544. new client_connect_username[MAX_PLAYER_NAME + 1], playerIP[IP_LEGNTH + 1], escaped_username[MAX_PLAYER_NAME + 1];
  1545. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
  1546. GetPlayerIp(playerid, playerIP, sizeof(playerIP));
  1547. sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username));
  1548. // Create user record
  1549. new user_query[61 + MAX_PLAYER_NAME + HASH_LENGTH + 1 + IP_LEGNTH + 1];
  1550. format(user_query, sizeof(user_query), "INSERT INTO \"user\"(name, password) VALUES('%s', '%s')", escaped_username, hash);
  1551. /*new Result:user_result = sql_query(sqlHandle, user_query);
  1552. //new id = sql_insert_id(user_result); // Broken, always returns 0.
  1553. // sql_insert_id workaround*/
  1554. sql_query(sqlHandle, user_query);
  1555. new id = getUserID(playerid); // Get ID user user record
  1556. SetGVarInt("userlevel", REGISTERED_PLAYER, playerid);
  1557. // Create user_ip relation table record
  1558. new user_ip_query[50 + SQL_INTERGER_LENGTH + SQL_INTERGER_LENGTH + 1 ];
  1559. format(user_ip_query, sizeof(user_ip_query), "INSERT INTO user_ip(id, id) VALUES(%i, %i)", getIPID(playerid), id);
  1560. sql_query(sqlHandle, user_ip_query);
  1561. createCharacterRecord(playerid, id); // Save character
  1562. // Inform user
  1563. new dialogMessage[135 + MAX_PLAYER_NAME + 1], message[31 + MAX_PLAYER_NAME + 1];
  1564. format(dialogMessage, sizeof(dialogMessage), "To login as any of your characters, connect as: \nRemember your username carefully, as you have to use it to connect with SA-MP client!", client_connect_username);
  1565. ShowPlayerDialog(playerid, DIALOG_ACCOUNT_CREATED, DIALOG_STYLE_MSGBOX, "Account created", dialogMessage, "Play", "");
  1566. format(message, sizeof(message), "SERVER: Remember your username, %s!", client_connect_username);
  1567. SendClientMessage(playerid, COLOR_WARNING_MESSAGE, message);
  1568. // send discord admin echo
  1569. new admin_message[30 + 4 + MAX_PLAYER_NAME + 1];
  1570. format(admin_message, sizeof(admin_message), "[%i] %s has registered an account.", playerid, client_connect_username);
  1571. DiscordEcho(message, ADMIN_ECHO_CHANNEL);
  1572. }
  1573. }
  1574. }
  1575. }
  1576. else{ // Pressed ESC or clicked cancel
  1577. DeleteGVar("hash", playerid);
  1578. }
  1579. }
  1580. case DIALOG_LOGIN:{
  1581. if(response){ // If they clicked 'Yes' or pressed enter
  1582. // Escape username
  1583. new client_connect_username[MAX_PLAYER_NAME + 1], escaped_username[MAX_PLAYER_NAME + 1];
  1584. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid);
  1585. sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username));
  1586. // Get account
  1587. new user_query[82 + MAX_PLAYER_NAME + 1], hash[HASH_LENGTH + 1];
  1588. format(user_query, sizeof(user_query), "SELECT id, password, level FROM \"user\" WHERE name = '%s'", escaped_username);
  1589. new Result:user_result = sql_query(sqlHandle, user_query);
  1590. sql_get_field_assoc_ex(user_result, 0, "password", hash, sizeof(hash));
  1591. // Compare hashes
  1592. new buffer[HASH_LENGTH + 1];
  1593. WP_Hash(buffer, sizeof(buffer), inputtext);
  1594. if (!isequal(buffer, hash)){ // Hashes don't match
  1595. // Brute-force protection
  1596. new authentication_count = GetGVarInt("authentication_count", playerid);
  1597. authentication_count++;
  1598. SetGVarInt("authentication_count", authentication_count, playerid);
  1599. ShowPlayerDialog(playerid, DIALOG_LOGIN_FAILED, DIALOG_STYLE_MSGBOX, "Authenticaion failed", "Invalid password.", "Wait", "");
  1600. switch(authentication_count){
  1601. case 0: SetTimerEx("authenticate", 3000, false, "i", playerid); // Return to authentication workflow in 3 second.
  1602. case 1: SetTimerEx("authenticate", 5000, false, "i", playerid); // Return to authentication workflow in 5 seconds.
  1603. case 2: SetTimerEx("authenticate", 7000, false, "i", playerid); // Return to authentication workflow in 7 seconds.
  1604. default: ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", ""); // After 4 failed authentication attempts
  1605. }
  1606. }
  1607. else{ // Hashes match
  1608. // Check bans
  1609. /*new ip_query[45 + IP_LEGNTH + 1];
  1610. format(ip_query, sizeof(ip_query), "SELECT ip_id, reason FROM ip WHERE address = '%s'", playerip);
  1611. new Result:ip_result = sql_query(sqlHandle, ip_query);
  1612. new ip_id = sql_get_field_assoc_int(ip_result, "ip_id");*/
  1613. new ban_query[79 + SQL_INTERGER_LENGTH + 1];
  1614. format(ban_query, sizeof(ban_query), "SELECT id, reason FROM user_ban WHERE user_id = %i AND expires > NOW()::timestamp", getUserID(playerid));
  1615. new Result:ban_result = sql_query(sqlHandle, ban_query);
  1616. if(sql_num_rows(ban_result) > 0){ // Banned
  1617. SendClientMessage(playerid, COLOR_DEFAULT_COMMAND_OUTPUT, "You may appeal your ban via the forum or Discord.");
  1618. new reason[121 + 1];
  1619. sql_get_field_assoc(ban_result, "reason", reason, sizeof(reason));
  1620. banPlayer(playerid, -1, reason, 10);
  1621. }
  1622. // Set userlevel
  1623. new userlevel = sql_get_field_assoc_int(user_result, "level");
  1624. SetGVarInt("userlevel", userlevel, playerid);
  1625. // Get character names
  1626. characterSelection(playerid);
  1627. }
  1628. }
  1629. else{ // Pressed ESC or clicked cancel
  1630. ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
  1631. }
  1632. }
  1633. case DIALOG_CHANGE_USERNAME:{
  1634. if(response){ // If they clicked 'Yes' or pressed enter
  1635. new Regex:r = Regex_New("^[0-9a-zA-Z\\[\\]\\(\\)\\$@._=]{1,}$"); // Regex name filter
  1636. new isValidName = Regex_Check(inputtext, r);
  1637. Regex_Delete(r);
  1638. if(isValidName){ // Valid name
  1639. // Get user record
  1640. new escaped_username[MAX_PLAYER_NAME + 1], user_query[36 + MAX_PLAYER_NAME + 1];
  1641. sql_escape_string(sqlHandle, inputtext, escaped_username, sizeof(escaped_username)); // Escape player name
  1642. format(user_query, sizeof(user_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username);
  1643. //new Result:result = sqlQuery(sqlHandle, user_query); // Middleware broken.
  1644. new Result:result = sql_query(sqlHandle, user_query);
  1645. if(sql_num_rows(result) > 0){ // Username taken
  1646. ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
  1647. }
  1648. else{ // Username free
  1649. SetGVarString("client_connect_username", inputtext, playerid); // Used by register and DIALOG_LOGIN
  1650. SetPlayerName(playerid, inputtext); // Change name in-game
  1651. return 1; // Do nothing to let player continue class selection
  1652. }
  1653. }
  1654. else{ // Invalid name
  1655. ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Invalid name. Names may only contain numbers (0-9), letters (a-z) & (A-Z) and special characters ([]()$@._=)", "Change", "");
  1656. }
  1657. }
  1658. else{ // Pick another username
  1659. ShowPlayerDialog(playerid, DIALOG_CHANGE_USERNAME, DIALOG_STYLE_INPUT, "Username", "Pick an unregistered username.", "Change", "");
  1660. }
  1661. }
  1662. case DIALOG_CHARACTERS:{
  1663. if(response){ // Spawn as character
  1664. new character_array_string[MAX_CHARACTERS_PER_USER * SQL_INTERGER_LENGTH + 1], character_array[MAX_CHARACTERS_PER_USER];
  1665. GetGVarString("character_array_string", character_array_string, sizeof(character_array_string), playerid);
  1666. DeleteGVar("character_array_string");
  1667. strtobin(character_array, character_array_string);
  1668. new character_query[100 + MAX_PLAYER_NAME + 1], name[24 + 1];
  1669. format(character_query, sizeof(character_query), "SELECT name, skin_id, cash, armour, health, pos_x, pos_y, pos_z, rotation FROM character WHERE id = %i", character_array[listitem]);
  1670. new Result:character_result = sql_query(sqlHandle, character_query);
  1671. sql_get_field_assoc(character_result, "name", name, sizeof(name));
  1672. new skin_id = sql_get_field_assoc_int(character_result, "skin_id");
  1673. new cash = sql_get_field_assoc_int(character_result, "cash");
  1674. new Float:armour = sql_get_field_assoc_int(character_result, "armour");
  1675. new Float:health = sql_get_field_assoc_int(character_result, "health");
  1676. new Float:pos_x = sql_get_field_assoc_int(character_result, "pos_x");
  1677. new Float:pos_y = sql_get_field_assoc_int(character_result, "pos_y");
  1678. new Float:pos_z = sql_get_field_assoc_int(character_result, "pos_z");
  1679. new Float:rotation = sql_get_field_assoc_int(character_result, "rotation");
  1680. // Temporary bug workaround TODO find cause
  1681. if(pos_x == 0 && pos_y == 0 && pos_z == 0){ // Sometimes this happens. Due to unreliable OnPlayerDisconnect() & OnGamemodeExit(), due to character creation, or due to (forgot the other possiblity, but had a strong hunch)?
  1682. pos_x = -144.0328;
  1683. pos_y = 1225.0564;
  1684. pos_z = 19.8992;
  1685. rotation = 175.5507;
  1686. }
  1687. SetSpawnInfo(playerid, 0, skin_id, pos_x, pos_y, pos_z, rotation, 0, 0, 0, 0, 0, 0);
  1688. //SetSpawnInfo(playerid, 0, skin_id, -144.0328, 1225.0564, 19.8992, 175.5507, 0, 0, 0, 0, 0, 0 );
  1689. SetPlayerName(playerid, name); // Change name in-game
  1690. SpawnPlayer(playerid);
  1691. ResetPlayerMoney(playerid);
  1692. GivePlayerMoney(playerid, cash);
  1693. SetPlayerHealth(playerid, health);
  1694. SetPlayerArmour(playerid, armour);
  1695. // Notify all players
  1696. new message[16 + 4 + MAX_PLAYER_NAME + 1];
  1697. format(message, sizeof(message), "* [%i] %s joined.", playerid, fromPlayerName(name));
  1698. SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players.
  1699. DiscordEcho(message, ECHO_CHANNEL); // Notify discord public echo
  1700. }
  1701. else{ // New character
  1702. // character cap
  1703. new id = getUserID(playerid);
  1704. new character_query[60 + SQL_INTERGER_LENGTH + 1];
  1705. format(character_query, sizeof(character_query), "SELECT id FROM character WHERE character_id = %i", id);
  1706. new Result:character_result = sql_query(sqlHandle, character_query);
  1707. printf("%i", sql_num_rows(character_result));
  1708. if(sql_num_rows(character_result) >= MAX_CHARACTERS_PER_USER){ // At or over character limit
  1709. SendClientMessage(playerid, COLOR_WARNING_MESSAGE, "SERVER: Maximum characters reached, can not create another.");
  1710. characterSelection(playerid);
  1711. }
  1712. else{
  1713. ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Pick a name.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", ""); // Force RP name.
  1714. // changeName(playerid, ""); // Pick name and save character ANNOYING this forces new character creation, no way out.
  1715. }
  1716. }
  1717. }
  1718. case DIALOG_LOGIN_FAILED:{
  1719. ShowPlayerDialog(playerid, DIALOG_LOGIN_FAILED, DIALOG_STYLE_MSGBOX, "Authenticaion failed", "Please wait...", "Wait", "");
  1720. }
  1721. case DIALOG_DELETE_CHARACTER:{
  1722. if(response){ // Delete character
  1723. new playername[MAX_PLAYER_NAME + 1];
  1724. GetPlayerName(playerid, playername, sizeof(playername));
  1725. // Checkcharacter amount
  1726. new id = getUserID(playerid), character_query[75 + SQL_INTERGER_LENGTH + MAX_CHARACTERS_PER_USER_DIGITS + 1];
  1727. format(character_query, sizeof(character_query), "SELECT id, name FROM character WHERE user_id = %i LIMIT %i", id, MAX_CHARACTERS_PER_USER);
  1728. new Result:character_result = sql_query(sqlHandle, character_query);
  1729. if(sql_num_rows(character_result) < 2){ // 1 character or less
  1730. SendClientMessage(playerid, COLOR_WARNING_MESSAGE, "SERVER: You can not have less then one character.\nCreate another character,before deleting this one.");
  1731. }
  1732. else{ // More then 1 character
  1733. // delete character record
  1734. new delete_query[50 + MAX_PLAYER_NAME + SQL_INTERGER_LENGTH + 1];
  1735. format(delete_query, sizeof(delete_query), "DELETE FROM character WHERE name = '%s' AND user_id = %i", playername, getUserID(playerid));
  1736. sql_query(sqlHandle, delete_query);
  1737. // infordm admin echo
  1738. new client_connect_username[MAX_PLAYER_NAME + 1], message[28 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1];
  1739. GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name
  1740. format(message, sizeof(message), "* [%i] %s has been deleted by it's user: %s", playerid, fromPlayerName(playername), client_connect_username);
  1741. DiscordEcho(message, ADMIN_ECHO_CHANNEL);
  1742. // TODO send to in-game adminchat
  1743. characterSelection(playerid);
  1744. }
  1745. }
  1746. }
  1747. }
  1748. return 0; // MUST return 0 here, just like OnPlayerCommandText.
  1749. }
  1750. public OnPlayerClickPlayer(playerid, clickedplayerid, source) // TODO for 0.0a
  1751. {
  1752. return 1;
  1753. }
  1754. public OnEnterExitModShop(playerid, enterexit, interiorid) // TODO for 0.0a
  1755. {
  1756. return 1;
  1757. }
  1758. public OnPlayerGiveDamage(playerid, damagedid, Float: amount, weaponid) // TODO for 0.0a
  1759. {
  1760. return 1;
  1761. }
  1762. public OnPlayerTakeDamage(playerid, issuerid, Float: amount, weaponid) // TODO for 0.0a
  1763. {
  1764. return 1;
  1765. }
  1766. // discord-connector events
  1767. public DCC_OnMessageCreate(DCC_Message:message){
  1768. // Originating Discord channel
  1769. new DCC_Channel:channel;
  1770. DCC_GetMessageChannel(message, channel);
  1771. // Originating Discord user
  1772. new DCC_User:author;
  1773. DCC_GetMessageAuthor(message, author);
  1774. // Message content
  1775. new str[256];
  1776. new command[32], params[128];
  1777. DCC_GetMessageContent(message, str);
  1778. sscanf(str, "s[32]s[128]", command, params); // This string is to small some times, and throws an error BROKEN TODO
  1779. // Ignore bots
  1780. new bool:isBot;
  1781. DCC_IsUserBot(author, isBot);
  1782. if(isBot){
  1783. return 1;
  1784. }
  1785. /*// Beyond this point, don't respond to commands on foreign guilds.
  1786. if(guild != homeGuild){
  1787. return 1;
  1788. }*/
  1789. // Beyond this point, only respond to privilaged channels.
  1790. if(channel != adminEchoChannel && channel != adminChannel && channel != managementChannel){
  1791. return 1;
  1792. }
  1793. // Test command.
  1794. if(!strcmp(command, "!test", true)){
  1795. DiscordSendChannelMessage(channel, "Works.");
  1796. print("Some said !test in the echo channel.");
  1797. }
  1798. return 1;
  1799. }
  1800. // Entrypoint
  1801. main(){
  1802. // Credits
  1803. print("\n*----------------------------------*");
  1804. print("TPFW");
  1805. new message[21 + 20 + 1];
  1806. format(message, sizeof(message), " Role-play framework v%s", MODE_NAME);
  1807. print(message);
  1808. //printf("System load average: %f",loadavg()); // Linux only TODO: test
  1809. print("*----------------------------------*\n");
  1810. }