1
0

RPFW.0.0a Build 6.pwn 100 KB

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