Yes, everything is handled by client rendering, but it's not such a beast as i thought before (Pentium 4, 2x3GHz core). Script changes cars' handling only once while changing ground type, so maximaly, for example, once a second or twice.
Some code:
addEventHandler( "onClientPreRender", root, function ( )
--............................
-- this lower the speed
if ( isVehicleOnGround( veh ) and getElementData( veh, 'handled' ) == true ) then
if a_type > 0 then
if ( a_type == 1 and speed > 100 ) then -- concrete
setElementVelocity( veh, velX-velX/200, velY-velY/200, velZ )
elseif ( a_type == 2 and speed > 50 ) then -- gravel
setElementVelocity( veh, velX-velX/75, velY-velY/75, velZ )
elseif ( a_type == 3 and speed > 25 ) then -- grass
setElementVelocity( veh, velX-velX/50, velY-velY/50, velZ )
elseif ( a_type == 4 and speed > 50 ) then -- dirt
setElementVelocity( veh, velX-velX/100, velY-velY/100, velZ )
elseif ( a_type == 5 and speed > 25 ) then -- sand
setElementVelocity( veh, velX-velX/25, velY-velY/25, velZ )
elseif ( a_type == 7 and speed > 50 ) then -- wood
setElementVelocity( veh, velX-velX/150, velY-velY/150, velZ )
elseif ( a_type == 10 and speed > 25 ) then -- vegetation
setElementVelocity( veh, velX-velX/30, velY-velY/30, velZ )
elseif ( a_type == 11 and speed > 0 ) then -- water
setElementVelocity( veh, velX-velX/10, velY-velY/10, velZ )
elseif ( a_type == 12 and speed > 50 ) then -- misc
setElementVelocity( veh, velX-velX/125, velY-velY/125, velZ )
end;
if a_change ~= a_type then
local x = getSurfaceHandle( a_type )
setHandlingChanges( veh, x, true )
a_change = a_type
end;
else
if a_change ~= a_type then
setHandlingChanges( veh, _, false )
a_change = a_type;
end;
--setVehicleHandling( veh, "tractionMultiplier", a_traction )
--
--end;
end;
end;
--.....................................
local material = getMaterial( X0, Y0, Z0 )
if ( a_material ~= material ) then
local a_text = '';
local m_type = 0;
if material >= 0 then
m_type = MATERIALS[material+1][2];
--outputChatBox( 'Material: '..MATERIALS[material+1][1]..', type: '..material_types[m_type+1]..' ('..m_type..'), ID: '..material ) -- output on surface change only
a_text = 'TERRAIN: '..MATERIALS[material+1][1]..' [iD: '..material..'], type: '..material_types[m_type+1]..' [iD: '..m_type..']';
else
if material == -1 then a_text = 'No ground!'
elseif material == -2 then a_text = 'Has not collided'
elseif material == -3 then a_text = 'Collided with created object (not world object)'
elseif material == -4 then a_text = 'Unknown material'
end;
end );
end;
guiSetText( guiGroundLabel, a_text );
--guiLabelSetColor( guiGroundLabel, 255 - math.ceil ( FPSCalc * FPSLimit ), math.ceil ( FPSCalc * FPSLimit ), 0 )
a_type = m_type;
end;
a_material = material;
-- FUNCTIONS
function getSurfaceHandle( type )
if type == 1 then return 1.2 -- concrete
elseif type == 2 then return 1.25 -- gravel
elseif type == 3 then return 2.5 -- grass
elseif type == 4 then return 1.5 -- dirt
elseif type == 5 then return 2 -- sand
elseif type == 7 then return 1.75 -- wood
elseif type == 10 then return 2.25 -- veget
elseif type == 11 then return 2.75 -- water
elseif type == 12 then return 1.1 -- misc
else return 1
end;
end;
function getMaterial( x, y, z )
if x then
local gz = getGroundPosition( x, y, z )
if not gz then --outputChatBox( 'No ground!' )
return -1
end
local hit, _,_,_,ele,_,_,_,material = processLineOfSight( x,y,z, x,y,gz-10, true,false,false,true,false,true,true,true,localPlayer, true )
if not hit then --outputChatBox( 'Has not collided' )
return -2
end
if ele then --outputChatBox( 'Collided with created object (not world object)' )
return -3
end
if not material then
--outputChatBox( 'Unknown material' )
return -4
else
return material
end
else
return 0
end;
end;
It's quite wierd, and that's not everything of course, but on the other hand it's easy.
The server side is complete:
---------------------------- HANDLING TEST ----------------------------
addEventHandler( "onVehicleEnter", getRootElement(), function( player, seat )
--local hset = { };
if seat == 0 then
if getElementData( source, 'handled' ) == false then
local mv = getVehicleHandling( source )["maxVelocity"];
setVehicleHandling( source, "maxVelocity", mv*2 );
--local ea = getVehicleHandling( source )["engineAcceleration"];
--setVehicleHandling( source, "engineAcceleration", ea-ea*0.1 );
local ei = getVehicleHandling( source )["engineInertia"];
setVehicleHandling( source, "engineInertia", ei*5 );
local bd = getVehicleHandling( source )["brakeDeceleration"];
setVehicleHandling( source, "brakeDeceleration", bd-bd*0.25 );
local sl = getVehicleHandling( source )["steeringLock"];
setVehicleHandling( source, "steeringLock", sl-sl*0.2 );
local tl = getVehicleHandling( source )["tractionLoss"];
setVehicleHandling( source, "tractionLoss", tl-tl*0.3 );
local tr = getVehicleHandling( source )["tractionMultiplier"];
local s_tr = tr+tr*0.5;
setVehicleHandling( source, "tractionMultiplier", s_tr ); -- using in dynamic handling
local tm = getVehicleHandling( source )["turnMass"];
setVehicleHandling( source, "turnMass", tm*1.25 );
local m = getVehicleHandling( source )["mass"];
setVehicleHandling( source, "mass", m*1.2 );
local dc = getVehicleHandling( source )["dragCoeff"];
setVehicleHandling( source, "dragCoeff", dc*0.5 );
-- wtfs
setVehicleHandling( source, "ABS", true );
setVehicleHandling( source, "headLight", 1 );
setVehicleHandling( source, "tailLight", 1 );
setElementData( source, 'handled', true );
-- send values setted
setElementData( source, "s_tractionMultiplier", s_tr );
else
local o_tr = getElementData( source, "s_tractionMultiplier" );
local g_tr = getVehicleHandling( source )["tractionMultiplier"];
if g_tr ~= o_tr then setVehicleHandling( source, "tractionMultiplier", o_tr ) end;
end;
outputChatBox( '* Handling: maxV='..string.format( '%d', getVehicleHandling( source )["maxVelocity"] )..', engAcc='..string.format( '%d', getVehicleHandling( source )["engineAcceleration"] )..', engIn='..string.format( '%d', getVehicleHandling( source )["engineInertia"] )..', brkDecc='..string.format( '%d', getVehicleHandling( source )["brakeDeceleration"] )..', steerL='..string.format( '%d', getVehicleHandling( source )["steeringLock"] ), player, 20, 200, 50 );
end;
end );
addEvent( 'changeHandling', true );
addEventHandler( 'changeHandling', root, function( veh, x, state )
local y = getElementData( veh, "s_tractionMultiplier" );
if state == true then
setVehicleHandling( veh, "tractionMultiplier", y/x );
--outputChatBox( '* tr = '..string.format( '%.2f', y/x ), source );
else
setVehicleHandling( veh, "tractionMultiplier", y );
--outputChatBox( '* tr = '..string.format( '%.2f', y ), source );
end;
end );
addCommandHandler( 'abs', function( player ) -- test - dunno if it works
if isPedInVehicle( player ) then
local veh = getPedOccupiedVehicle( player );
if getVehicleHandling( veh )[ "ABS" ] == true then
setVehicleHandling( veh, "ABS", false )
outputChatBox( '* ABS turned off.', player, 150, 50, 50 );
else
setVehicleHandling( veh, "ABS", true )
outputChatBox( '* ABS turned on.', player, 150, 50, 50 );
end;
else
outputChatBox( '* Be in vehicle.', player );
end;
end );
And very important table (it's client side, but it can be probably server side also), just copied from wiki (materials): [materials.lua]
material_types = { 'Default', 'Concrete', 'Gravel', 'Grass', 'Dirt', 'Sand', 'Glass', 'Wood', 'Metal', 'Stone', 'Vegetation', 'Water', 'Misc' }; --[0..12]
MATERIALS = { --[0..178] [ 'Name', type ]
{ 'Default', 0 },
{ 'Tarmac', 0 },
{ 'Tarmac (fucked)', 0 },
{ 'Tarmac (really fucked)', 0 },
{ 'Pavement', 1 },
{ 'Pavement (fucked)', 1 },
{ 'Gravel', 2 },
{ 'Concrete (fucked)', 1 },
{ 'Painted Ground', 1 },
{ 'Grass (short lush)', 3 },
{ 'Grass (medium lush)', 3 },
{ 'Grass (long lush)', 3 },
{ 'Grass (short dry)', 3 },
{ 'Grass (medium dry)', 3 },
{ 'Grass (long dry)', 3 },
{ 'Golf Grass (rough)', 3 },
{ 'Golf Grass (smooth)', 3 },
{ 'Steep Slidy Grass', 3 },
{ 'Steep Cliff', 9 },
{ 'Flower Bed', 4 },
{ 'Meadow', 3 },
{ 'Waste Ground', 4 },
{ 'Woodland Ground', 4 },
{ 'Vegetation', 10 },
{ 'Mud (wet)', 4 },
{ 'Mud (dry)', 4 },
{ 'Dirt', 4 },
{ 'Dirt Track', 4 },
{ 'Sand (deep)', 5 },
{ 'Sand (medium)', 5 },
{ 'Sand (compact)', 5 },
{ 'Sand (arid)', 5 },
{ 'Sand (more)', 5 },
{ 'Sand (beach)', 5 },
{ 'Concrete (beach)', 1 },
{ 'Rock (dry)', 9 },
{ 'Rock (wet)', 9 },
{ 'Rock (cliff)', 9 },
{ 'Water (riverbed)', 11 },
{ 'Water (shallow)', 11 },
{ 'Corn Field', 4 },
{ 'Hedge', 10 },
{ 'Wood (crates)', 7 },
{ 'Wood (solid)', 7 },
{ 'Wood (thin)', 7 },
{ 'Glass', 6 },
{ 'Glass Windows (large)', 6 },
{ 'Glass Windows (small)', 6 },
{ 'Empty1', 12 },
{ 'Empty2', 12 },
{ 'Garage Door', 8 },
{ 'Thick Metal Plate', 8 },
{ 'Scaffold Pole', 8 },
{ 'Lamp Post', 8 },
{ 'Metal Gate', 8 },
{ 'Metal Chain fence', 8 },
{ 'Girder', 8 },
{ 'Fire Hydrant', 8 },
{ 'Container', 8 },
{ 'News Vendor', 8 },
{ 'Wheelbase', 12 },
{ 'Cardboard Box', 12 },
{ 'Ped', 12 },
{ 'Car', 8 },
{ 'Car (panel)', 8 },
{ 'Car (moving component)', 8 },
{ 'Transparent Cloth', 12 },
{ 'Rubber', 12 },
{ 'Plastic', 12 },
{ 'Transparent Stone', 9 },
{ 'Wood (bench)', 7 },
{ 'Carpet', 12 },
{ 'Floorboard', 7 },
{ 'Stairs (wood)', 7 },
{ 'Sand', 5 },
{ 'Sand (dense)', 5 },
{ 'Sand (arid)', 5 },
{ 'Sand (compact)', 5 },
{ 'Sand (rocky)', 5 },
{ 'Sand (beach)', 5 },
{ 'Grass (short)', 3 },
{ 'Grass (meadow)', 3 },
{ 'Grass (dry)', 3 },
{ 'Woodland', 4 },
{ 'Wood Dense', 4 },
{ 'Roadside', 2 },
{ 'Roadside Des', 5 },
{ 'Flowerbed', 4 },
{ 'Waste Ground', 4 },
{ 'Concrete', 1 },
{ 'Office Desk', 12 },
{ '711 Shelf 1', 12 },
{ '711 Shelf 2', 12 },
{ '711 Shelf 3', 12 },
{ 'Restuarant Table', 12 },
{ 'Bar Table', 12 },
{ 'Underwater (lush)', 5 },
{ 'Underwater (barren)', 5 },
{ 'Underwater (coral)', 5 },
{ 'Underwater (deep)', 5 },
{ 'Riverbed', 4 },
{ 'Rubble', 2 },
{ 'Bedroom Floor', 12 },
{ 'Kitchen Floor', 12 },
{ 'Livingroom Floor', 12 },
{ 'corridor Floor', 12 },
{ '711 Floor', 12 },
{ 'Fast Food Floor', 12 },
{ 'Skanky Floor', 12 },
{ 'Mountain', 9 },
{ 'Marsh', 4 },
{ 'Bushy', 10 },
{ 'Bushy (mix)', 10 },
{ 'Bushy (dry)', 10 },
{ 'Bushy (mid)', 10 },
{ 'Grass (wee flowers)', 3 },
{ 'Grass (dry tall)', 3 },
{ 'Grass (lush tall)', 3 },
{ 'Grass (green mix)', 3 },
{ 'Grass (brown mix)', 3 },
{ 'Grass (low)', 3 },
{ 'Grass (rocky)', 3 },
{ 'Grass (small trees)', 3 },
{ 'Dirt (rocky)', 4 },
{ 'Dirt (weeds)', 4 },
{ 'Grass (weeds)', 3 },
{ 'River Edge', 4 },
{ 'Poolside', 1 },
{ 'Forest (stumps)', 4 },
{ 'Forest (sticks)', 4 },
{ 'Forest (leaves)', 4 },
{ 'Desert Rocks', 5 },
{ 'Forest (dry)', 4 },
{ 'Sparse Flowers', 4 },
{ 'Building Site', 2 },
{ 'Docklands', 1 },
{ 'Industrial', 1 },
{ 'Industrial Jetty', 1 },
{ 'Concrete (litter)', 1 },
{ 'Alley Rubbish', 1 },
{ 'Junkyard Piles', 2 },
{ 'Junkyard Ground', 4 },
{ 'Dump', 4 },
{ 'Cactus Dense', 5 },
{ 'Airport Ground', 1 },
{ 'Cornfield', 4 },
{ 'Grass (light)', 3 },
{ 'Grass (lighter)', 3 },
{ 'Grass (lighter 2)', 3 },
{ 'Grass (mid 1)', 3 },
{ 'Grass (mid 2)', 3 },
{ 'Grass (dark)', 3 },
{ 'Grass (dark 2)', 3 },
{ 'Grass (dirt mix)', 3 },
{ 'Riverbed (stone)', 9 },
{ 'Riverbed (shallow)', 4 },
{ 'Riverbed (weeds)', 4 },
{ 'Seaweed', 5 },
{ 'Door', 12 },
{ 'Plastic Barrier', 12 },
{ 'Park Grass', 3 },
{ 'Stairs (stone)', 9 },
{ 'Stairs (metal)', 8 },
{ 'Stairs (carpet)', 12 },
{ 'Floor (metal)', 8 },
{ 'Floor (concrete)', 1 },
{ 'Bin Bag', 12 },
{ 'Thin Metal Sheet', 8 },
{ 'Metal Barrel', 8 },
{ 'Plastic Cone', 12 },
{ 'Plastic Dumpster', 12 },
{ 'Metal Dumpster', 8 },
{ 'Wood Picket Fence', 7 },
{ 'Wood Slatted Fence', 7 },
{ 'Wood Ranch Fence', 7 },
{ 'Unbreakable Glass', 6 },
{ 'Hay Bale', 12 },
{ 'Gore', 12 },
{ 'Rail Track', 12 }
};
On the screen GUI shows actual surface (dunno if it's in this code above) and everything works fine.
You can of course change these parameters which handle the preferences to get stronger or weaker forces, or add some others, and the performance is (for me) exactly the same as without this. It's nice fun, try something like this yourself
Note that programming like this (a bit of chaos included) causes many bugs, and it's recomended to fight'em to have really good and deliberate results.