This tutorial will take you through the complete process of adding a new flying vehicle with wing mounted weapons. Step #1 Create a file in your scripts/vehicles directory called vehicle_sparrow.cs. Paste the following code in there: //************************************************************** // VEHICLE CHARACTERISTICS //************************************************************** datablock FlyingVehicleData(SparrowFlyer) : ShrikeDamageProfile { spawnOffset = "0 0 2"; catagory = "Vehicles"; shapeFile = "vehicle_air_scout.dts"; multipassenger = false; computeCRC = true; debrisShapeName = "vehicle_air_scout_debris.dts"; debris = ShapeDebris; renderWhenDestroyed = false; drag = 0.15; density = 1.0; mountPose[0] = sitting; numMountPoints = 1; isProtectedMountPoint[0] = true; cameraMaxDist = 15; cameraOffset = 2.5; cameraLag = 0.9; explosion = VehicleExplosion; explosionDamage = 0.5; explosionRadius = 5.0; maxDamage = 1.40; destroyedLevel = 1.40; isShielded = true; energyPerDamagePoint = 160; maxEnergy = 280; minDrag = 30; rotationalDrag = 900; rechargeRate = 0.8; maxAutoSpeed = 15; autoAngularForce = 400; autoLinearForce = 300; autoInputDamping = 0.95; // Maneuvering maxSteeringAngle = 5; horizontalSurfaceForce = 6; verticalSurfaceForce = 4; maneuveringForce = 3000; steeringForce = 1200; steeringRollForce = 400; rollForce = 4; hoverHeight = 5; createHoverHeight = 3; // Turbo Jet jetForce = 2000; minJetEnergy = 28; jetEnergyDrain = 2.8; vertThrustMultiple = 2.0; // Rigid body mass = 150; bodyFriction = 0; bodyRestitution = 0.5; minRollSpeed = 0; softImpactSpeed = 14; hardImpactSpeed = 25; // Ground Impact Damage (uses DamageType::Ground) minImpactSpeed = 23; speedDamageScale = 0.06; // Object Impact Damage (uses DamageType::Impact) collDamageThresholdVel = 23.0; collDamageMultiplier = 0.02; // minTrailSpeed = 15; trailEmitter = ContrailEmitter; forwardJetEmitter = FlyerJetEmitter; downJetEmitter = FlyerJetEmitter; // jetSound = ScoutFlyerThrustSound; engineSound = ScoutFlyerEngineSound; softImpactSound = SoftImpactSound; hardImpactSound = HardImpactSound; //wheelImpactSound = WheelImpactSound; // softSplashSoundVelocity = 10.0; mediumSplashSoundVelocity = 15.0; hardSplashSoundVelocity = 20.0; exitSplashSoundVelocity = 10.0; exitingWater = VehicleExitWaterMediumSound; impactWaterEasy = VehicleImpactWaterSoftSound; impactWaterMedium = VehicleImpactWaterMediumSound; impactWaterHard = VehicleImpactWaterMediumSound; waterWakeSound = VehicleWakeMediumSplashSound; dustEmitter = VehicleLiftoffDustEmitter; triggerDustHeight = 4.0; dustHeight = 1.0; damageEmitter[0] = LightDamageSmoke; damageEmitter[1] = HeavyDamageSmoke; damageEmitter[2] = DamageBubbles; damageEmitterOffset[0] = "0.0 -3.0 0.0 "; damageLevelTolerance[0] = 0.3; damageLevelTolerance[1] = 0.7; numDmgEmitterAreas = 1; // max[chaingunAmmo] = 1000; minMountDist = 4; splashEmitter[0] = VehicleFoamDropletsEmitter; splashEmitter[1] = VehicleFoamEmitter; shieldImpact = VehicleShieldImpact; cmdCategory = "Tactical"; cmdIcon = CMDFlyingScoutIcon; cmdMiniIconName = "commander/MiniIcons/com_scout_grey"; targetNameTag = 'Sparrow'; targetTypeTag = 'Flying Vehicle'; sensorData = AWACPulseSensor; sensorRadius = AWACPulseSensor.detectRadius; sensorColor = "255 194 9"; checkRadius = 5.5; observeParameters = "1 10 10"; runningLight[0] = ShrikeLight1; // runningLight[1] = ShrikeLight2; shieldEffectScale = "0.937 1.125 0.60"; }; //************************************************************** // WEAPONS //************************************************************** datablock ShapeBaseImageData(SparrowChaingunPairImage) { className = WeaponImage; shapeFile = "weapon_energy_vehicle.dts"; item = Chaingun; ammo = ChaingunAmmo; projectile = ShoulderMissile; projectileType = SeekerProjectile; mountPoint = 10; offset = "4.2 -1.6 -1.6"; usesEnergy = true; useMountEnergy = true; // DAVEG -- balancing numbers below! minEnergy = 5; fireEnergy = 5; fireTimeout = 125; stateName[0] = "Activate"; stateTransitionOnTimeout[0] = "ActivateReady"; stateTimeoutValue[0] = 0.5; stateSequence[0] = "Activate"; stateSound[0] = MissileSwitchSound; stateName[1] = "ActivateReady"; stateTransitionOnLoaded[1] = "Ready"; stateTransitionOnNoAmmo[1] = "NoAmmo"; stateName[2] = "Ready"; stateTransitionOnNoAmmo[2] = "NoAmmo"; stateTransitionOnTriggerDown[2] = "CheckTarget"; stateName[3] = "Fire"; stateTransitionOnTimeout[3] = "Reload"; stateTimeoutValue[3] = 3.0; stateFire[3] = true; stateRecoil[3] = LightRecoil; stateAllowImageChange[3] = false; stateSequence[3] = "Fire"; stateScript[3] = "onFire"; stateSound[3] = MissileFireSound; stateName[4] = "Reload"; stateTransitionOnNoAmmo[4] = "NoAmmo"; stateTransitionOnTimeout[4] = "Ready"; stateTimeoutValue[4] = 2.5; stateAllowImageChange[4] = false; stateSequence[4] = "Reload"; stateSound[4] = MissileReloadSound; stateName[5] = "NoAmmo"; stateTransitionOnAmmo[5] = "Reload"; stateSequence[5] = "NoAmmo"; stateTransitionOnTriggerDown[5] = "Fire"; // Was DryFire stateName[6] = "DryFire"; stateSound[6] = MissileDryFireSound; stateTimeoutValue[6] = 1.0; stateTransitionOnTimeout[6] = "ActivateReady"; stateName[7] = "CheckTarget"; stateTransitionOnNoTarget[7] = "Fire"; // Was DryFire stateTransitionOnTarget[7] = "Fire"; }; datablock ShapeBaseImageData(SparrowChaingunImage) : SparrowChaingunPairImage { offset = "-4.2 -1.6 -1.6"; //stateScript[3] = "onTriggerDown"; //stateScript[5] = "onTriggerUp"; //stateScript[6] = "onTriggerUp"; }; datablock ShapeBaseImageData(SparrowChaingunParam) { mountPoint = 2; shapeFile = "turret_muzzlepoint.dts"; projectile = ShoulderMissile; projectileType = SeekerProjectile; }; That defines the vehicle and the weapons. The weapons are two wing-mounted missile launchers with a long reload time. All of those datablocks are copied from the Shrike and then modified. The largest modifications are in the weapon state code which could probably be a little cleaner, but it works. Step #2 In station.cs, find the StationVehiclePad::onAdd() function. At the bottom of that function, add: if(%obj.sparrowFlyer !$= "Removed") %sv.vehicle[sparrowFlyer] = true; That sets it up so that if a vehicle is not supposed to be available then you can't buy it. Step #3 In serverVehicleHud.cs, go to the VehicleHud::updateHud function. Right after the if statment for adding a line for the Shrike Flyer, add: if ( %station.vehicle[sparrowFlyer] ) { messageClient( %client, 'SetLineHud', "", %tag, %count, "SPARROW FLIER", "", SparrowFlyer, $VehicleMax[SparrowFlyer] - $VehicleTotalCount[%team, SparrowFlyer] ); %count++; } That makes it so if a Sparrow Flyer can be bought from that station, it appears on the hud. Step #4 In vehicle.cs, find the definitions of the base vehicle onAdd functions. Add a function for the Sparrow: //---------------------------- // SPARROW FLIER //---------------------------- function SparrowFlyer::onAdd(%this, %obj) { Parent::onAdd(%this, %obj); %obj.mountImage(SparrowChaingunParam, 0); %obj.mountImage(SparrowChaingunImage, 2); %obj.mountImage(SparrowChaingunPairImage, 3); %obj.nextWeaponFire = 2; %obj.schedule(5500, "playThread", $ActivateThread, "activate"); } This mounts the weapons when a Sparrow is created. Then, find the definitions of the base vehicle playerMounted functions. Add: //---------------------------- // SPARROW FLIER //---------------------------- function SparrowFlyer::playerMounted(%data, %obj, %player, %node) { // scout flyer == SUV (single-user vehicle) commandToClient(%player.client, 'setHudMode', 'Pilot', "Shrike", %node); $numVWeapons = 1; } This makes the nice vehicle hud appear at the bottom of the screen while piloting. Step #5 Still in vehicle.cs, find the section where the $VehicleMax array is set. Add a line for the Sparrow Flyer: $VehicleMax[SparrowFlyer] = 3; Then in the clearVehicleCount function, add the following line: $VehicleTotalCount[%team, SparrowVehicle] = 0; Step #6 In weapTurretCode.cs, right before the ScoutFlyer::onTrigger function add: function SparrowFlyer::onTrigger(%data, %obj, %trigger, %state) { // data = Sparrow datablock // obj = Sparrow object number // trigger = 0 for "fire", 1 for "jump", 3 for "thrust" // state = 1 for firing, 0 for not firing if(%trigger == 0) { switch (%state) { case 0: %obj.fireWeapon = false; %obj.setImageTrigger(2, false); %obj.setImageTrigger(3, false); case 1: %obj.fireWeapon = true; if(%obj.nextWeaponFire == 2) { %obj.setImageTrigger(2, true); %obj.setImageTrigger(3, false); } else { %obj.setImageTrigger(2, false); %obj.setImageTrigger(3, true); } } } } function SparrowFlyer::playerDismounted(%data, %obj, %player) { %obj.fireWeapon = false; %obj.setImageTrigger(2, false); %obj.setImageTrigger(3, false); setTargetSensorGroup(%obj.getTarget(), %obj.team); } function SparrowChaingunImage::onFire(%data,%obj,%slot) { // obj = SparrowFlyer object number // slot = 2 %p = Parent::onFire(%data,%obj,%slot); MissileSet.add(%p); %obj.nextWeaponFire = 3; schedule(%data.fireTimeout, 0, "fireNextGun", %obj); } function SparrowChaingunPairImage::onFire(%data,%obj,%slot) { // obj = SparrowFlyer object number // slot = 3 %p = Parent::onFire(%data,%obj,%slot); MissileSet.add(%p); %obj.nextWeaponFire = 2; schedule(%data.fireTimeout, 0, "fireNextGun", %obj); } function SparrowChaingunImage::onTriggerDown(%this, %obj, %slot) { } function SparrowChaingunImage::onTriggerUp(%this, %obj, %slot) { } function SparrowChaingunImage::onMount(%this, %obj, %slot) { } function SparrowChaingunPairImage::onMount(%this, %obj, %slot) { } function SparrowChaingunImage::onUnmount(%this,%obj,%slot) { } function SparrowChaingunPairImage::onUnmount(%this,%obj,%slot) { } All of those are just copies of the corresponding Shrike functions. Step #7 In server.cs, right after this line: exec("scripts/vehicles/vehicle_mpb.cs"); Add: exec("scripts/vehicles/vehicle_sparrow.cs"); --------------------------------------------------------------------------------