├── .gitattributes ├── .gitignore └── filterscripts └── driverfs.pwn /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /filterscripts/driverfs.pwn: -------------------------------------------------------------------------------- 1 | #if 0 2 | #pragma option -r 3 | #pragma option -d3 4 | #endif 5 | 6 | /* ----------------------------------------------------------------------------- 7 | 8 | Smooth NPC Drivers - Have some Singleplayer-like NPCs on your server! - by NaS & AIped (c) 2013-2016 9 | 10 | Version: 1.2.5 11 | 12 | This is the FS-version of our NPC Drivers Script. 13 | To find bugs and improve its features we decided to release this compact version for testing and experimenting purposes. 14 | 15 | Please post any suggestions or buggy encounters to our thread in the official SA-MP Forums (http://forum.sa-mp.com/showthread.php?t=587634). Thanks! 16 | 17 | Note: The steep-hills-glitch (vehicles snapping around while driving up/down) is not possible to fix script-wise. This is probably caused by the "smooth" but unprecise movement of NPCs client-sided and the node distances (Z not always precise). 18 | 19 | --------------- 20 | 21 | > Credits 22 | 23 | AIped - Initiator of the 2013 version, help, ideas, scripting, ... 24 | Gamer_Z - RouteConnector Plugin, QuaternionStuff Plugin and great help with some math-problems. 25 | OrMisicL & ZiGGi - FCNPC Plugin 26 | Pottus and all other developers of ColAndreas 27 | kvann - Modern GPS Plugin 28 | 29 | Feel free to use and modify as you wish, but don't re-release this without our permission! 30 | 31 | // ----------------------------------------------------------------------------- 32 | 33 | Latest Changenotes: 34 | 35 | [v1.2.5] 36 | 37 | - Added support for the Modern GPS Plugin by kvann/kristoisberg 38 | 39 | [v1.2.4] 40 | 41 | - Removed dependancy on QuaternionStuff 42 | - Now uses Nero_3D's rotations.inc to calculate Vehicle Rotations 43 | 44 | [v1.2.3] 45 | 46 | - Support for newest FCNPC Version (1.7.6) 47 | - NPCs can drive bikes (and also lean left/right in turns) 48 | - NPCs rotate on all axes now, depending on terrain 49 | - Taxi call will now be correctly aborted if the called Taxi gets killed by someone 50 | 51 | [v1.2.2] 52 | 53 | - Support for newest FCNPC Version (1.7.5) 54 | - Small optimizations 55 | 56 | [v1.2.1] 57 | 58 | - Support for newest FCNPC version (1.0.5) 59 | - Added Cops (count as civilains) 60 | Cops turn on the sirens sometimes to annoy their surroundings 61 | - Added Skin Arrays for different types (Civ, Cops, Taxis) 62 | - There is a very rough time measurement now when you choose your destination 63 | - Fixed stop'n'go (Using math instead of crappy distance guesses) 64 | - Better and more efficient movement streaming method (based on NPC Streaming..) - streamer not required anymore 65 | - Automatic start & end node calculation based for parking lots (1-connection nodes) + new GPS.dat with many 66 | fixes and a lot of new parking lots and improvements -> NPCs actually drive somewhere and wait a bit (will be extended) 67 | 68 | [v1.2] 69 | 70 | - General performance improvements 71 | - Using ColAndreas for more precise rotations 72 | . Using Streamer Plugin for Areas 73 | - Manually fixed hundreds of nodes 74 | - Reduction of path length (-> Less memory usage) 75 | - Streaming NPC-Movements (npcs will move very roughly (skip every 3 nodes) if no players are around -> saving quite a lot run-time calculations) 76 | - NPCs brake when someone (another npc) is in front of them 77 | 78 | [v1.1] 79 | 80 | - Improved random start- and destination node calculations (added PathNodes array), also improves NPC spreading around the world 81 | - Shortened paths on recalculations (performance improvement) 82 | - Improved speed calculations, speed changes over time by steepness and turning radiuses 83 | - Brakes when near to destination 84 | 85 | 86 | TO DO: 87 | 88 | - Add detection of players, make the NPCs Stop etc 89 | - Add more efficient code that detects other NPCs and Players in vicinity (current method is bad, but works for now) 90 | - Optimizations! 91 | 92 | *///---------------------------------------------------------------------------- 93 | 94 | 95 | // ----------------------------------------------------------------------------- 96 | 97 | #define SCRIPT_NAME "driverfs" // The Scriptname (by default driverfs.pwn) 98 | #define FILTERSCRIPT 99 | 100 | #include 101 | #undef MAX_PLAYERS 102 | #define MAX_PLAYERS (1000) // Redefine to your MAX_PLAYERS value to save some memory. 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | 109 | // ----------------------------------------------------------------------------- CONFIG 110 | 111 | #define NPC_NAMES "NPCDriver_%d" // %d will be replaced by the Drivers's ID (not NPC ID!) 112 | 113 | #define DEBUG_BUBBLE (false) // Lets NPCs show info via chat bubbles 114 | #define DEBUG_PRINTS (false) // Prints calculation times and warnings 115 | #define INFO_PRINTS (true) // Prints Driver Info every X seconds 116 | #define INFO_DELAY (300) // seconds 117 | #define MAP_ZONES (false) // Creates gang zones for every driver as replacement for a map marker (all npcs are always visible in ESC->Map) 118 | #define SEND_DEATH_MESSAGE (false) // Sends death message for killed NPC in chat (change in OnPlayerDeath to fit your Server) 119 | 120 | #define DRIVER_AMOUNT (450) // TOTAL NPC COUNT - Different driver types are part of the overall driver amount (300/20/20 = 300 NPCS of which are 20 Taxis, 20 Cops and 260 Normies) 121 | #define DRIVER_TAXIS (50) 122 | #define DRIVER_COPS (35) 123 | 124 | #define MAX_NODE_DIST (18.0) 125 | #define MIN_NODE_DIST (2.5) // Small changes here usually make a big difference. Do not go below 1.5. 126 | #define SIDE_DIST (2.02) // Distance to the center of the road, 2m is usually the best (unfortunately the actual data of road sizes hasn't been included with the GPS Plugin) 127 | #define SMOOTH_W_DATA (0.6) // Smoothing values, DATA - data weight, SMOOTH - smooth weight, should be bewtween 0.1 and 1.0 128 | #define SMOOTH_W_SMOOTH (0.2) 129 | #define SMOOTH_AMOUNT (20) // Amount of smoothing passes - was dynamic before but that made it hard to limit - medium smoothing is 15 (data 0.6, weight 0.2) 130 | 131 | #define MIN_SPEED (0.7) 132 | #define MAX_SPEED (2.2) 133 | #define DUTY_SPEED_BOOST (1.125) // Speed boost when on duty (Cops, Taxi) 134 | #define STEER_ANGLE (5.0) 135 | 136 | #define JAM_DIST (15.0) // Distance between 2 Drivers to make them slow down 137 | #define JAM_ANGLE (25) // INT! Max angle distance between 2 Drivers to make them slow down 138 | 139 | #define MAX_PATH_LEN (1350) 140 | 141 | #define MAX_TAXI_DIST (3000.0) // Max Distance to a taxi to respond 142 | #define TAXI_RANGE (35.0) // Max Range to closest nodes (Upon calling) 143 | #define TAXI_COOLDOWN (60) // seconds 144 | #define TAXI_TIMEOUT (40) // seconds 145 | 146 | #define ROUTE_MIN_DIST (900.0) 147 | #define ROUTE_MAX_DIST (2400.0) 148 | 149 | #define DRIVERS_ROUTE_ID (10000) // Starting routeid for path calculations - change if conflicts arise 150 | #define DIALOG_ID (10000) // Starting dialogid for dialogs - change if conflicts arise 151 | 152 | // ----------------------------------------------------------------------------- INTERNAL CONFIG/DEFINES 153 | 154 | #define DRIVER_TYPE_RANDOM (0) 155 | #define DRIVER_TYPE_TAXI (1) 156 | #define DRIVER_TYPE_COP (2) 157 | 158 | #define DRIVER_STATE_NONE (0) 159 | #define DRIVER_STATE_DRIVE (1) 160 | #define DRIVER_STATE_PAUSE (2) 161 | 162 | #define MAX_PATH_NODES (800) // Max Start & End Nodes 163 | 164 | #define TAXI_STATE_NONE (0) 165 | #define TAXI_STATE_DRIVE1 (1) 166 | #define TAXI_STATE_WAIT1 (2) 167 | #define TAXI_STATE_DRIVE2 (3) 168 | 169 | #define ZONES_NUM (90) // This is just for determining npc distances to each other via integers, lower value means bigger zones -> more npcs to check 170 | 171 | #define DID_TAXI (DIALOG_ID + 0) 172 | 173 | #pragma dynamic (50000) // Needs to be higher for longer paths/more npcs (for 900 - you can lower this or remove it if you use less than 400)! 174 | 175 | // ----------------------------------------------------------------------------- DEFINE WHERE NPCS SPAWN/GOTO - If you narrow it down to a small area lower the NPC Amount proportionally! 176 | 177 | #define MAP_ENABLE_LS (true) // Note that if you enable (for example) only LV and SF, the drivers will most likely drive from LV to SF and vice-versa as well. 178 | #define MAP_ENABLE_COUNTY (true) 179 | #define MAP_ENABLE_SF (true) 180 | #define MAP_ENABLE_LV (true) 181 | #define MAP_ENABLE_LV_DESERT (true) 182 | 183 | #if MAP_ENABLE_LS != true && MAP_ENABLE_SF != true && MAP_ENABLE_LV != true && MAP_ENABLE_LV_DESERT != true && MAP_ENABLE_COUNTY != true 184 | #error You must at least enable one area (MAP_ENABLE_* defines) 185 | #endif 186 | 187 | #if MAP_ENABLE_LS != true || MAP_ENABLE_SF != true || MAP_ENABLE_LV != true || MAP_ENABLE_LV_DESERT != true || MAP_ENABLE_COUNTY != true 188 | #if MAP_ENABLE_LS != true 189 | new Float:LSCoords[4][4] = // maxx, maxy, minx, miny - Created with GTA Zone Editor by zeppelin - Quite rough but works perfectly for nodes 190 | { 191 | {2992.19, -1093.75, 70.94, -2851.56}, 192 | {2984.38, -875.00, 257.81, -1093.75}, 193 | {1601.56, -687.50, 750.00, -875.00}, 194 | {1601.56, -585.94, 882.81, -695.31} 195 | }; 196 | #endif 197 | #if MAP_ENABLE_SF != true 198 | new Float:SFCoords[4][4] = // maxx, maxy, minx, miny 199 | { 200 | {-1421.88, 1562.50, -2898.44, -710.94}, 201 | {-1171.88, 617.19, -1453.13, -695.31}, 202 | {-1023.44, 54.69, -1195.31, -375.00}, 203 | {-1867.19, -703.13, -2265.63, -1062.50} 204 | }; 205 | #endif 206 | #if MAP_ENABLE_LV != true 207 | new Float:LVCoords[4] = // maxx, maxy, minx, miny 208 | {3015.63, 3031.25, 859.38, 625.00}; 209 | #endif 210 | #if MAP_ENABLE_LV_DESERT != true 211 | new Float:LVDesertCoords[4][4] = // maxx, maxy, minx, miny 212 | { 213 | {859.38, 3000.00, -875.00, 523.44}, 214 | {-875.00, 3007.81, -1320.31, 875.00}, 215 | {-1304.69, 3015.63, -2117.19, 1671.88}, 216 | {-2117.19, 3007.81, -2976.56, 2117.19} // Bayside! 217 | }; 218 | #endif 219 | #if MAP_ENABLE_COUNTY != true 220 | new Float:CountyCoords[8][4] = // maxx, maxy, minx, miny 221 | { 222 | {46.88, -1085.94, -2945.31, -2968.75}, 223 | {257.81, -695.31, -1898.44, -1085.94}, 224 | {250.00, -375.00, -1187.50, -695.31}, 225 | {250.00, 335.94, -1015.63, -375.00}, 226 | {765.63, 445.31, 234.38, -929.69}, 227 | {882.81, 453.13, 765.63, -695.31}, 228 | {2968.75, 593.75, 882.81, -585.94}, 229 | {2976.56, -570.31, 1593.75, -875.00} 230 | }; 231 | #endif 232 | #endif 233 | 234 | // ----------------------------------------------------------------------------- Arrays, Vars etc 235 | 236 | enum E_DRIVERS 237 | { 238 | bool:nUsed, 239 | bool:nOnDuty, 240 | bool:nActive, // Active means a player is close (-> does all calculations, otherwise skips some nodes and doesnt process collision/rotation) 241 | nNPCID, 242 | nType, 243 | nState, 244 | nCurNode, 245 | MapNode:nLastStart, 246 | MapNode:nLastDest, 247 | Float:nDistance, 248 | Float:nSpeed, 249 | nSkinID, 250 | nVehicle, 251 | nVehicleModel, 252 | bool:nVehicleIsBike, 253 | Float:nVehicleLastLean, 254 | nPlayer, 255 | nLT, // Last Tick 256 | nCopStuffTick, 257 | nCalcFails, 258 | nZoneX, 259 | nZoneY, 260 | nDeathTick, 261 | bool:nResetVeh 262 | 263 | #if MAP_ZONES == true 264 | , nGangZone 265 | #endif 266 | }; 267 | new Drivers[DRIVER_AMOUNT][E_DRIVERS]; 268 | new NPCDriverID[MAX_PLAYERS] = {-1, ...}; 269 | 270 | new Float:DriverPath[DRIVER_AMOUNT][MAX_PATH_LEN][3]; 271 | new DriverPathLen[DRIVER_AMOUNT]; 272 | 273 | new PlayerEnterDriver[MAX_PLAYERS] = {-1, ...}; 274 | 275 | new Float:VehicleZOffsets[] = // Contains normal 4wheel vehicles, including Quad, Police Cars and Police Rancher, since the angle calculations also some bikes 276 | { 277 | 1.0982/*(400)*/,0.7849/*(401)*/,0.8371/*(402)*/,-1000.0/*(403)*/,0.7416/*(404)*/,0.8802/*(405)*/,-1000.0/*(406)*/,-1000.0/*(407)*/,-1000.0/*(408)*/,0.7901/*(409)*/, 278 | 0.6667/*(410)*/,-1000.0/*(411)*/,0.8450/*(412)*/,-1000.0/*(413)*/,-1000.0/*(414)*/,0.7754/*(415)*/,-1000.0/*(416)*/,-1000.0/*(417)*/,-1000.0/*(418)*/,0.8033/*(419)*/, 279 | 0.7864/*(420)*/,0.8883/*(421)*/,0.9969/*(422)*/,-1000.0/*(423)*/,0.7843/*(424)*/,-1000.0/*(425)*/,0.7490/*(426)*/,-1000.0/*(427)*/,1.1306/*(428)*/,0.6862/*(429)*/, 280 | -1000.0/*(430)*/,-1000.0/*(431)*/,-1000.0/*(432)*/,-1000.0/*(433)*/,-1000.0/*(434)*/,-1000.0/*(435)*/,0.7756/*(436)*/,-1000.0/*(437)*/,1.0092/*(438)*/,0.9020/*(439)*/, 281 | 1.1232/*(440)*/,-1000.0/*(441)*/,0.8379/*(442)*/,-1000.0/*(443)*/,-1000.0/*(444)*/,0.8806/*(445)*/,-1000.0/*(446)*/,-1000.0/*(447)*/,0.5835/*(448)*/,-1000.0/*(449)*/, 282 | -1000.0/*(450)*/,-1000.0/*(451)*/,-1000.0/*(452)*/,-1000.0/*(453)*/,-1000.0/*(454)*/,-1000.0/*(455)*/,-1000.0/*(456)*/,-1000.0/*(457)*/,0.8842/*(458)*/,-1000.0/*(459)*/, 283 | -1000.0/*(460)*/,0.5674/*(461)*/,0.5917/*(462)*/,0.5328/*(463)*/,-1000.0/*(464)*/,-1000.0/*(465)*/,0.7490/*(466)*/,0.7465/*(467)*/,-1000.0/*(468)*/,-1000.0/*(469)*/, 284 | -1000.0/*(470)*/,0.3005/*(471)*/,-1000.0/*(472)*/,-1000.0/*(473)*/,0.7364/*(474)*/,0.8077/*(475)*/,-1000.0/*(476)*/,-1000.0/*(477)*/,1.0010/*(478)*/,0.7994/*(479)*/, 285 | 0.7799/*(480)*/,-1000.0/*(481)*/,1.1209/*(482)*/,-1000.0/*(483)*/,-1000.0/*(484)*/,-1000.0/*(485)*/,-1000.0/*(486)*/,-1000.0/*(487)*/,-1000.0/*(488)*/,1.1498/*(489)*/, 286 | -1000.0/*(490)*/,0.7619/*(491)*/,0.7875/*(492)*/,-1000.0/*(493)*/,-1000.0/*(494)*/,1.3588/*(495)*/,0.7226/*(496)*/,-1000.0/*(497)*/,1.0726/*(498)*/,0.9988/*(499)*/, 287 | 1.1052/*(500)*/,-1000.0/*(501)*/,-1000.0/*(502)*/,-1000.0/*(503)*/,-1000.0/*(504)*/,1.1498/*(505)*/,0.7100/*(506)*/,0.8319/*(507)*/,1.3809/*(508)*/,-1000.0/*(509)*/, 288 | -1000.0/*(510)*/,-1000.0/*(511)*/,-1000.0/*(512)*/,-1000.0/*(513)*/,1.5913/*(514)*/,-1000.0/*(515)*/,0.8388/*(516)*/,0.8608/*(517)*/,0.6761/*(518)*/,-1000.0/*(519)*/, 289 | -1000.0/*(520)*/,0.5569/*(521)*/,0.5529/*(522)*/,0.5569/*(523)*/,-1000.0/*(524)*/,-1000.0/*(525)*/,0.7724/*(526)*/,0.7214/*(527)*/,-1000.0/*(528)*/,0.6374/*(529)*/, 290 | -1000.0/*(530)*/,-1000.0/*(531)*/,-1000.0/*(532)*/,0.7152/*(533)*/,0.7315/*(534)*/,0.7702/*(535)*/,0.7437/*(536)*/,-1000.0/*(537)*/,-1000.0/*(538)*/,-1000.0/*(539)*/, 291 | 0.8672/*(540)*/,-1000.0/*(541)*/,0.7501/*(542)*/,0.8309/*(543)*/,-1000.0/*(544)*/,0.8169/*(545)*/,0.7293/*(546)*/,0.7404/*(547)*/,-1000.0/*(548)*/,0.7048/*(549)*/, 292 | 0.8274/*(550)*/,0.8066/*(551)*/,-1000.0/*(552)*/,-1000.0/*(553)*/,1.0894/*(554)*/,0.6901/*(555)*/,-1000.0/*(556)*/,-1000.0/*(557)*/,0.6349/*(558)*/,0.6622/*(559)*/, 293 | 0.7105/*(560)*/,0.8190/*(561)*/,0.6632/*(562)*/,-1000.0/*(563)*/,-1000.0/*(564)*/,0.6317/*(565)*/,0.7889/*(566)*/,0.8733/*(567)*/,0.8720/*(568)*/,-1000.0/*(569)*/, 294 | -1000.0/*(570)*/,-1000.0/*(571)*/,-1000.0/*(572)*/,-1000.0/*(573)*/,-1000.0/*(574)*/,0.6107/*(575)*/,0.6128/*(576)*/,-1000.0/*(577)*/,-1000.0/*(578)*/,0.9359/*(579)*/, 295 | 0.8016/*(580)*/,-1000.0/*(581)*/,-1000.0/*(582)*/,-1000.0/*(583)*/,-1000.0/*(584)*/,0.5899/*(585)*/,-1000.0/*(586)*/,0.7336/*(587)*/,-1000.0/*(588)*/,0.6643/*(589)*/, 296 | -1000.0/*(590)*/,-1000.0/*(591)*/,-1000.0/*(592)*/,-1000.0/*(593)*/,-1000.0/*(594)*/,-1000.0/*(595)*/,0.7278/*(596)*/,0.7756/*(597)*/,0.7178/*(598)*/,1.1971/*(599)*/, 297 | 0.7171/*(600)*/,-1000.0/*(601)*/,0.8129/*(602)*/,0.8440/*(603)*/,-1000.0/*(604)*/,-1000.0/*(605)*/,-1000.0/*(606)*/,-1000.0/*(607)*/,-1000.0/*(608)*/,1.0727/*(609)*/, 298 | -1000.0/*(610)*/,-1000.0/*(611)*/ 299 | }; 300 | 301 | new DriverSkins[] = // Skin IDs for citizens, not sorted - no specific/story skins 302 | { 303 | 10, 101, 12, 13, 136, 14, 142, 143, 15, 151, 156, 168, 169, 304 | 17, 170, 180, 182, 183, 184, 263, 186, 185, 19, 216, 91, 206, 305 | 21, 22, 210, 214, 215, 220, 221, 225, 226, 222, 223, 227, 231, 306 | 228, 234, 76, 235, 236, 89, 88, 24, 218, 240, 25, 250, 261, 40, 307 | 41, 35, 37, 38, 44, 69, 43, 46, 9, 93, 39, 48, 47, 229, 58, 59, 308 | 60, 233, 72, 55, 94, 95, 98, 241, 242, 73, 83 309 | }; 310 | 311 | new CopSkins[] = // Skin IDs for cops 312 | { 313 | 280, 281, 282, 283, 288, 306, 307, 310, 311 314 | }; 315 | 316 | new TaxiSkins[] = // Skin IDs for taxi drivers - kind of randomly picked 317 | { 318 | 188, 20, 36, 262, 7, 56 319 | }; 320 | 321 | new MapNode:PathNodes[MAX_PATH_NODES], PathNodesNum = 0; // Start & End Nodes for paths - generated at init - Always use newest GPS.dat to have enough 1-connection nodes & well spread NPCs! 322 | // I'll gather such nodes until we have about 1200 or even more. 323 | new IgnoredPathNodes[] = // Nodes to ignore for start/end nodes - mostly too many at one spot except stated otherwise - will NOT be ignored for regular driving 324 | { 325 | // Parking LS (too many) 326 | 9522,9513,9503,9511,9502, 327 | // Underground Parking LS 328 | 3845,3844,3852,3857, 329 | // Underground Parking SF - Some left over ;) 330 | 21198,21143,21148,21205,21149,21204,21206,21150,21172,21187,21112,21129,21115,21109, 331 | // Underground Parking LV 332 | 27155,27158,27165,27164,27156, 333 | // South LV Houses 334 | 19386,19388,19392,19393,19398,19277,19920,19984,19991,19985,19937,19768, 335 | 19977,19971,23291,19264,19343,23624,19263,19359,19350,19344,19349,19271, 336 | // More stupid LV nodes 337 | 24559,24555,24551, 338 | // Chiliad - Completely ignored 339 | 1895,2214,2232, 340 | // Country jump bridge (connection removed - two bad endpoints) 341 | 19517, 19516 342 | }; 343 | 344 | new RandomVehicleList[212], VehicleListNum = 0; 345 | 346 | new Taxi[MAX_PLAYERS] = {-1, ...}; // -1 => no taxi called, everything else => driverid 347 | new TaxiState[MAX_PLAYERS] = {TAXI_STATE_NONE, ...}; 348 | new LastTaxiInteraction[MAX_PLAYERS]; 349 | new bool:InTaxiView[MAX_PLAYERS]; 350 | 351 | enum E_DESTINATIONS 352 | { 353 | destName[36], 354 | Float:destX, 355 | Float:destY, 356 | Float:destZ 357 | }; 358 | new gDestinationList[][E_DESTINATIONS] = 359 | { 360 | {"Los Santos Airport", 1643.2167, -2241.9209, 13.4900}, 361 | {"Grove Street (LS)", 2500.9397, -1669.3757, 13.3438}, 362 | {"Skatepark (LS)", 1923.5677,-1403.0310,13.2974}, 363 | {"Mount Chiliad (LS)", -2250.8413,-1719.0470,480.0685}, 364 | {"--------"}, 365 | {"San Fierro Airport", -1424.2325, -291.3162, 14.1484}, 366 | {"Jizzy's Club (SF)", -2625.6680, 1382.9760, 7.1820}, 367 | {"Wang Cars (SF)", -1976.1716, 287.7719, 35.1719}, 368 | {"Avispa Country Club (SF)", -2723.8706, -312.4941, 7.1875}, 369 | {"Otto's Autos (SF)", -1628.4856, 1198.1681, 7.0391}, 370 | {"--------"}, 371 | {"Las Venturas Airport", 1682.3629, 1447.5713, 10.7722,}, 372 | {"Four Dragons Casino (LV)", 2033.4517, 1009.9388, 10.8203}, 373 | {"Caligula's Casino (LV)", 2158.8887, 1679.9889, 10.6953}, 374 | {"Yellow Bell Golf Club (LV)", 1464.2926, 2773.0825, 10.6719}, 375 | {"--------"}, 376 | {"Blueberry (LS)", 200.8919, -144.7279, 1.5859}, 377 | {"Palomino Creek (LS)", 2266.0808, 27.1097, 26.1645}, 378 | {"Dillimore (LS)", 660.9581, -535.4933, 16.3359}, 379 | {"Bayside (SF/LV)", -2466.1084, 2234.2334, 4.5125}, 380 | {"Angel Pine (SF/LS)", -2119.8252, -2492.1013, 30.6250}, 381 | {"El Quebrados (LV)", -1516.0896, 2540.1277, 55.6875}, 382 | {"Las Barrancas (LV)", -745.9706, 1565.6580, 26.9609}, 383 | {"Las Payasadas (LV)", -170.1701, 2693.7996, 62.4128}, 384 | {"Bone County (LV)", 712.7426, 1920.7234, 5.5391}, 385 | {"Verdant Meadows (LV)", 399.0638, 2484.6252, 16.484375} 386 | }; 387 | new gDestinationDialogSTR[678]; 388 | 389 | new MaxPathLen = 0; 390 | 391 | new rescueid = 0; // Current ID to check in the RescueTimer (only checks few entries (20) each time it calls to prevent long loops) 392 | new avgcalctimes[50] = {100, ...}, avgcalcidx; 393 | new avgticks[50] = {200, ...}, avgtickidx; 394 | new rescuetimer = -1; 395 | #if INFO_PRINTS == true 396 | new updtimer = -1; 397 | #endif 398 | 399 | new bool:Initialized = false; 400 | new InitialCalculations = 0, InitialCalculationStart; 401 | 402 | new NumRouteCalcs = 0, ExitPlayerID = -1; // Important for smooth FS unloading 403 | 404 | // ----------------------------------------------------------------------------- 405 | 406 | forward Float:SmoothPath(const Float:path[][2], len = sizeof path); 407 | forward Float:OffsetPath(const Float:path[MAX_PATH_LEN][2], len, Float:d); 408 | forward Float:Get2DAngleOf3Points(Float:x1, Float:y1, Float:x2, Float:y2, Float:x3, Float:y3); 409 | forward Float:RayCastLineZ(Float:X, Float:Y, Float:Z, Float:dist); 410 | forward Float:floatangledistdir(Float:firstAngle, Float:secondAngle); 411 | forward MapNode:GetRandomStartEndPathNode(); 412 | 413 | // ----------------------------------------------------------------------------- 414 | 415 | public OnFilterScriptInit() 416 | { 417 | Drivers_Init(); 418 | 419 | return 1; 420 | } 421 | 422 | public OnFilterScriptExit() 423 | { 424 | Drivers_Exit(1, 0); 425 | 426 | return 1; 427 | } 428 | 429 | public OnGameModeInit() 430 | { 431 | Drivers_Init(); 432 | 433 | return 1; 434 | } 435 | 436 | public OnGameModeExit() 437 | { 438 | Drivers_Exit(1, 1); 439 | 440 | return 1; 441 | } 442 | 443 | // ----------------------------------------------------------------------------- 444 | 445 | Drivers_Init() 446 | { 447 | if(Initialized) return 1; 448 | 449 | new name[MAX_PLAYER_NAME], cmp[MAX_PLAYER_NAME], len; 450 | strcat(cmp, NPC_NAMES); 451 | for(new i = 0; i < strlen(cmp); i ++) if(cmp[i] == '%') { cmp[i] = 0; break; } 452 | len = strlen(cmp); 453 | 454 | for(new i = 0; i < DRIVER_AMOUNT; i ++) Drivers[i][nNPCID] = -1; 455 | 456 | if(len >= 3) 457 | { 458 | for(new i = 0; i < MAX_PLAYERS; i ++) 459 | { 460 | if(!FCNPC_IsValid(i)) continue; 461 | 462 | GetPlayerName(i, name, MAX_PLAYER_NAME); 463 | 464 | if(strcmp(name, cmp, false, strlen(cmp)) == 0 && strlen(name) == len) FCNPC_Destroy(i); 465 | } 466 | } 467 | 468 | FCNPC_SetUpdateRate(30); 469 | 470 | CA_Init(); // You should uncomment this if you don't initialize ColAndreas before this FS gets loaded! 471 | 472 | format(gDestinationDialogSTR, sizeof(gDestinationDialogSTR), ""); 473 | 474 | new minsize = sizeof(gDestinationList) * 9 + 1; // plus ("\n" + color code(8)) * number of entries 475 | for(new i = 0; i < sizeof(gDestinationList); i ++) minsize += strlen(gDestinationList[i][destName]); 476 | 477 | if(sizeof(gDestinationDialogSTR) < minsize) printf("[DRIVERS] Warning: Higher the size of gDestinationDialogSTR from %d to at least %d. Not all Destinations can be displayed.", sizeof(gDestinationDialogSTR), minsize); 478 | 479 | for(new i = 0; i < sizeof(gDestinationList); i ++) 480 | { 481 | if(strlen(gDestinationDialogSTR) >= sizeof(gDestinationDialogSTR) - strlen(gDestinationList[i][destName]) - 10) break; // In case gDestinationDialogSTR is too small, stop at the last Teleport that fits in to prevent cut-off 482 | 483 | if(gDestinationList[i][destName][0] == '-') format(gDestinationDialogSTR, sizeof(gDestinationDialogSTR), "%s{666666}%s\n", gDestinationDialogSTR, gDestinationList[i][destName]); 484 | else format(gDestinationDialogSTR, sizeof(gDestinationDialogSTR), "%s{999999}%s\n", gDestinationDialogSTR, gDestinationList[i][destName]); 485 | } 486 | 487 | if(rescuetimer != -1) KillTimer(rescuetimer); 488 | rescuetimer = SetTimer("RescueTimer", 500, 1); 489 | 490 | #if INFO_PRINTS == true 491 | if(updtimer != -1) KillTimer(updtimer); 492 | updtimer = SetTimer("PrintDriverUpdate", INFO_DELAY*1000, 1); 493 | #endif 494 | 495 | // ---------------- GENERATE START & END NODES 496 | 497 | new Float:X, Float:Y, Float:Z; 498 | 499 | for(new MapNode:i, max_node = GetHighestMapNodeID(); _:i <= max_node && PathNodesNum < MAX_PATH_NODES; i ++) 500 | { 501 | if(!IsValidMapNode(i)) continue; 502 | 503 | new count; 504 | GetMapNodeConnectionCount(i, count); 505 | 506 | if(count != 1) continue; 507 | 508 | new bool:ignore = false; 509 | for(new j = 0; j < sizeof(IgnoredPathNodes); j ++) if(i == MapNode:IgnoredPathNodes[j]) 510 | { 511 | ignore = true; 512 | break; 513 | } 514 | 515 | if(ignore) continue; 516 | 517 | #if MAP_ENABLE_LS != true || MAP_ENABLE_SF != true || MAP_ENABLE_LV != true || MAP_ENABLE_LV_DESERT != true || MAP_ENABLE_COUNTY != true // Check for disabled zones (if any) 518 | 519 | GetMapNodePos(i, X, Y, Z); 520 | 521 | #if MAP_ENABLE_LS != true 522 | for(new j = 0; j < sizeof(LSCoords); j ++) if(X < LSCoords[j][0] && Y < LSCoords[j][1] && X > LSCoords[j][2] && Y > LSCoords[j][3]) 523 | { 524 | ignore = true; 525 | break; 526 | } 527 | if(ignore) continue; 528 | #endif 529 | 530 | #if MAP_ENABLE_SF != true 531 | for(new j = 0; j < sizeof(SFCoords); j ++) if(X < SFCoords[j][0] && Y < SFCoords[j][1] && X > SFCoords[j][2] && Y > SFCoords[j][3]) 532 | { 533 | ignore = true; 534 | break; 535 | } 536 | if(ignore) continue; 537 | #endif 538 | 539 | #if MAP_ENABLE_LV != true 540 | if(X < LVCoords[0] && Y < LVCoords[1] && X > LVCoords[2] && Y > LVCoords[3]) continue; 541 | #endif 542 | 543 | #if MAP_ENABLE_LV_DESERT != true 544 | for(new j = 0; j < sizeof(LVDesertCoords); j ++) if(X < LVDesertCoords[j][0] && Y < LVDesertCoords[j][1] && X > LVDesertCoords[j][2] && Y > LVDesertCoords[j][3]) 545 | { 546 | ignore = true; 547 | break; 548 | } 549 | if(ignore) continue; 550 | #endif 551 | 552 | #if MAP_ENABLE_COUNTY != true 553 | for(new j = 0; j < sizeof(CountyCoords); j ++) if(X < CountyCoords[j][0] && Y < CountyCoords[j][1] && X > CountyCoords[j][2] && Y > CountyCoords[j][3]) 554 | { 555 | ignore = true; 556 | break; 557 | } 558 | if(ignore) continue; 559 | #endif 560 | #endif 561 | 562 | PathNodes[PathNodesNum] = i; 563 | PathNodesNum ++; 564 | } 565 | 566 | if(PathNodesNum < 30) print("DRIVER WARNING: Insufficient amount of parking lots - Use newest GPS.dat or enable more areas!"); 567 | 568 | // ---------------- CONNECT NPCS & stuff 569 | 570 | for(new i = 0; i <= 211; i ++) // Generate a list of vehicles to use 571 | { 572 | if(VehicleZOffsets[i] < -950.0 || i == 20 || i == 38) continue; 573 | 574 | RandomVehicleList[VehicleListNum] = i+400; 575 | 576 | VehicleListNum ++; 577 | } 578 | 579 | new maxnpc = GetServerVarAsInt("maxnpc"), othernpcs = 0; 580 | 581 | for(new i = 0; i < MAX_PLAYERS; i ++) 582 | { 583 | if(IsPlayerNPC(i)) othernpcs ++; 584 | 585 | if(IsPlayerConnected(i) && InTaxiView[i]) SetCameraBehindPlayer(i); 586 | 587 | Taxi[i] = -1; 588 | LastTaxiInteraction[i] = GetTickCount() - TAXI_COOLDOWN*1000; 589 | 590 | NPCDriverID[i] = -1; 591 | } 592 | 593 | Initialized = true; 594 | InitialCalculationStart = GetTickCount(); 595 | 596 | for(new i = 0; i < DRIVER_AMOUNT; i ++) Drivers[i][nNPCID] = -1; 597 | 598 | new npcname[MAX_PLAYER_NAME]; 599 | 600 | for(new i = 0; i < DRIVER_AMOUNT; i ++) 601 | { 602 | if(i >= maxnpc - othernpcs) 603 | { 604 | printf("[DRIVERS] Error: maxnpc exceeded, current limit for this script: %d.", maxnpc-othernpcs); 605 | 606 | break; 607 | } 608 | 609 | new MapNode:startnode = GetRandomStartEndPathNode(), MapNode:endnode, Float:dist; 610 | 611 | do 612 | { 613 | endnode = GetRandomStartEndPathNode(); 614 | GetDistanceBetweenMapNodes(startnode, endnode, dist); 615 | } 616 | while(dist < ROUTE_MIN_DIST || dist > ROUTE_MAX_DIST); 617 | 618 | GetMapNodePos(startnode, X, Y, Z); 619 | 620 | new vmodel, colors[2], skinid; 621 | 622 | if(i < DRIVER_TAXIS) 623 | { 624 | Drivers[i][nType] = DRIVER_TYPE_TAXI; 625 | 626 | vmodel = (random(2) ? 420 : 438); 627 | skinid = TaxiSkins[random(sizeof(TaxiSkins))]; 628 | colors = {-1, -1}; 629 | } 630 | else if(i < DRIVER_COPS + DRIVER_TAXIS) 631 | { 632 | Drivers[i][nType] = DRIVER_TYPE_COP; 633 | 634 | switch(random(5)) 635 | { 636 | case 0: vmodel = 596; 637 | case 1: vmodel = 597; 638 | case 2: vmodel = 598; 639 | case 3: vmodel = 599; 640 | case 4: vmodel = 523; // HPV 641 | } 642 | 643 | skinid = CopSkins[random(sizeof(CopSkins))]; 644 | colors = {-1, -1}; 645 | } 646 | else 647 | { 648 | Drivers[i][nType] = DRIVER_TYPE_RANDOM; 649 | 650 | do 651 | { 652 | vmodel = RandomVehicleList[random(VehicleListNum)]; 653 | } while(vmodel == 596 || vmodel == 597 || vmodel == 598 || vmodel == 599 || vmodel == 420 || vmodel == 438 || vmodel == 523); 654 | 655 | skinid = DriverSkins[random(sizeof(DriverSkins))]; 656 | colors[0] = random(127), colors[1] = random(127); 657 | } 658 | 659 | format(npcname, MAX_PLAYER_NAME, NPC_NAMES, i); 660 | 661 | Drivers[i][nVehicle] = CreateVehicle(vmodel, X, Y, Z + 100000.0, 0.0, colors[0], colors[1], 120000); // Spawn somewhere noone ever will get! This prevents FCNPC's spawn flickering (vehicles showing up at spawn coords between movements for < 1ms (annoying when driving into them just then!)) 662 | 663 | if(!FCNPC_IsValid(Drivers[i][nNPCID])) Drivers[i][nNPCID] = FCNPC_Create(npcname); 664 | 665 | if(!FCNPC_IsValid(Drivers[i][nNPCID])) 666 | { 667 | printf("[DRIVERS] Error: Failed creating NPC (Driver ID %d). Aborted!", i); 668 | 669 | DestroyVehicle(Drivers[i][nVehicle]); 670 | break; 671 | } 672 | 673 | FCNPC_Spawn(Drivers[i][nNPCID], skinid, X, Y, Z + 1.0); 674 | FCNPC_PutInVehicle(Drivers[i][nNPCID], Drivers[i][nVehicle], 0); 675 | FCNPC_SetPosition(Drivers[i][nNPCID], X, Y, Z + VehicleZOffsets[vmodel - 400]); 676 | 677 | NPCDriverID[Drivers[i][nNPCID]] = i; 678 | 679 | //FCNPC_SetInvulnerable(Drivers[i][nNPCID], true); 680 | //FCNPC_SetHealth(Drivers[i][nNPCID], 100.0); 681 | 682 | Drivers[i][nOnDuty] = false; 683 | Drivers[i][nPlayer] = -1; 684 | Drivers[i][nCurNode] = 0; 685 | Drivers[i][nState] = DRIVER_STATE_NONE; 686 | Drivers[i][nSkinID] = skinid; 687 | Drivers[i][nVehicleModel] = vmodel; 688 | Drivers[i][nVehicleLastLean] = 0.0; 689 | Drivers[i][nUsed] = true; 690 | Drivers[i][nLT] = GetTickCount(); 691 | Drivers[i][nLastStart] = startnode; 692 | Drivers[i][nLastDest] = endnode; 693 | #if MAP_ZONES == true 694 | Drivers[i][nGangZone] = -1; 695 | #endif 696 | 697 | switch(vmodel) 698 | { 699 | case 448, 461, 462, 463, 521, 522, 523: Drivers[i][nVehicleIsBike] = true; 700 | default: Drivers[i][nVehicleIsBike] = false; 701 | } 702 | 703 | pubCalculatePath(i, startnode, endnode); 704 | } 705 | 706 | printf("\n\n Total Drivers: %d, Random Drivers: %d, Taxi Drivers: %d, Cops: %d\n maxnpc: %d, Other NPCs: %d\n Number of random nodes: %d/%d\n\n", DRIVER_AMOUNT, (DRIVER_AMOUNT - DRIVER_TAXIS - DRIVER_COPS), DRIVER_TAXIS, DRIVER_COPS, maxnpc, othernpcs, PathNodesNum, MAX_PATH_NODES); 707 | 708 | print(" Initial Calculations started, please wait a moment to finish ..."); 709 | 710 | return 1; 711 | } 712 | 713 | forward Drivers_Exit(fastunload, gmx); 714 | public Drivers_Exit(fastunload, gmx) 715 | { 716 | if(!Initialized && fastunload == 1) return 1; 717 | 718 | for(new i = 0; i < MAX_PLAYERS; i ++) 719 | { 720 | if(IsPlayerConnected(i) && !IsPlayerNPC(i) && InTaxiView[i]) SetCameraBehindPlayer(i); 721 | 722 | Taxi[i] = -1; 723 | NPCDriverID[i] = -1; 724 | } 725 | 726 | if(rescuetimer != -1) KillTimer(rescuetimer); 727 | rescuetimer = -1; 728 | 729 | #if INFO_PRINTS == true 730 | if(updtimer != -1) KillTimer(updtimer); 731 | updtimer = -1; 732 | #endif 733 | 734 | if(fastunload == 0) // This prevents crashes when exiting the FS by destroying NPCs in seperate calls (might be fixed in new version). 735 | { 736 | print("[DRIVERS] Warning: Unloading Driver FS ..."); 737 | SetTimerEx("Drivers_DestroyID", 1000, 0, "i", 0); 738 | } 739 | else 740 | { 741 | for(new i = 0; i < DRIVER_AMOUNT; i ++) 742 | { 743 | if(!Drivers[i][nUsed]) continue; 744 | 745 | Drivers[i][nUsed] = false; 746 | 747 | if(GetVehicleModel(Drivers[i][nVehicle]) >= 400 && !gmx) DestroyVehicle(Drivers[i][nVehicle]); 748 | 749 | //if(FCNPC_IsValid(Drivers[i][nNPCID])) FCNPC_Destroy(Drivers[i][nNPCID]); 750 | 751 | Drivers[i][nNPCID] = -1; 752 | Drivers[i][nVehicle] = -1; 753 | } 754 | } 755 | 756 | Initialized = false; 757 | 758 | return 1; 759 | } 760 | 761 | forward Drivers_DestroyID(count); 762 | public Drivers_DestroyID(count) 763 | { 764 | if(count < 0 || count >= DRIVER_AMOUNT) 765 | { 766 | if(count == DRIVER_AMOUNT) 767 | { 768 | Initialized = false; 769 | 770 | if(IsPlayerConnected(ExitPlayerID)) 771 | { 772 | SendClientMessage(ExitPlayerID, -1, "Driver FS unloaded."); 773 | print("[DRIVERS] Warning: Driver FS unloaded."); 774 | } 775 | else print("[DRIVERS] Warning: Driver FS unloaded."); 776 | 777 | //SendRconCommand("unloadfs "SCRIPT_NAME); 778 | return 2; 779 | } 780 | return 0; 781 | } 782 | 783 | if(NumRouteCalcs > 0) 784 | { 785 | if(IsPlayerConnected(ExitPlayerID)) 786 | { 787 | new str[50]; 788 | format(str, sizeof(str), "Waiting for %d Path Calculations to proceed.", NumRouteCalcs); 789 | SendClientMessage(ExitPlayerID, -1, str); 790 | printf("[DRIVERS] Warning: Waiting for %d Path Calculations to proceed.", NumRouteCalcs); 791 | } 792 | else printf("[DRIVERS] Warning: Waiting for %d Path Calculations to proceed.", NumRouteCalcs); 793 | 794 | SetTimerEx("Drivers_DestroyID", 3000, 0, "i", count); 795 | 796 | return 1; 797 | } 798 | 799 | if(Drivers[count][nUsed]) 800 | { 801 | Drivers[count][nUsed] = false; 802 | 803 | if(FCNPC_IsValid(Drivers[count][nNPCID])) 804 | { 805 | FCNPC_RemoveFromVehicle(Drivers[count][nNPCID]); 806 | FCNPC_Destroy(Drivers[count][nNPCID]); 807 | NPCDriverID[Drivers[count][nNPCID]] = -1; 808 | } 809 | 810 | if(GetVehicleModel(Drivers[count][nVehicle]) >= 400) DestroyVehicle(Drivers[count][nVehicle]); 811 | 812 | Drivers[count][nNPCID] = -1; 813 | Drivers[count][nVehicle] = -1; 814 | } 815 | 816 | SetTimerEx("Drivers_DestroyID", 7, 0, "i", ++count); 817 | return 1; 818 | } 819 | 820 | // ----------------------------------------------------------------------------- 821 | 822 | public FCNPC_OnCreate(npcid) 823 | { 824 | return 1; 825 | } 826 | 827 | // ----------------------------------------------------------------------------- 828 | 829 | public FCNPC_OnSpawn(npcid) 830 | { 831 | return 1; 832 | } 833 | 834 | // ----------------------------------------------------------------------------- 835 | 836 | public FCNPC_OnStreamIn(npcid, forplayerid) 837 | { 838 | new driverid = GetDriverID(npcid); 839 | 840 | if(PlayerEnterDriver[forplayerid] != -1 && PlayerEnterDriver[forplayerid] == driverid) 841 | { 842 | PutPlayerInVehicle(forplayerid, Drivers[driverid][nVehicle], 1); 843 | PlayerEnterDriver[forplayerid] = -1; 844 | } 845 | 846 | return 1; 847 | } 848 | 849 | // ----------------------------------------------------------------------------- 850 | 851 | public FCNPC_OnDeath(npcid, killerid, reason) 852 | { 853 | if(!Initialized) return 1; 854 | 855 | if(!FCNPC_IsSpawned(npcid)) return 1; 856 | 857 | new driverid = GetDriverID(npcid); 858 | 859 | if(driverid == -1) return 1; 860 | 861 | if(IsPlayerConnected(Drivers[driverid][nPlayer])) 862 | { 863 | SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Service]: {FF0000}Sorry, our driver could not make it to your location. Please call again if you still need a pick up."); 864 | Taxi[Drivers[driverid][nPlayer]] = -1; 865 | } 866 | 867 | Drivers[driverid][nOnDuty] = false; 868 | Drivers[driverid][nPlayer] = -1; 869 | 870 | Drivers[driverid][nState] = DRIVER_STATE_NONE; 871 | 872 | Drivers[driverid][nDeathTick] = GetTickCount(); 873 | Drivers[driverid][nResetVeh] = false; 874 | 875 | #if SEND_DEATH_MESSAGE == true 876 | if(IsPlayerConnected(killerid)) 877 | { 878 | new str[100], name[MAX_PLAYER_NAME+1], weap[25], killdesc[10]; 879 | GetPlayerName(npcid, str, sizeof(str)); 880 | GetPlayerName(killerid, name, sizeof(name)); 881 | GetWeaponName(reason, weap, sizeof(weap)); 882 | 883 | switch(random(16)) 884 | { 885 | case 0: strcat(killdesc, "humiliated"); 886 | case 1: strcat(killdesc, "killed"); 887 | case 2: strcat(killdesc, "torn apart"); 888 | case 3: strcat(killdesc, "erased"); 889 | case 4: strcat(killdesc, "vaporized"); 890 | case 5: strcat(killdesc, "filled with lead"); 891 | case 6: strcat(killdesc, "wiped out"); 892 | case 7: strcat(killdesc, "slaughtered"); 893 | case 8: strcat(killdesc, "murdered"); 894 | case 9: strcat(killdesc, "wasted"); 895 | case 10: strcat(killdesc, "annihilated"); 896 | case 11: strcat(killdesc, "dumped"); 897 | case 12: strcat(killdesc, "lynched"); 898 | case 13: strcat(killdesc, "obliterated"); 899 | case 14: strcat(killdesc, "liquidated"); 900 | case 15: strcat(killdesc, "put to death"); 901 | } 902 | 903 | if(!strlen(weap)) strcat(weap, "Blown up"); 904 | 905 | format(str, sizeof(str), "%s was %s by %s [%s]", str, killdesc, name, weap); 906 | SendClientMessageToAll(0xCC6633FF, str); 907 | } 908 | #endif 909 | 910 | return 1; 911 | } 912 | 913 | // ----------------------------------------------------------------------------- 914 | 915 | public OnPlayerConnect(playerid) 916 | { 917 | if(!Initialized) return 1; 918 | 919 | if(!IsPlayerNPC(playerid)) 920 | { 921 | LastTaxiInteraction[playerid] = GetTickCount() - TAXI_COOLDOWN*1000; 922 | PlayerEnterDriver[playerid] = -1; 923 | } 924 | 925 | return 1; 926 | } 927 | 928 | public OnPlayerDisconnect(playerid) 929 | { 930 | if(!Initialized) return 1; 931 | 932 | return 1; 933 | } 934 | 935 | // ----------------------------------------------------------------------------- 936 | 937 | COMMAND:ds(playerid, params[]) 938 | { 939 | if(!Initialized || !IsPlayerAdmin(playerid)) return 0; 940 | 941 | new id = -1; 942 | 943 | if(!isnull(params) && strlen(params) < 8) 944 | { 945 | if(params[0] == 'r') id = random(DRIVER_AMOUNT); 946 | else 947 | { 948 | id = strval(params); 949 | 950 | if(id < 0 || id >= DRIVER_AMOUNT) id = -1; 951 | } 952 | } 953 | 954 | if(id == -1) return SendClientMessage(playerid, -1, "Invalid Driver ID"); 955 | 956 | new Float:x, Float:y, Float:z; 957 | FCNPC_GetPosition(Drivers[id][nNPCID], x, y, z); 958 | 959 | if(FCNPC_IsStreamedIn(Drivers[id][nNPCID], playerid)) PutPlayerInVehicle(playerid, Drivers[id][nVehicle], 1); 960 | else 961 | { 962 | SetPlayerPos(playerid, x, y, z); 963 | PlayerEnterDriver[playerid] = id; 964 | } 965 | 966 | return 1; 967 | } 968 | 969 | COMMAND:dfs_cmds(playerid, params[]) 970 | { 971 | if(!Initialized) return 0; 972 | 973 | SendClientMessage(playerid, -1, " "); 974 | SendClientMessage(playerid, -1, "{99FF00}Driver FS by NaS & AIped (c) 2015-2017"); 975 | 976 | if(IsPlayerAdmin(playerid)) 977 | { 978 | SendClientMessage(playerid, -1, "{FF9900} Admin Commands - []: required, (): optional"); 979 | SendClientMessage(playerid, -1, " /ds [ID] (seat) - Take a seat in the specified Driver's Vehicle."); 980 | SendClientMessage(playerid, -1, " /dfs_info - Prints Driver Updates."); 981 | SendClientMessage(playerid, -1, " /dfs_exit - Slowly kicks all NPCs and stops the Script. Unloading the actual FS crashes! :("); 982 | } 983 | 984 | SendClientMessage(playerid, -1, "{FF9900} Player Commands"); 985 | SendClientMessage(playerid, -1, " /Taxi - Calls a Taxi to your location."); 986 | 987 | return 1; 988 | } 989 | 990 | COMMAND:dfs_help(playerid, params[]) 991 | { 992 | return cmd_dfs_cmds(playerid, params); 993 | } 994 | 995 | COMMAND:dfs_exit(playerid, params[]) 996 | { 997 | if(!Initialized || !IsPlayerAdmin(playerid)) return 0; 998 | 999 | if(NumRouteCalcs == 0) SendClientMessage(playerid, -1, "Unloading Driver FS ..."); 1000 | else 1001 | { 1002 | new text[128]; 1003 | format(text, sizeof(text), "There are %d Path Calculations left. The Script will unload once they are completed.", NumRouteCalcs); 1004 | SendClientMessage(playerid, -1, text); 1005 | } 1006 | 1007 | ExitPlayerID = playerid; 1008 | 1009 | Drivers_Exit(0, 0); 1010 | 1011 | return 1; 1012 | } 1013 | 1014 | COMMAND:dfs_info(playerid, params[]) 1015 | { 1016 | if(!Initialized || !IsPlayerAdmin(playerid)) return 0; 1017 | 1018 | PrintDriverUpdate(); 1019 | 1020 | return 1; 1021 | } 1022 | 1023 | COMMAND:taxi(playerid, params[]) 1024 | { 1025 | if(!Initialized) return 0; 1026 | 1027 | if(Taxi[playerid] != -1) return SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Sorry Sir, it seems like you have already ordered a taxi."), 1; 1028 | 1029 | if(GetTickCount() - LastTaxiInteraction[playerid] < TAXI_COOLDOWN*1000) return SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Sorry Sir, we don't have any available cabs right now."), 1; 1030 | 1031 | new taxi = -1, Float:tdist = MAX_TAXI_DIST, Float:node_dist, Float:X, Float:Y, Float:Z, MapNode:destnode; 1032 | GetPlayerPos(playerid, X, Y, Z); 1033 | 1034 | GetClosestMapNodeToPoint(X, Y, Z, destnode); 1035 | 1036 | if(!IsValidMapNode(destnode)) 1037 | { 1038 | SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Sorry, we don't have your location on our GPS."); 1039 | 1040 | return 1; 1041 | } 1042 | 1043 | GetMapNodeDistanceFromPoint(destnode, X, Y, Z, node_dist); 1044 | 1045 | if(node_dist > TAXI_RANGE) 1046 | { 1047 | SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Unfortunately our driver is unable to reach your current location."); 1048 | SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Please proceed to the closest road."); 1049 | 1050 | return 1; 1051 | } 1052 | 1053 | for(new i = 0; i < DRIVER_TAXIS; i ++) 1054 | { 1055 | if(!Drivers[i][nUsed] || Drivers[i][nOnDuty] || !IsPlayerNPC(Drivers[i][nNPCID])) continue; 1056 | 1057 | if(Drivers[i][nState] != DRIVER_STATE_DRIVE) continue; 1058 | 1059 | new Float:dist = GetPlayerDistanceFromPoint(Drivers[i][nNPCID], X, Y, Z); 1060 | 1061 | if(dist < tdist) 1062 | { 1063 | taxi = i; 1064 | tdist = dist; 1065 | } 1066 | } 1067 | 1068 | if(taxi == -1) return SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Sorry Sir, we don't have an available cab near you."), 1; 1069 | 1070 | new MapNode:startnode, npcid = Drivers[taxi][nNPCID]; 1071 | 1072 | FCNPC_GetPosition(npcid, X, Y, Z); 1073 | GetClosestMapNodeToPoint(X, Y, Z, startnode); 1074 | 1075 | if(!IsValidMapNode(startnode)) 1076 | { 1077 | SendClientMessage(playerid, -1, "[Taxi Service]: {990000}Sorry, we don't have an available cab near your location."); 1078 | 1079 | return 1; 1080 | } 1081 | 1082 | Drivers[taxi][nState] = DRIVER_STATE_NONE; 1083 | if(FCNPC_IsMoving(npcid)) FCNPC_Stop(npcid); 1084 | 1085 | Drivers[taxi][nOnDuty] = true; 1086 | Drivers[taxi][nPlayer] = playerid; 1087 | 1088 | if(tdist < TAXI_RANGE) 1089 | { 1090 | TaxiState[playerid] = TAXI_STATE_WAIT1; 1091 | SetVehicleParamsForPlayer(Drivers[taxi][nVehicle], playerid, 1, 0); 1092 | Drivers[taxi][nLastStart] = Drivers[taxi][nLastDest]; 1093 | Drivers[taxi][nLastDest] = startnode; 1094 | 1095 | SendClientMessage(playerid, -1, "[Taxi Service]: {009900}We got a driver right around the corner!"); 1096 | } 1097 | else 1098 | { 1099 | pubCalculatePath(taxi, startnode, destnode); 1100 | TaxiState[playerid] = TAXI_STATE_DRIVE1; 1101 | } 1102 | 1103 | Taxi[playerid] = taxi; 1104 | 1105 | return 1; 1106 | } 1107 | 1108 | public OnPlayerCommandText(playerid, cmdtext[]) 1109 | { 1110 | if(!Initialized) return 0; 1111 | 1112 | return 0; 1113 | } 1114 | 1115 | public OnRconCommand(cmd[]) 1116 | { 1117 | if(strcmp(cmd, "dfs_exit", true) == 0) 1118 | { 1119 | if(!Initialized) return print("[DRIVERS] Warning: Driver FS is not initialized (GMX?)."), 1; 1120 | else 1121 | { 1122 | ExitPlayerID = -1; 1123 | Drivers_Exit(0, 0); 1124 | } 1125 | 1126 | return 1; 1127 | } 1128 | return 0; 1129 | } 1130 | 1131 | // ----------------------------------------------------------------------------- 1132 | 1133 | public OnPlayerStateChange(playerid, newstate, oldstate) 1134 | { 1135 | if(IsPlayerNPC(playerid)) return 1; 1136 | 1137 | if(!Initialized) return 1; 1138 | 1139 | if(newstate == PLAYER_STATE_PASSENGER) 1140 | { 1141 | if(Taxi[playerid] >= 0 && Taxi[playerid] < DRIVER_TAXIS && TaxiState[playerid] == TAXI_STATE_WAIT1) 1142 | { 1143 | if(IsPlayerNPC(Drivers[Taxi[playerid]][nNPCID])) 1144 | { 1145 | if(GetPlayerVehicleID(playerid) == Drivers[Taxi[playerid]][nVehicle]) 1146 | { 1147 | ShowPlayerDialog(playerid, DID_TAXI, DIALOG_STYLE_LIST, "Choose a destination", gDestinationDialogSTR, "Go", "Cancel"); 1148 | SetVehicleParamsEx(Drivers[Taxi[playerid]][nVehicle], 1, 0, 0, 0, 0, 0, 0); 1149 | 1150 | new Float:cX, Float:cY, Float:cZ, Float:A, Float:tX, Float:tY; 1151 | FCNPC_GetPosition(Drivers[Taxi[playerid]][nNPCID], cX, cY, cZ); 1152 | A = FCNPC_GetAngle(Drivers[Taxi[playerid]][nNPCID]); 1153 | 1154 | tX = cX; 1155 | tY = cY; 1156 | if(GetVehicleModel(Drivers[Taxi[playerid]][nVehicle]) == 420) 1157 | { 1158 | GetXYInFrontOfPoint(cX, cY, A+180.0, cX, cY, 1.1); 1159 | GetXYInFrontOfPoint(cX, cY, A+90.0, cX, cY, 0.15); 1160 | cZ += 0.5; 1161 | 1162 | GetXYInFrontOfPoint(tX, tY, A, tX, tY, 2.5); 1163 | } 1164 | else 1165 | { 1166 | GetXYInFrontOfPoint(cX, cY, A+180.0, cX, cY, 0.5); 1167 | GetXYInFrontOfPoint(cX, cY, A+90.0, cX, cY, 0.15); 1168 | cZ += 0.3; 1169 | 1170 | GetXYInFrontOfPoint(tX, tY, A, tX, tY, 2.5); 1171 | } 1172 | SetPlayerCameraPos(playerid, cX, cY, cZ); 1173 | SetPlayerCameraLookAt(playerid, tX, tY, cZ-0.5); 1174 | 1175 | InTaxiView[playerid] = true; 1176 | 1177 | return 1; 1178 | } 1179 | } 1180 | } 1181 | } 1182 | 1183 | if(oldstate == PLAYER_STATE_PASSENGER) 1184 | { 1185 | if(Taxi[playerid] >= 0 && Taxi[playerid] < DRIVER_TAXIS && TaxiState[playerid] == TAXI_STATE_DRIVE2) 1186 | { 1187 | SetTimerEx("ResetTaxi", 5000, 0, "dd", Taxi[playerid], 3000); 1188 | } 1189 | } 1190 | 1191 | return 1; 1192 | } 1193 | 1194 | // ----------------------------------------------------------------------------- 1195 | 1196 | public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]) 1197 | { 1198 | if(!Initialized) return 0; 1199 | 1200 | if(dialogid == DID_TAXI) 1201 | { 1202 | if(Taxi[playerid] == -1 || TaxiState[playerid] != TAXI_STATE_WAIT1) return 1; 1203 | 1204 | if(response) 1205 | { 1206 | if(gDestinationList[listitem][destName] == '-') 1207 | { 1208 | ShowPlayerDialog(playerid, DID_TAXI, DIALOG_STYLE_LIST, "Choose a destination", gDestinationDialogSTR, "Go", "Cancel"); 1209 | return SendClientMessage(playerid, -1, "[Taxi Driver]: {990000}Excuse me, can you re-phrase that?"), 1; 1210 | } 1211 | 1212 | new MapNode:destnode; 1213 | 1214 | GetClosestMapNodeToPoint(gDestinationList[listitem][destX], gDestinationList[listitem][destY], gDestinationList[listitem][destZ], destnode); 1215 | 1216 | if(!IsValidMapNode(destnode)) 1217 | { 1218 | ShowPlayerDialog(playerid, DID_TAXI, DIALOG_STYLE_LIST, "Choose a destination", gDestinationDialogSTR, "Go", "Cancel"); 1219 | SendClientMessage(playerid, -1, "[Taxi Driver]: {990000}Weird. I can't find that spot on my map!"); 1220 | 1221 | return 1; 1222 | } 1223 | 1224 | Drivers[Taxi[playerid]][nState] = DRIVER_STATE_NONE; 1225 | if(FCNPC_IsMoving(Drivers[Taxi[playerid]][nNPCID])) FCNPC_Stop(Drivers[Taxi[playerid]][nNPCID]); 1226 | 1227 | SetTimerEx("pubCalculatePath", 1000 + random(1000), 0, "ddd", Taxi[playerid], _:Drivers[Taxi[playerid]][nLastDest], _:destnode); 1228 | 1229 | TaxiState[playerid] = TAXI_STATE_DRIVE2; 1230 | 1231 | SetVehicleParamsEx(Drivers[Taxi[playerid]][nVehicle], 1, 0, 0, 0, 0, 0, 0); 1232 | 1233 | SetCameraBehindPlayer(playerid); 1234 | InTaxiView[playerid] = false; 1235 | 1236 | LastTaxiInteraction[playerid] = GetTickCount(); 1237 | } 1238 | else 1239 | { 1240 | SetCameraBehindPlayer(playerid); 1241 | InTaxiView[playerid] = false; 1242 | 1243 | ResetTaxi(Taxi[playerid], 5000); 1244 | } 1245 | return 1; 1246 | } 1247 | return 0; 1248 | } 1249 | 1250 | // ----------------------------------------------------------------------------- 1251 | 1252 | forward pubCalculatePath(driverid, MapNode:startnode, MapNode:endnode); 1253 | public pubCalculatePath(driverid, MapNode:startnode, MapNode:endnode) 1254 | { 1255 | if(!Initialized) return 1; 1256 | 1257 | if(driverid < 0 || driverid >= DRIVER_AMOUNT) return 1; 1258 | 1259 | if(!Drivers[driverid][nUsed]) return 1; 1260 | 1261 | if(FCNPC_IsDead(Drivers[driverid][nNPCID]) || !FCNPC_IsSpawned(Drivers[driverid][nNPCID])) return 1; 1262 | 1263 | if(Drivers[driverid][nState] != DRIVER_STATE_NONE) return 1; 1264 | 1265 | Drivers[driverid][nLT] = GetTickCount(); 1266 | 1267 | //CalculatePath(startnode, endnode, DRIVERS_ROUTE_ID + driverid, false, _, true); 1268 | 1269 | FindPathThreaded(startnode, endnode, "OnPathCalculated", "i", DRIVERS_ROUTE_ID + driverid); 1270 | 1271 | NumRouteCalcs ++; 1272 | 1273 | Drivers[driverid][nLastStart] = startnode; 1274 | Drivers[driverid][nLastDest] = endnode; 1275 | 1276 | return 1; 1277 | } 1278 | 1279 | forward pub_RemovePlayerFromVehicle(playerid); 1280 | public pub_RemovePlayerFromVehicle(playerid) 1281 | { 1282 | RemovePlayerFromVehicle(playerid); 1283 | return 1; 1284 | } 1285 | 1286 | // ----------------------------------------------------------------------------- Resets a Taxi after aborted ride, or reaching destination 1287 | 1288 | forward ResetTaxi(driverid, calcdelay); 1289 | public ResetTaxi(driverid, calcdelay) 1290 | { 1291 | if(!Initialized) return 1; 1292 | 1293 | if(driverid < 0 || driverid >= DRIVER_TAXIS) return 1; 1294 | 1295 | if(!Drivers[driverid][nUsed] || !Drivers[driverid][nOnDuty] || Drivers[driverid][nType] != DRIVER_TYPE_TAXI) return 1; 1296 | 1297 | if(!FCNPC_IsSpawned(Drivers[driverid][nNPCID]) || FCNPC_IsDead(Drivers[driverid][nNPCID])) return 1; 1298 | 1299 | new playerid = Drivers[driverid][nPlayer]; 1300 | if(playerid >= 0 && playerid < MAX_PLAYERS) 1301 | { 1302 | TaxiState[playerid] = TAXI_STATE_NONE; 1303 | Taxi[playerid] = -1; 1304 | 1305 | LastTaxiInteraction[playerid] = GetTickCount(); 1306 | 1307 | if(InTaxiView[playerid]) 1308 | { 1309 | SetCameraBehindPlayer(playerid); 1310 | HidePlayerDialog(playerid); 1311 | InTaxiView[playerid] = false; 1312 | } 1313 | 1314 | for(new i = 0; i < MAX_PLAYERS; i ++) if(!IsPlayerNPC(i) && GetPlayerVehicleID(i) == Drivers[driverid][nVehicle]) RemovePlayerFromVehicle(i); 1315 | } 1316 | 1317 | if(GetPlayerVehicleID(playerid) == Drivers[driverid][nVehicle]) SetTimerEx("pub_RemovePlayerFromVehicle", 1000, 0, "d", playerid); 1318 | SetVehicleParamsEx(Drivers[driverid][nVehicle], 1, 0, 0, 0, 0, 0, 0); 1319 | 1320 | if(FCNPC_IsMoving(Drivers[driverid][nNPCID])) FCNPC_Stop(Drivers[driverid][nNPCID]); 1321 | 1322 | Drivers[driverid][nState] = DRIVER_STATE_NONE; 1323 | Drivers[driverid][nOnDuty] = false; 1324 | Drivers[driverid][nLT] = GetTickCount(); 1325 | 1326 | new Float:X, Float:Y, Float:Z; 1327 | FCNPC_GetPosition(Drivers[driverid][nNPCID], X, Y, Z); 1328 | 1329 | new MapNode:startnode, MapNode:endnode, Float:dist; 1330 | 1331 | GetClosestMapNodeToPoint(X, Y, Z, startnode); 1332 | 1333 | do 1334 | { 1335 | endnode = GetRandomStartEndPathNode(); 1336 | GetDistanceBetweenMapNodes(startnode, endnode, dist); 1337 | } 1338 | while(dist < ROUTE_MIN_DIST || dist > ROUTE_MAX_DIST); 1339 | 1340 | if(calcdelay > 0) SetTimerEx("pubCalculatePath", calcdelay, 0, "ddd", driverid, _:startnode, _:endnode); 1341 | else pubCalculatePath(driverid, startnode, endnode); 1342 | 1343 | return 1; 1344 | } 1345 | 1346 | // ----------------------------------------------------------------------------- This resets NPCs that are dead for a while, or stuck for some reason (eg when a bad GPS.dat was used) 1347 | 1348 | forward RescueTimer(); 1349 | public RescueTimer() 1350 | { 1351 | if(!Initialized) return 1; 1352 | 1353 | if(avgtickidx >= sizeof(avgticks)) avgtickidx = 0; 1354 | avgticks[avgtickidx] = GetServerTickRate(); 1355 | avgtickidx ++; 1356 | 1357 | new tick = GetTickCount(); 1358 | 1359 | for(new i = 0; i < 20; i ++) 1360 | { 1361 | if(Drivers[rescueid][nUsed]) 1362 | { 1363 | SetPlayerColor(Drivers[rescueid][nNPCID], 0); 1364 | 1365 | if(!FCNPC_IsDead(Drivers[rescueid][nNPCID])) 1366 | { 1367 | if(tick - Drivers[rescueid][nLT] > TAXI_TIMEOUT*1000) 1368 | { 1369 | if(FCNPC_IsMoving(Drivers[rescueid][nNPCID])) Drivers[rescueid][nLT] = tick; 1370 | else if(Drivers[rescueid][nType] == DRIVER_TYPE_TAXI && Drivers[rescueid][nOnDuty]) ResetTaxi(rescueid, 10000); 1371 | 1372 | #if DEBUG_BUBBLE == true 1373 | new str[40]; 1374 | format(str, sizeof(str), "{888888}[%d]\n{DD0000}Timed out", rescueid); 1375 | SetPlayerChatBubble(Drivers[rescueid][nNPCID], str, -1, 10.0, TAXI_TIMEOUT*1000); 1376 | #endif 1377 | } 1378 | } 1379 | else if(Drivers[rescueid][nState] == DRIVER_STATE_NONE) 1380 | { 1381 | if(tick - Drivers[rescueid][nDeathTick] > 10000 && !Drivers[rescueid][nResetVeh]) // Respawns Vehicle 1382 | { 1383 | Drivers[rescueid][nResetVeh] = true; 1384 | SetVehicleToRespawn(Drivers[rescueid][nVehicle]); 1385 | } 1386 | else if(tick - Drivers[rescueid][nDeathTick] > 20000) // Resets the NPC and spawns it 1387 | { 1388 | Drivers[rescueid][nCurNode] = 0; 1389 | Drivers[rescueid][nLT] = tick; 1390 | Drivers[rescueid][nDeathTick] = tick + 10000; 1391 | 1392 | new MapNode:startnode = GetRandomStartEndPathNode(), MapNode:endnode, Float:dist, tries = 45; 1393 | 1394 | do 1395 | { 1396 | endnode = GetRandomStartEndPathNode(); 1397 | GetDistanceBetweenMapNodes(startnode, endnode, dist); 1398 | 1399 | tries --; 1400 | } 1401 | while(dist < ROUTE_MIN_DIST || (dist > ROUTE_MAX_DIST && tries > 0)); // tries is used to prevent a long loop (which can happen, because of random - it's veeery unlikely though) 1402 | 1403 | new Float:X, Float:Y, Float:Z; 1404 | GetMapNodePos(startnode, X, Y, Z); 1405 | 1406 | FCNPC_Respawn(Drivers[rescueid][nNPCID]); 1407 | FCNPC_PutInVehicle(Drivers[rescueid][nNPCID], Drivers[rescueid][nVehicle], 0); 1408 | FCNPC_SetPosition(Drivers[rescueid][nNPCID], X, Y, Z + VehicleZOffsets[Drivers[rescueid][nVehicleModel] - 400]); 1409 | 1410 | SetTimerEx("pubCalculatePath", 1000, 0, "ddd", rescueid, _:startnode, _:endnode); 1411 | } 1412 | } 1413 | } 1414 | 1415 | rescueid ++; 1416 | 1417 | if(rescueid >= DRIVER_AMOUNT) rescueid = 0; 1418 | } 1419 | return 1; 1420 | } 1421 | 1422 | // ----------------------------------------------------------------------------- 1423 | 1424 | #if INFO_PRINTS == true 1425 | forward PrintDriverUpdate(); 1426 | public PrintDriverUpdate() 1427 | #else 1428 | PrintDriverUpdate() 1429 | #endif 1430 | { 1431 | new curtick = GetTickCount(); 1432 | 1433 | new maxnpc = GetServerVarAsInt("maxnpc"), othernpcs = -DRIVER_AMOUNT, idlenpcs = 0; 1434 | for(new i = 0; i < MAX_PLAYERS; i ++) 1435 | { 1436 | if(IsPlayerNPC(i)) othernpcs ++; 1437 | 1438 | if(i >= DRIVER_AMOUNT) continue; 1439 | 1440 | if(!Drivers[i][nUsed] || !IsPlayerNPC(Drivers[i][nNPCID])) continue; 1441 | 1442 | if(curtick - Drivers[i][nLT] > 60000) 1443 | { 1444 | printf("Driver %d idle!", i); 1445 | idlenpcs ++; 1446 | } 1447 | } 1448 | 1449 | new Float:avgcalctime = 1.0*avgcalctimes[0]; 1450 | for(new i = 1; i < sizeof(avgcalctimes); i ++) avgcalctime += 1.0*avgcalctimes[i]; 1451 | avgcalctime = avgcalctime / (1.0*sizeof(avgcalctimes)); 1452 | 1453 | new Float:avgtick = 1.0*avgticks[0]; 1454 | for(new i = 1; i < sizeof(avgticks); i ++) avgtick += 1.0*avgticks[i]; 1455 | avgtick = avgtick / (1.0*sizeof(avgticks)); 1456 | 1457 | new rtms = curtick - InitialCalculationStart, rts, rtm, rth; 1458 | 1459 | rts = (rtms / 1000) % 60; 1460 | rtm = (rtms / 60000) % 60; 1461 | rth = rtms / (1000*60*60); 1462 | 1463 | printf("\n Total Drivers: %d, Random Drivers: %d, Taxi Drivers: %d, Cops: %d\n maxnpc: %d, Other NPCs: %d, Idle NPCs: %d\n MaxPathLen: %d/%d, Uptime: %02d:%02d:%02d\n - - - Avg. calc. time: %.02fms, Avg. Server Tick: %.02f\n", DRIVER_AMOUNT, (DRIVER_AMOUNT - DRIVER_TAXIS - DRIVER_COPS), DRIVER_TAXIS, DRIVER_COPS, maxnpc, othernpcs, idlenpcs, MaxPathLen, MAX_PATH_LEN, rth, rtm, rts, avgcalctime, avgtick); 1464 | return 1; 1465 | } 1466 | 1467 | // ----------------------------------------------------------------------------- Called when a route was calculated 1468 | 1469 | forward OnPathCalculated(Path:pathid, routeid); 1470 | public OnPathCalculated(Path:pathid, routeid) 1471 | { 1472 | NumRouteCalcs --; 1473 | if(!Initialized) return 1; 1474 | 1475 | if(InitialCalculations < DRIVER_AMOUNT) InitialCalculations ++; 1476 | 1477 | if(!IsValidPath(pathid)) return 1; 1478 | 1479 | new t = GetTickCount(); 1480 | 1481 | if(routeid >= DRIVERS_ROUTE_ID && routeid < DRIVERS_ROUTE_ID + DRIVER_AMOUNT) // the routeid given comes from this script 1482 | { 1483 | new driverid = routeid - DRIVERS_ROUTE_ID; 1484 | 1485 | if(!Drivers[driverid][nUsed]) return 1; 1486 | 1487 | if(!IsPlayerNPC(Drivers[driverid][nNPCID]) || FCNPC_IsDead(Drivers[driverid][nNPCID]) || !FCNPC_IsSpawned(Drivers[driverid][nNPCID])) return 1; 1488 | 1489 | if(Drivers[driverid][nState] != DRIVER_STATE_NONE) return 1; 1490 | 1491 | new amount_of_nodes; 1492 | GetPathSize(pathid, amount_of_nodes); 1493 | 1494 | if(amount_of_nodes < 3) 1495 | { 1496 | #if DEBUG_PRINTS == true 1497 | print("[DRIVERS] Error: Failed calculating path for Driver ID %d", driverid); 1498 | #endif 1499 | 1500 | Drivers[driverid][nCalcFails] ++; 1501 | return 1; 1502 | } 1503 | 1504 | new Float:NodePosX[MAX_PATH_LEN], Float:NodePosY[MAX_PATH_LEN], Float:NodePosZ[MAX_PATH_LEN], 1505 | MapNode:nodeid, 1506 | Float:distance; 1507 | 1508 | GetPathLength(pathid, distance); 1509 | 1510 | for(new i = 0; i < amount_of_nodes && i < MAX_PATH_LEN; i ++) 1511 | { 1512 | GetPathNode(pathid, i, nodeid); 1513 | GetMapNodePos(nodeid, NodePosX[i], NodePosY[i], NodePosZ[i]); 1514 | } 1515 | 1516 | Drivers[driverid][nCalcFails] = 0; 1517 | Drivers[driverid][nLT] = t; 1518 | 1519 | new arrayid = 0, Float:newpath[MAX_PATH_LEN][2]; 1520 | 1521 | newpath[0][0] = NodePosX[0]; 1522 | newpath[0][1] = NodePosY[0]; 1523 | DriverPath[driverid][0][2] = NodePosZ[0]; 1524 | 1525 | DriverPathLen[driverid] = 1; 1526 | 1527 | /* 1528 | Loop explanation (below) 1529 | 1530 | i is the index to write (for newpath array) 1531 | arrayid is the index to read (for node_id_array) 1532 | 1533 | The target node will stay as long as the distance is too high. 1534 | The target node will skip if the distance is too low. 1535 | 1536 | */ 1537 | 1538 | for(new i = 1; arrayid < amount_of_nodes && i < MAX_PATH_LEN; i ++) 1539 | { 1540 | if(arrayid == amount_of_nodes-1) 1541 | { 1542 | newpath[i][0] = NodePosX[amount_of_nodes - 1]; 1543 | newpath[i][1] = NodePosY[amount_of_nodes - 1]; 1544 | DriverPath[driverid][i][2] = NodePosZ[amount_of_nodes - 1]; 1545 | 1546 | DriverPathLen[driverid] ++; 1547 | break; 1548 | } 1549 | 1550 | new Float:dis = floatsqroot(floatpower(NodePosX[arrayid] - newpath[i-1][0], 2) + floatpower(NodePosY[arrayid] - newpath[i-1][1], 2) + floatpower(NodePosZ[arrayid] - DriverPath[driverid][i-1][2], 2)); 1551 | 1552 | new Float:ndis = MAX_NODE_DIST; 1553 | 1554 | if(i >= 3 && arrayid < amount_of_nodes - 2) 1555 | { 1556 | new Float:a1 = floatangledistdir(-atan2(NodePosX[arrayid]-newpath[i-1][0], NodePosY[arrayid]-newpath[i-1][1]), -atan2(NodePosX[arrayid+1]-NodePosX[arrayid], NodePosY[arrayid+1]-NodePosY[arrayid])); 1557 | 1558 | if(a1 < 0.0) a1 = -a1; 1559 | 1560 | #define SP_ANGLE 25.0 1561 | if(a1 > SP_ANGLE) a1 = SP_ANGLE; 1562 | 1563 | ndis -= (a1/SP_ANGLE) * MAX_NODE_DIST; 1564 | #undef SP_ANGLE 1565 | 1566 | new Float: Zrel = (NodePosZ[arrayid] - DriverPath[driverid][i-1][2]) / dis; 1567 | if(Zrel < 0.0) Zrel *= -3.0; 1568 | else Zrel *= 3.0; 1569 | if(Zrel > 0.9) Zrel = 0.9; 1570 | 1571 | ndis -= (Zrel * MAX_NODE_DIST * 0.7); 1572 | } 1573 | else ndis = MAX_NODE_DIST/2.0; 1574 | 1575 | if(ndis < MIN_NODE_DIST) ndis = MIN_NODE_DIST; 1576 | if(ndis > MAX_NODE_DIST) ndis = MAX_NODE_DIST; 1577 | 1578 | if(dis > ndis || arrayid >= amount_of_nodes-2) 1579 | { 1580 | new Float:fact = (dis/ndis); 1581 | 1582 | newpath[i][0] = newpath[i-1][0] + ((NodePosX[arrayid] - newpath[i-1][0]) / fact); 1583 | newpath[i][1] = newpath[i-1][1] + ((NodePosY[arrayid] - newpath[i-1][1]) / fact); 1584 | DriverPath[driverid][i][2] = DriverPath[driverid][i-1][2] + ((NodePosZ[arrayid] - DriverPath[driverid][i-1][2]) / fact); 1585 | 1586 | DriverPathLen[driverid] ++; 1587 | 1588 | if(dis - ndis < MIN_NODE_DIST) 1589 | { 1590 | arrayid ++; 1591 | continue; 1592 | } 1593 | } 1594 | else 1595 | { 1596 | if(i > 0) i --; 1597 | arrayid ++; 1598 | continue; 1599 | } 1600 | } 1601 | 1602 | if(arrayid < amount_of_nodes - 1) print("[DRIVERS] Error: Could not finish path. Higher MAX_PATH_LEN or MAX_NODE_DIST!"); 1603 | 1604 | newpath = OffsetPath(newpath, DriverPathLen[driverid], -SIDE_DIST); // Offset (right side) - Quick! 1605 | 1606 | newpath = SmoothPath(newpath, DriverPathLen[driverid]); // Smoothing - heaviest part here 1607 | 1608 | new Float:MapZd, Float:MapZu; 1609 | 1610 | for(new i = 0; i < DriverPathLen[driverid]; i ++) 1611 | { 1612 | MapZd = RayCastLineZ(newpath[i][0], newpath[i][1], DriverPath[driverid][i][2], -10.0); 1613 | MapZu = RayCastLineZ(newpath[i][0], newpath[i][1], DriverPath[driverid][i][2], 30.0); 1614 | 1615 | if(MapZd == 0.0) MapZd = -990.0; 1616 | if(MapZu == 0.0) MapZu = -990.0; 1617 | 1618 | if(MapZd > -900.0 && MapZu > -900.0) 1619 | { 1620 | new Float:difd = DriverPath[driverid][i][2] - MapZd, Float:difu = (MapZu - DriverPath[driverid][i][2]); 1621 | 1622 | if(difu < difd) DriverPath[driverid][i][2] = MapZu; 1623 | else DriverPath[driverid][i][2] = MapZd; 1624 | } 1625 | else if(MapZd > -900.0) DriverPath[driverid][i][2] = MapZd; 1626 | else if(MapZu > -900.0) DriverPath[driverid][i][2] = MapZu; 1627 | 1628 | DriverPath[driverid][i][2] = DriverPath[driverid][i][2] + VehicleZOffsets[Drivers[driverid][nVehicleModel]-400]; 1629 | 1630 | DriverPath[driverid][i][0] = newpath[i][0]; 1631 | DriverPath[driverid][i][1] = newpath[i][1]; 1632 | } 1633 | 1634 | Drivers[driverid][nCurNode] = 0; 1635 | Drivers[driverid][nState] = DRIVER_STATE_DRIVE; 1636 | Drivers[driverid][nSpeed] = (MIN_SPEED + MAX_SPEED) / 3.0; 1637 | Drivers[driverid][nDistance] = distance; 1638 | 1639 | SetTimerEx("FCNPC_OnReachDestination", 50, 0, "d", Drivers[driverid][nNPCID]); 1640 | 1641 | if(Drivers[driverid][nType] == DRIVER_TYPE_TAXI && Drivers[driverid][nOnDuty] && IsPlayerConnected(Drivers[driverid][nPlayer])) 1642 | { 1643 | if(TaxiState[Drivers[driverid][nPlayer]] == TAXI_STATE_DRIVE1) 1644 | { 1645 | if(distance < 250.0) SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Service]: {009900}Stay where you are. A driver is on his way!"); 1646 | else if(distance < 1000.0) SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Service]: {DD9900}Please be patient, our driver may need some time to approach your location."); 1647 | else if(distance < 2000.0) SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Service]: {DD5500}We don't have a taxi close to you. Please wait a few minutes."); 1648 | else SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Service]: {DD5500}We hope you're not in a hurry. Our driver will need quite a while to come to you."); 1649 | } 1650 | else if(TaxiState[Drivers[driverid][nPlayer]] == TAXI_STATE_DRIVE2) 1651 | { 1652 | new roughmins = floatround((distance / (8.3 * MAX_SPEED * DUTY_SPEED_BOOST)) / 60.0); 1653 | 1654 | if(roughmins <= 1) SendClientMessage(Drivers[driverid][nPlayer], -1, "[Taxi Driver]: {009900}I don't like these short ways..."); 1655 | else 1656 | { 1657 | new str[115]; 1658 | format(str, sizeof(str), "[Taxi Driver]: {009900}Most would say this takes more than %d minutes. But I'll get you there in about %d!", roughmins + 2 + random(4), roughmins); 1659 | SendClientMessage(Drivers[driverid][nPlayer], -1, str); 1660 | } 1661 | } 1662 | } 1663 | 1664 | #if DEBUG_PRINTS == true 1665 | if(InitialCalculations <= DRIVER_AMOUNT) printf("[DRIVERS] Debug: (%d ms) - PathLen: %d - Nr %d/%d", GetTickCount() - t, DriverPathLen[driverid], InitialCalculations, DRIVER_AMOUNT); 1666 | else printf("[DRIVERS] Debug: (%d ms) - PathLen: %d", GetTickCount() - t, DriverPathLen[driverid]); 1667 | #endif 1668 | 1669 | if(InitialCalculations == DRIVER_AMOUNT) { printf("\n[DRIVERS] Initial calculations completed after %.02fs.", (GetTickCount() - InitialCalculationStart) / 1000.0); PrintDriverUpdate(); InitialCalculations = DRIVER_AMOUNT+1; } 1670 | 1671 | if(DriverPathLen[driverid] > MaxPathLen) MaxPathLen = DriverPathLen[driverid]; 1672 | 1673 | if(avgcalcidx >= sizeof(avgcalctimes)) avgcalcidx = 0; 1674 | avgcalctimes[avgcalcidx] = GetTickCount() - t; 1675 | avgcalcidx ++; 1676 | 1677 | return 1; 1678 | } 1679 | 1680 | return 1; 1681 | } 1682 | 1683 | // ----------------------------------------------------------------------------- Main code 1684 | 1685 | public FCNPC_OnReachDestination(npcid) 1686 | { 1687 | if(!Initialized) return 1; 1688 | 1689 | if(!FCNPC_IsSpawned(npcid) || FCNPC_IsDead(npcid)) return 1; 1690 | 1691 | new driverid = GetDriverID(npcid); 1692 | 1693 | if(driverid != -1) 1694 | { 1695 | #if MAP_ZONES == true 1696 | if(Drivers[driverid][nGangZone] != -1) { GangZoneDestroy(Drivers[driverid][nGangZone]); Drivers[driverid][nGangZone] = -1; } 1697 | #endif 1698 | 1699 | Drivers[driverid][nLT] = GetTickCount(); 1700 | 1701 | Drivers[driverid][nCurNode] ++; 1702 | 1703 | if(Drivers[driverid][nType] == DRIVER_TYPE_COP && random(100) <= 2) 1704 | { 1705 | if(Drivers[driverid][nLT] - Drivers[driverid][nCopStuffTick] > 9000 && Drivers[driverid][nOnDuty]) 1706 | { 1707 | FCNPC_UseVehicleSiren(npcid, false); 1708 | Drivers[driverid][nOnDuty] = false; 1709 | } 1710 | else if(Drivers[driverid][nLT] - Drivers[driverid][nCopStuffTick] > 90000 && !Drivers[driverid][nOnDuty]) 1711 | { 1712 | Drivers[driverid][nCopStuffTick] = Drivers[driverid][nLT]; 1713 | FCNPC_UseVehicleSiren(npcid, true); 1714 | Drivers[driverid][nOnDuty] = true; 1715 | } 1716 | } 1717 | 1718 | if(Drivers[driverid][nCurNode] == DriverPathLen[driverid]) // Final Destination! >:D 1719 | { 1720 | if(Drivers[driverid][nType] == DRIVER_TYPE_RANDOM || (Drivers[driverid][nType] == DRIVER_TYPE_TAXI && !Drivers[driverid][nOnDuty]) || Drivers[driverid][nType] == DRIVER_TYPE_COP) 1721 | { 1722 | Drivers[driverid][nState] = DRIVER_STATE_NONE; 1723 | 1724 | new Float:X, Float:Y, Float:Z; 1725 | FCNPC_GetPosition(npcid, X, Y, Z); 1726 | 1727 | new MapNode:startnode, MapNode:endnode, Float:dist; 1728 | 1729 | GetClosestMapNodeToPoint(X, Y, Z, startnode); 1730 | 1731 | do 1732 | { 1733 | endnode = GetRandomStartEndPathNode(); 1734 | GetDistanceBetweenMapNodes(startnode, endnode, dist); 1735 | } 1736 | while(dist < ROUTE_MIN_DIST || dist > ROUTE_MAX_DIST); 1737 | 1738 | SetTimerEx("pubCalculatePath", 10000 + random(10000), 0, "ddd", driverid, _:startnode, _:endnode); 1739 | } 1740 | else if(Drivers[driverid][nType] == DRIVER_TYPE_TAXI && Drivers[driverid][nOnDuty]) 1741 | { 1742 | Drivers[driverid][nState] = DRIVER_STATE_NONE; 1743 | new playerid = Drivers[driverid][nPlayer]; 1744 | 1745 | if(TaxiState[playerid] == TAXI_STATE_DRIVE1) 1746 | { 1747 | SetVehicleParamsForPlayer(Drivers[driverid][nVehicle], playerid, 1, 0); 1748 | TaxiState[playerid] = TAXI_STATE_WAIT1; 1749 | } 1750 | 1751 | if(TaxiState[playerid] == TAXI_STATE_DRIVE2) 1752 | { 1753 | SetVehicleParamsForPlayer(Drivers[driverid][nVehicle], playerid, 0, 1); 1754 | SetTimerEx("pub_RemovePlayerFromVehicle", 1000, 0, "d", playerid); 1755 | SetTimerEx("ResetTaxi", 10000, 0, "dd", driverid, 0); 1756 | 1757 | SendClientMessage(playerid, -1, "[Taxi Driver]: {009900}Hope you enjoyed the ride! Have a nice day."); 1758 | } 1759 | } 1760 | 1761 | FCNPC_SetKeys(npcid, 0, 0, 0); 1762 | 1763 | #if DEBUG_BUBBLE == true 1764 | new str[40]; 1765 | format(str, sizeof(str), "{888888}[%d]\n{880000}Finished!", driverid); 1766 | SetPlayerChatBubble(npcid, str, -1, 10.0, 60000); 1767 | #endif 1768 | return 1; 1769 | } 1770 | 1771 | new cnode = Drivers[driverid][nCurNode]; 1772 | 1773 | #if MAP_ZONES == true 1774 | new Float:X, Float:Y, Float:Z; 1775 | FCNPC_GetPosition(npcid, X, Y, Z); 1776 | 1777 | Drivers[driverid][nGangZone] = GangZoneCreate(X-4.5, Y-4.5, X+4.5, Y+4.5); 1778 | GangZoneShowForAll(Drivers[driverid][nGangZone], 0x66FF00FF); 1779 | #endif 1780 | 1781 | if(!FCNPC_IsStreamedInForAnyone(npcid)) 1782 | { 1783 | if(Drivers[driverid][nCurNode] < DriverPathLen[driverid]-10) 1784 | { 1785 | Drivers[driverid][nCurNode] += 3; 1786 | cnode += 3; 1787 | } 1788 | 1789 | FCNPC_GoTo(npcid, DriverPath[driverid][cnode][0], DriverPath[driverid][cnode][1], DriverPath[driverid][cnode][2], FCNPC_MOVE_TYPE_DRIVE, MAX_SPEED*0.8, .pathfinding = FCNPC_MOVE_PATHFINDING_NONE, .radius = 0.0, .set_angle = true, .min_distance = 0.0, .stopdelay = 0); 1790 | Drivers[driverid][nSpeed] = MAX_SPEED*0.7; 1791 | Drivers[driverid][nActive] = false; 1792 | 1793 | return 1; 1794 | } 1795 | 1796 | Drivers[driverid][nActive] = true; 1797 | 1798 | #if MAP_ZONES != true 1799 | new Float:X, Float:Y, Float:Z; 1800 | FCNPC_GetPosition(npcid, X, Y, Z); 1801 | #endif 1802 | 1803 | if(X < -3000.0) X = -3000.0; 1804 | if(X > 3000.0) X = 3000.0; 1805 | if(Y < -3000.0) Y = -3000.0; 1806 | if(Y > 3000.0) Y = 3000.0; 1807 | 1808 | Drivers[driverid][nZoneX] = floatround((X + 3000.0) / 6000.0 * ZONES_NUM); 1809 | Drivers[driverid][nZoneY] = floatround((Y + 3000.0) / 6000.0 * ZONES_NUM); 1810 | 1811 | new Float:A1, Float:A2, bool:blocked = false, Float:x2, Float:y2, Float:z2, Float:dist; 1812 | GetVehicleZAngle(Drivers[driverid][nVehicle], A1); 1813 | 1814 | for(new i = 0; i < DRIVER_AMOUNT; i ++) 1815 | { 1816 | if(!Drivers[i][nUsed] || !Drivers[i][nActive] || i == driverid) continue; 1817 | 1818 | if(!FCNPC_IsValid(Drivers[i][nNPCID])) continue; 1819 | 1820 | if(Drivers[driverid][nZoneX] != Drivers[i][nZoneX] || Drivers[driverid][nZoneY] != Drivers[i][nZoneY]) continue; 1821 | 1822 | FCNPC_GetPosition(Drivers[i][nNPCID], x2, y2, z2); 1823 | 1824 | dist = floatsqroot(floatpower(x2-X, 2) + floatpower(y2-Y, 2)); 1825 | 1826 | if(dist >= JAM_DIST) continue; // Distance between both NPCs 1827 | 1828 | GetVehicleZAngle(Drivers[driverid][nVehicle], A2); 1829 | 1830 | if(floatangledist(A1, A2) >= JAM_ANGLE) continue; // Angle distance between both NPCs (do they face the same direction?) 1831 | 1832 | if(floatangledist(A1, -atan2(x2-X, y2-Y)) >= JAM_ANGLE) continue; // Angle distance between NPC1 and the direction to NPC2 (is NPC1 going in NPC2's direction?) - Criteria for being behind! 1833 | 1834 | blocked = true; 1835 | if(Drivers[driverid][nSpeed] > Drivers[i][nSpeed]) Drivers[driverid][nSpeed] = Drivers[i][nSpeed] - 0.15; 1836 | else if(dist < (JAM_DIST*0.3)) Drivers[driverid][nSpeed] -= 0.1; 1837 | 1838 | if(Drivers[driverid][nSpeed] < MIN_SPEED) Drivers[driverid][nSpeed] = MIN_SPEED; 1839 | 1840 | break; 1841 | } 1842 | 1843 | if(!blocked) 1844 | { 1845 | new Float:AimedSpeed; 1846 | if(cnode > 1 && cnode < DriverPathLen[driverid]-4) 1847 | { 1848 | new Float:Xdif = DriverPath[driverid][cnode][0] - X, Float:Ydif = DriverPath[driverid][cnode][1] - Y, Float:Zdif = DriverPath[driverid][cnode][2] - Z; 1849 | 1850 | new Float:dif = floatsqroot(Xdif*Xdif + Ydif*Ydif); 1851 | if(dif == 0.0) dif = 1.0; 1852 | else dif = Zdif / dif; 1853 | 1854 | if(dif < 0.0) dif *= -1.0; 1855 | if(dif > 1.0) dif = 1.0; 1856 | 1857 | AimedSpeed = MAX_SPEED - (1.7*dif*(MAX_SPEED-MIN_SPEED)); // base speed based on steepness 1858 | 1859 | new Adif = floatangledist(0.0, Get2DAngleOf3Points(DriverPath[driverid][cnode-1][0], DriverPath[driverid][cnode-1][1], DriverPath[driverid][cnode][0], DriverPath[driverid][cnode][1], DriverPath[driverid][cnode+1][0], DriverPath[driverid][cnode+1][1])); 1860 | 1861 | if(Adif > 40) Adif = 40; 1862 | 1863 | AimedSpeed = AimedSpeed - ((AimedSpeed/80.0) * (Adif)); // turning angle 1864 | } 1865 | else if(Drivers[driverid][nOnDuty] && Drivers[driverid][nType] == DRIVER_TYPE_TAXI) AimedSpeed = Drivers[driverid][nSpeed] * 0.8; 1866 | else AimedSpeed = (MIN_SPEED + MAX_SPEED) / 2.0; 1867 | 1868 | if(AimedSpeed < Drivers[driverid][nSpeed]) Drivers[driverid][nSpeed] = (Drivers[driverid][nSpeed] + AimedSpeed*4.5) / 5.5; 1869 | else Drivers[driverid][nSpeed] += (AimedSpeed - Drivers[driverid][nSpeed]) / (Drivers[driverid][nSpeed]*10.0) + 0.02; 1870 | 1871 | if(Drivers[driverid][nSpeed] < MIN_SPEED) Drivers[driverid][nSpeed] = MIN_SPEED; 1872 | } 1873 | 1874 | if(Drivers[driverid][nSpeed] > MAX_SPEED) Drivers[driverid][nSpeed] = MAX_SPEED; 1875 | 1876 | if(!blocked && Drivers[driverid][nOnDuty] && cnode < DriverPathLen[driverid]-6) Drivers[driverid][nSpeed] *= DUTY_SPEED_BOOST; 1877 | 1878 | new Float:Qw, Float:Qx, Float:Qy, Float:Qz; 1879 | FCNPC_GetPosition(npcid, X, Y, Z); 1880 | 1881 | #if DEBUG_BUBBLE == true 1882 | new bool:complex, Float:z_angle; 1883 | #endif 1884 | 1885 | if(cnode > 0 && cnode < DriverPathLen[driverid]-1) 1886 | { 1887 | new Float:z_angle1, Float:z_angle2, Float:waste, Float:surface_rx, Float:surface_ry; 1888 | 1889 | GetRZFromVectorXY(DriverPath[driverid][cnode][0] - DriverPath[driverid][cnode - 1][0], DriverPath[driverid][cnode][1] - DriverPath[driverid][cnode - 1][1], z_angle1); 1890 | GetRZFromVectorXY(DriverPath[driverid][cnode + 1][0] - DriverPath[driverid][cnode][0], DriverPath[driverid][cnode + 1][1] - DriverPath[driverid][cnode][1], z_angle2); 1891 | 1892 | #if DEBUG_BUBBLE == true 1893 | z_angle = z_angle1; 1894 | #endif 1895 | 1896 | new Float:angle_dif = floatangledistdir(z_angle1, z_angle2); 1897 | 1898 | FCNPC_SetKeys(npcid, 0, (angle_dif <= -STEER_ANGLE ? 128 : (angle_dif >= STEER_ANGLE ? -128 : 0)), 0); 1899 | 1900 | CA_RayCastLineAngle(X, Y, Z + 0.75, X, Y, Z - 3.0, waste, waste, waste, surface_rx, surface_ry, waste); 1901 | 1902 | if(Drivers[driverid][nVehicleIsBike]) 1903 | { 1904 | angle_dif = -angle_dif / 15.0; 1905 | 1906 | if(angle_dif < -1.0) angle_dif = -1.0; 1907 | else if(angle_dif > 1.0) angle_dif = 1.0; 1908 | 1909 | angle_dif = (angle_dif + Drivers[driverid][nVehicleLastLean] + Drivers[driverid][nVehicleLastLean]) / 3.0; 1910 | 1911 | Drivers[driverid][nVehicleLastLean] = angle_dif; 1912 | 1913 | GetRotForSurface(Qw, Qx, Qy, Qz, surface_rx, surface_ry, 0.0, angle_dif * 30.0, z_angle1); 1914 | } 1915 | else GetRotForSurface(Qw, Qx, Qy, Qz, surface_rx, surface_ry, 0.0, 0.0, z_angle1); 1916 | } 1917 | else 1918 | { 1919 | FCNPC_SetKeys(npcid, 0, 0, 0); 1920 | 1921 | if(cnode > 0) 1922 | { 1923 | new Float:z_angle1, Float:rx; 1924 | 1925 | GetRZFromVectorXY(DriverPath[driverid][cnode][0] - DriverPath[driverid][cnode - 1][0], DriverPath[driverid][cnode][1] - DriverPath[driverid][cnode - 1][1], z_angle1); 1926 | GetRXFromVectorZ(DriverPath[driverid][cnode][2] - DriverPath[driverid][cnode - 1][2], rx); 1927 | 1928 | GetRotForSurface(Qw, Qx, Qy, Qz, 0.0, 0.0, rx, 0.0, z_angle1); 1929 | } 1930 | else GetRotForSurface(Qw, Qx, Qy, Qz, 0.0, 0.0, 0.0, 0.0, FCNPC_GetAngle(npcid)); 1931 | } 1932 | 1933 | FCNPC_GoTo(npcid, DriverPath[driverid][cnode][0], DriverPath[driverid][cnode][1], DriverPath[driverid][cnode][2], FCNPC_MOVE_TYPE_DRIVE, Drivers[driverid][nSpeed], .pathfinding = FCNPC_MOVE_PATHFINDING_NONE, .radius = 0.0, .set_angle = true, .min_distance = 0.0, .stopdelay = 0); 1934 | FCNPC_SetQuaternion(npcid, Qw, Qx, Qy, Qz); 1935 | 1936 | #if DEBUG_BUBBLE == true 1937 | new str[65]; 1938 | format(str, sizeof(str), "{888888}[%d]\nX:%d Y:%d B:%b C:%b\n %d {666666}Speed: %.02f\nangle: %f", driverid, Drivers[driverid][nZoneX], Drivers[driverid][nZoneY], blocked, complex, cnode, Drivers[driverid][nSpeed], z_angle); 1939 | SetPlayerChatBubble(npcid, str, -1, 10.0, 5000); 1940 | #endif 1941 | 1942 | return 1; 1943 | } 1944 | return 1; 1945 | } 1946 | 1947 | // ----------------------------------------------------------------------------- Some random functions. 1948 | 1949 | Float:SmoothPath(const Float:path[][2], len = sizeof path) // Basic Smoothing algorithm I (NaS) converted from Python - All nodes orientate at 2 coords in a relation (defined by weight_data & weight_smooth), the original data and the smooth path 1950 | { 1951 | new Float:npath[MAX_PATH_LEN][2]; 1952 | 1953 | if(len > MAX_PATH_LEN) len = MAX_PATH_LEN; 1954 | 1955 | for(new i = 0; i < len; i ++) 1956 | { 1957 | npath[i][0] = path[i][0]; 1958 | npath[i][1] = path[i][1]; 1959 | } 1960 | 1961 | for(new x = 0; x < SMOOTH_AMOUNT; x ++) for(new i = 1; i < len - 1; i ++) // all nodes except start & end 1962 | { 1963 | npath[i][0] = npath[i][0] + SMOOTH_W_DATA * (path[i][0] - npath[i][0]); // Drag node to original pos (with factor) 1964 | npath[i][0] = npath[i][0] + SMOOTH_W_SMOOTH * (npath[i-1][0] + npath[i+1][0] - (2.0 * npath[i][0])); // Drag node to interpolated pos (with factor) 1965 | 1966 | npath[i][1] = npath[i][1] + SMOOTH_W_DATA * (path[i][1] - npath[i][1]); 1967 | npath[i][1] = npath[i][1] + SMOOTH_W_SMOOTH * (npath[i-1][1] + npath[i+1][1] - (2.0 * npath[i][1])); 1968 | } 1969 | 1970 | return npath; 1971 | } 1972 | 1973 | Float:OffsetPath(const Float:path[MAX_PATH_LEN][2], len, Float:d) // Another classy algorithm for offsetting a 2D path - d = distance, negative = right 1974 | { 1975 | new Float:H[MAX_PATH_LEN][2], Float:U[MAX_PATH_LEN][2]; 1976 | 1977 | for(new i = 0; i < len-1; i ++) 1978 | { 1979 | new Float:C = path[i+1][0] - path[i][0]; 1980 | new Float:S = path[i+1][1] - path[i][1]; 1981 | new Float:L = floatsqroot(C*C+S*S); 1982 | U[i][0] = C/L; 1983 | U[i][1] = S/L; 1984 | } 1985 | 1986 | H[0][0] = path[0][0] - d*U[0][1]; 1987 | H[0][1] = path[0][1] + d*U[0][0]; 1988 | 1989 | for(new i = 1; i < len-1; i ++) 1990 | { 1991 | new Float:v = (1.0 + U[i][0]*U[i-1][0] + U[i][1]*U[i-1][1]); 1992 | new Float:L = d/(v == 0.0 ? 0.0001 : v); 1993 | 1994 | H[i][0] = path[i][0] - L*(U[i][1] + U[i-1][1]); 1995 | H[i][1] = path[i][1] + L*(U[i][0] + U[i-1][0]); 1996 | } 1997 | 1998 | H[len-1][0] = path[len-1][0] - d*U[len-2][1]; 1999 | H[len-1][1] = path[len-1][1] + d*U[len-2][0]; 2000 | 2001 | return H; 2002 | } 2003 | 2004 | GetXYInFrontOfPoint(Float:gX, Float:gY, Float:R, &Float:x, &Float:y, Float:distance) 2005 | { // Created by Y_Less 2006 | x = gX + (distance * floatsin(-R, degrees)); 2007 | y = gY + (distance * floatcos(-R, degrees)); 2008 | } 2009 | 2010 | HidePlayerDialog(playerid) return ShowPlayerDialog(playerid,-1,0," "," "," "," "); 2011 | 2012 | MapNode:GetRandomStartEndPathNode() 2013 | { 2014 | if(PathNodesNum < 1 || PathNodesNum > MAX_PATH_NODES) return INVALID_MAP_NODE_ID; 2015 | 2016 | return PathNodes[random(PathNodesNum)]; 2017 | } 2018 | 2019 | Float:Get2DAngleOf3Points(Float:x1, Float:y1, Float:x2, Float:y2, Float:x3, Float:y3) 2020 | { 2021 | return floatangledistdir(-atan2(x2-x1, y2-y1), -atan2(x3-x2, y3-y2)); 2022 | } 2023 | 2024 | Float:RayCastLineZ(Float:X, Float:Y, Float:Z, Float:dist) 2025 | { 2026 | if(CA_RayCastLine(X, Y, Z, X, Y, Z + dist, X, Y, Z)) return Z; 2027 | else return -999.0; 2028 | } 2029 | 2030 | floatangledist(Float:alpha, Float:beta) // Ranging from 0 to 180, not directional 2031 | { 2032 | new phi = floatround(floatabs(beta - alpha), floatround_floor) % 360; 2033 | new distance = phi > 180 ? 360 - phi : phi; 2034 | return distance; 2035 | } 2036 | 2037 | Float:floatangledistdir(Float:firstAngle, Float:secondAngle) // Ranging from -180 to 180 (directional) 2038 | { 2039 | new Float:difference = secondAngle - firstAngle; 2040 | while(difference < -180.0) difference += 360.0; 2041 | while(difference > 180.0) difference -= 360.0; 2042 | return difference; 2043 | } 2044 | 2045 | GetDriverID(npcid) // Fast NPCID -> DriverID 2046 | { 2047 | if(!FCNPC_IsValid(npcid) || npcid < 0 || npcid >= MAX_PLAYERS) return -1; 2048 | 2049 | new id = NPCDriverID[npcid]; 2050 | 2051 | if(id >= 0 && id < DRIVER_AMOUNT) if(Drivers[id][nUsed] && Drivers[id][nNPCID] == npcid) return id; 2052 | 2053 | for(new i = 0; i < DRIVER_AMOUNT; i ++) // Note: This will only be executed if the Array doesn't hold the ID for some reason. Never happened yet. 2054 | { 2055 | if(npcid != Drivers[i][nNPCID] || !Drivers[i][nUsed]) continue; 2056 | 2057 | return i; 2058 | } 2059 | 2060 | return -1; 2061 | } 2062 | 2063 | public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ) // Fixes NPC Car Damage 2064 | { 2065 | return 1; 2066 | } 2067 | 2068 | public FCNPC_OnTakeDamage(npcid, issuerid, Float:amount, weaponid, bodypart) // Fixes NPC Body Damage 2069 | { 2070 | return 1; 2071 | } 2072 | 2073 | public OnPlayerTakeDamage(playerid, issuerid, Float: amount, weaponid, bodypart) 2074 | { 2075 | return 1; 2076 | } 2077 | 2078 | public OnPlayerGiveDamage(playerid, damagedid, Float: amount, weaponid, bodypart) 2079 | { 2080 | return 1; 2081 | } 2082 | 2083 | GetRotForSurface(&Float:qw, &Float:qx, &Float:qy, &Float:qz, Float:surface_rx, Float:surface_ry, Float:offset_rx = 0.0, Float:offset_ry = 0.0, Float:offset_rz = 0.0) // By NaS 2084 | { 2085 | const eulermode:rot_mode = euler_zxy; 2086 | 2087 | new Float:matrix[4][4]; 2088 | 2089 | GetRotationMatrixFromEuler(matrix, offset_rx, offset_ry, offset_rz, rot_mode); 2090 | 2091 | if(surface_rx != 0.0 || surface_ry != 0.0) RotateMatrixWithEuler(matrix, surface_rx, surface_ry, 0.0, rot_mode); 2092 | 2093 | new Float:euler_rx, Float:euler_ry, Float:euler_rz; 2094 | 2095 | GetEulerFromMatrix(matrix, euler_rx, euler_ry, euler_rz); 2096 | GetQuatFromEuler(euler_rx, euler_ry, euler_rz, qw, qx, qy, qz, rot_mode); 2097 | 2098 | return 1; 2099 | } 2100 | 2101 | GetRZFromVectorXY(Float:vx, Float:vy, &Float:a) 2102 | { 2103 | if(vx == 0.0 && vy == 0.0) return 0; 2104 | 2105 | new Float:len = VectorSize(vx, vy, 0.0); 2106 | 2107 | vx = vx / len; 2108 | vy = vy / len; 2109 | 2110 | a = atan2(vy, vx) - 90.0; 2111 | 2112 | return 1; 2113 | } 2114 | 2115 | GetRXFromVectorZ(Float:vz, &Float:rx) 2116 | { 2117 | rx = -(acos(vz) - 90.0); 2118 | 2119 | return 1; 2120 | } 2121 | 2122 | // #EOF 2123 | --------------------------------------------------------------------------------