object vehicle_magog_npc { void preinit(); void init() {} void destroy(); void syncFields(); // states void Deliver(); void Drop(); void Return(); // callbacks void OnSetDeploymentParms( float deploymentItemIndex, float playerIndex, vector target, float rotation ); void vSetDeploymentParms( float deploymentItemIndex, float playerIndex, vector target, float rotation ) { OnSetDeploymentParms( deploymentItemIndex, playerIndex, target, rotation ); } void ClearRequest(); void OnItemChanged(); void OnStateTimeChanged(); // utility void SetupCrate(); void SetupCommandMap(); boolean PerformDrop(); void UpdateEffects( vector pos ); // virtuals boolean vIgnoreMagogCollsion(); // threads void UpdateRotors(); void KillAllInBoundingBox(); void RopeThread(); // void CheckDeployable(); vector GetItemAttachPoint(); // deployment stuff float deployIndex; float deployPlayerIndex; entity item; vector targetPos; float itemRotation; float startTime; float dropTime; float returnTime; vector gdfCorner; vector matchPosition; // local data float maxHealth; float commandMapHandle; // rotor blades float rearUpperRotorJoint; float rearLowerRotorJoint; float leftUpperRotorJoint; float leftLowerRotorJoint; float rightUpperRotorJoint; float rightLowerRotorJoint; float bladeRotationSpeed; // rope stuff float attachJoint; float ropeJoint; float hatchJoint; float itemAttachJoint; // physics float maxZAccel; float maxZVel; boolean cancelDrop; boolean dropped; vector renderMins; vector renderMaxs; // rotor ground fx boolean groundEffects; float groundEffectsThreshhold; float lastGroundEffectsTime; entity vGetItem() { return item; } void CacheItemInfoThread(); vector cachedItemOrigin; vector cachedItemAngles; } void vehicle_magog_npc::SetupCommandMap() { commandMapHandle = sys.allocCMIcon( self, 1 ); float commandMapSize = getFloatKeyWithDefault( "icon_size_cm", 16.f ); sys.setCMIconDrawMode( commandMapHandle, DM_ROTATED_MATERIAL ); sys.setCMIconSize( commandMapHandle, commandMapSize ); sys.setCMIconColorMode( commandMapHandle, CM_ALLEGIANCE ); sys.setCMIconMaterial( commandMapHandle, GetMaterial( getKey( "mtr_commandmap" ) ) ); sys.setCMIconUnknownMaterial( commandMapHandle, GetMaterial( getKey( "mtr_commandmap_unknown" ) ) ); sys.setCMIconUnknownSize( commandMapHandle, getFloatKeyWithDefault( "icon_unknown_size_cm", commandMapSize / 2.0f ) ); sys.setCMIconFlag( commandMapHandle, CMF_FOLLOWROTATION ); } void vehicle_magog_npc::syncFields() { syncBroadcast( "item" ); syncBroadcast( "dropTime" ); syncBroadcast( "returnTime" ); syncBroadcast( "targetPos" ); syncBroadcast( "itemRotation" ); syncBroadcast( "gdfCorner" ); syncBroadcast( "startTime" ); syncBroadcast( "matchPosition" ); syncCallback( "item", "OnItemChanged" ); syncCallback( "startTime", "OnStateTimeChanged" ); syncCallback( "dropTime", "OnStateTimeChanged" ); syncCallback( "returnTime", "OnStateTimeChanged" ); } void vehicle_magog_npc::OnItemChanged() { SetupCrate(); if ( sys.isClient() ) { thread CacheItemInfoThread(); } } void vehicle_magog_npc::OnStateTimeChanged() { if ( returnTime != -1 ) { setState( "Return" ); } else if ( dropTime != -1 ) { setState( "Drop" ); } else if ( startTime != -1 ) { setState( "Deliver" ); } } void vehicle_magog_npc::preinit() { deployPlayerIndex = -1; commandMapHandle = -1; startTime = -1; dropTime = -1; returnTime = -1; maxHealth = getFloatKey( "health" ); SetupCommandMap(); rearUpperRotorJoint = getJointHandle( "rear_upper_rotor" ); rearLowerRotorJoint = getJointHandle( "rear_lower_rotor" ); leftUpperRotorJoint = getJointHandle( "front_left_upper_rotor" ); leftLowerRotorJoint = getJointHandle( "front_left_lower_rotor" ); rightUpperRotorJoint = getJointHandle( "front_right_upper_rotor" ); rightLowerRotorJoint = getJointHandle( "front_right_lower_rotor" ); bladeRotationSpeed = getFloatKey( "blade_speed" ); if ( bladeRotationSpeed <= 0 ) { bladeRotationSpeed = 1440.f; } // this is to account for rotor spooling etc maxZAccel = getFloatKey( "max_thrust" ); maxZVel = getFloatKey( "max_z_vel" ); gdfCorner = g_vectorZero; cancelDrop = false; dropped = false; // stop the comedy knock back from the deployable exploding when the magog is delivering it disableKnockback(); startSound( "snd_rotor", SND_VEHICLE_START ); attachJoint = getJointHandle( getKey( "joint_attach" ) ); ropeJoint = getJointHandle( getKey( "joint_rope" ) ); hatchJoint = getJointHandle( getKey( "joint_hatch" ) ); itemAttachJoint = INVALID_JOINT; groundEffects = false; groundEffectsThreshhold = getFloatKey( "groundeffects_threshhold" ); lastGroundEffectsTime = 0.f; setCoverage( 0.f ); thread UpdateRotors(); thread KillAllInBoundingBox(); thread RopeThread(); renderMins = getMins(); renderMaxs = getMaxs(); } void vehicle_magog_npc::ClearRequest() { if ( sys.isClient() ) { return; } if ( deployPlayerIndex != -1 ) { sys.clearDeployRequest( deployPlayerIndex ); deployPlayerIndex = -1; } } void vehicle_magog_npc::destroy() { if ( item != $null_entity && !sys.isClient() ) { item.remove(); } sys.freeCMIcon( self, commandMapHandle ); ClearRequest(); } #define HOVER_HEIGHT_MIN 400 #define HOVER_HEIGHT_AIM 2300 void vehicle_magog_npc::Deliver() { if ( sys.isClient() ) { pathFind( getKey( "path_type" ), targetPos, startTime, 1.0f, gdfCorner_x, gdfCorner_y, 0, false ); pathLevel( 20, -1, -1 ); pathStraighten(); } float numPoints = pathGetNumPoints(); if ( numPoints < 2 ) { return; } // thread CheckDeployable(); float pathSpeed = getFloatKeyWithDefault( "path_speed", 1024 ); float pathLength = pathGetLength(); vector endPoint = pathGetPoint( numPoints - 1 ); endPoint_z += HOVER_HEIGHT_MIN; vector endAngles = g_vectorZero; endAngles_y = sys.angleNormalize180( itemRotation ); // find the actual end point float fraction = 1.0f; while ( fraction == 1.0f ) { fraction = sys.traceOriented( endPoint, endPoint - '0 0 3000', renderMins, renderMaxs, endAngles, MASK_SOLID | MASK_OPAQUE, self ); endPoint = sys.getTraceEndPos(); } endPoint_z += HOVER_HEIGHT_AIM; matchPosition = endPoint; startMagogDelivery( startTime, pathSpeed, 2.0f, endPoint, itemRotation ); float coverage = 0.f; while ( true ) { if ( sys.isServer() ) { if ( item == $null_entity ) { sys.warning( "DEPLOYABLE GONE" ); } } float frameTime = sys.getFrameTime(); setCoverage( coverage ); coverage = coverage + frameTime; vector origin = getWorldOrigin(); if ( !sys.isClient() ) { float distToEnd = sys.vecLength( origin - endPoint ); if ( sys.vecLength( origin - endPoint ) < 64.0f ) { vector velocity = getLinearVelocity(); velocity_z = 0.0f; if ( sys.vecLength( velocity ) < 50.0f ) { setState( "Drop" ); } } if ( cancelDrop ) { ClearRequest(); setState( "Return" ); } } sys.waitFrame(); } } void vehicle_magog_npc::CacheItemInfoThread() { sys.threadName( "CacheItemInfoThread_" + getName() ); while ( true ) { if ( item != $null_entity ) { if ( item.isBound() ) { cachedItemOrigin = item.getWorldOrigin(); cachedItemAngles = item.getAngles(); } } sys.waitFrame(); } } void vehicle_magog_npc::Drop() { if( !sys.isClient() ) { if ( !PerformDrop() ) { // the player has switched sides or class since requesting the deployable ClearRequest(); setState( "Return" ); } } if ( !sys.isClient() ) { dropTime = sys.getTime(); } else { while ( dropTime == -1 ) { sys.waitFrame(); } } vector origin = getWorldOrigin(); vector endAngles = g_vectorZero; endAngles_y = sys.angleNormalize180( itemRotation ); if ( item.isBound() ) { cachedItemOrigin = item.getWorldOrigin(); cachedItemAngles = item.getAngles(); item.unbind(); } sys.killThread( "CacheItemInfoThread_" + getName() ); // setting this back again ensures a seamless drop for all clients item.setOrigin( cachedItemOrigin ); item.setAngles( cachedItemAngles ); item.vStartMagogDrop(); if ( item.getFloatKey( "no_drop" ) == 0.f ) { float timeToDrop = 4.0f; vector dropOrigin = item.getWorldOrigin(); vector dropDelta = targetPos - dropOrigin; vector dropAngles = item.getAngles(); vector dropAngleDelta = endAngles - dropAngles; dropAngleDelta_y = sys.angleNormalize180( dropAngleDelta_y ); float time; float doneNess; vector spot; vector angles; vector ropeModelSpot; vector hookModelSpot; // expand the bounds while dropping the item vector mins = getRenderMins(); vector maxs = getRenderMaxs(); mins_z += dropDelta_z; setRenderBounds( mins, maxs ); if ( !sys.isClient() ) { item.vStartBoundsKillThread(); } vector hookStartSpot = getJointPos( attachJoint ); vector ropeStartSpot = getJointPos( ropeJoint ); vector hookStartModelSpot = worldToModelSpace( hookStartSpot - origin ); vector ropeStartModelSpot = worldToModelSpace( ropeStartSpot - origin ); while ( true ) { time = sys.getTime() - dropTime; doneNess = time / timeToDrop; if ( doneNess > 1.0f ) { doneNess = 1.0f; } doneNess = 1 - ( sys.cos( doneNess * 180 ) + 1 ) * 0.5; spot = dropOrigin + dropDelta * doneNess; angles = dropAngles + dropAngleDelta * doneNess; item.setWorldOrigin( spot ); item.setAngles( angles ); item.setLinearVelocity( vec3_origin ); item.setAngularVelocity( vec3_origin ); // use the joint position to position the hook vector hookSpot = GetItemAttachPoint(); //item.getJointPos( itemAttachJoint ); vector ropeSpot = ( hookSpot + dropOrigin ) * 0.5f; origin = getWorldOrigin(); hookModelSpot = worldToModelSpace( hookSpot - origin ); ropeModelSpot = worldToModelSpace( ropeSpot - origin ); setJointPos( attachJoint, JOINTMOD_WORLD_OVERRIDE, hookModelSpot ); setJointPos( ropeJoint, JOINTMOD_WORLD_OVERRIDE, ropeModelSpot ); vector resultHookSpot = getJointPos( attachJoint ); sys.waitFrame(); if ( doneNess == 1.0f ) { break; } } if ( !sys.isClient() ) { ClearRequest(); if ( item != $null_entity ) { item.vOnDeploy(); item = $null_entity; } } // bring the hook back up vector hookEndSpot = getJointPos( attachJoint ); vector ropeEndSpot = getJointPos( ropeJoint ); float raiseTime = sys.getTime() + 1; while ( true ) { time = sys.getTime() - raiseTime; doneNess = time / ( timeToDrop * 0.75f ); CLAMP( doneNess, 0.0f, 1.0f ); doneNess = 1 - ( sys.cos( doneNess * 180 ) + 1 ) * 0.5; origin = getWorldOrigin(); ropeStartSpot = sys.toWorldSpace( ropeStartModelSpot, self ); hookStartSpot = sys.toWorldSpace( hookStartModelSpot, self ); ropeModelSpot = worldToModelSpace( ( ropeStartSpot - ropeEndSpot ) * doneNess + ropeEndSpot - origin ); hookModelSpot = worldToModelSpace( ( hookStartSpot - hookEndSpot ) * doneNess + hookEndSpot - origin ); setJointPos( attachJoint, JOINTMOD_WORLD_OVERRIDE, hookModelSpot ); setJointPos( ropeJoint, JOINTMOD_WORLD_OVERRIDE, ropeModelSpot ); vector resultHookSpot2 = getJointPos( attachJoint ); vector hookPooSpot = ( hookStartSpot - hookEndSpot ) * doneNess + hookEndSpot - origin; sys.waitFrame(); if ( doneNess == 1.0f ) { break; } } // restore the original render bounds mins_z -= dropDelta_z; setRenderBounds( mins, maxs ); } dropped = true; if ( !sys.isClient() ) { setState( "Return" ); } } void vehicle_magog_npc::Return() { if ( !sys.isClient() ) { returnTime = sys.getTime(); } else { while ( returnTime == -1 ) { sys.waitFrame(); } } pathFind( getKey( "path_type" ), targetPos, returnTime, -1.0f, gdfCorner_x, gdfCorner_y, 0, false ); float numPoints = pathGetNumPoints(); if ( numPoints < 2 ) { return; } pathLevel( 20, -1, -1 ); pathStraighten(); float pathSpeed = getFloatKeyWithDefault( "path_speed", 1024 ); float pathLength = pathGetLength(); vector hoverOffset = g_vectorZero; hoverOffset_z = HOVER_HEIGHT_MIN; vector endPoint = pathGetPoint( numPoints - 1 ) + hoverOffset; startMagogReturn( returnTime, pathSpeed, 2.0f, endPoint ); while ( true ) { float time = sys.getTime() - returnTime; float position = time * pathSpeed; float coverage = ( pathLength - 1 - position ) / pathSpeed; setCoverage( coverage ); if ( !sys.isClient() ) { if ( position > pathLength ) { remove(); } } sys.waitFrame(); } } void vehicle_magog_npc::UpdateRotors() { vector ang; ang_y = 0.f; while ( true ) { sys.waitFrame(); ang_y += bladeRotationSpeed * sys.getFrameTime(); setJointAngle( rearUpperRotorJoint, JOINTMOD_WORLD, ang ); setJointAngle( rearLowerRotorJoint, JOINTMOD_WORLD, ang ); setJointAngle( leftUpperRotorJoint, JOINTMOD_WORLD, ang * 0.99f ); setJointAngle( leftLowerRotorJoint, JOINTMOD_WORLD, ang * 0.99f ); setJointAngle( rightUpperRotorJoint, JOINTMOD_WORLD, ang * 1.01f ); setJointAngle( rightLowerRotorJoint, JOINTMOD_WORLD, ang * 1.01f ); vector absMins = getAbsMins(); vector traceOrg = ( absMins + getAbsMaxs() ) * 0.5f; UpdateEffects( traceOrg ); } } void vehicle_magog_npc::OnSetDeploymentParms( float deploymentItemIndex, float playerIndex, vector target, float rotation ) { deployPlayerIndex = playerIndex; targetPos = target; itemRotation = sys.angleNormalize180( rotation ); if( !PerformDrop() ) { // player has switched sides since calling in the deployable ClearRequest(); remove(); return; } player p = sys.getClient( playerIndex ); item = sys.spawnType( deploymentItemIndex ); item.vSetDeployableOwner( p ); deployable_base deployable = item; if ( deployable != $null_entity ) { p.SetTargetingItem( item ); } string statName = item.getKey( "stat_name" ); if ( statName != "" ) { sys.increaseStatInt( sys.allocStatInt( statName + "_deployed" ), deployPlayerIndex, 1 ); } SetupCrate(); // setup the delivery path startTime = sys.getTime(); gdfCorner = objManager.GetGDFBasePosition(); pathFind( getKey( "path_type" ), targetPos, startTime, 1.0f, gdfCorner_x, gdfCorner_y, 0, false ); pathLevel( 20, -1, -1 ); pathStraighten(); vector startPoint = pathGetPoint( 0 ); startPoint_z += HOVER_HEIGHT_MIN; vector startDir = pathGetDirection( 0 ); startDir = sys.vecNormalize( startDir ); vector startAngles = sys.vecToAngles( startDir ); setWorldOrigin( startPoint ); setAngles( startAngles ); setState( "Deliver" ); } void vehicle_magog_npc::SetupCrate() { if ( item == $null_entity ) { return; } vector org; itemAttachJoint = item.getJointHandle( item.getKey( "joint_attach" ) ); org = GetItemAttachPoint(); org = item.worldToModelSpace( org - item.getWorldOrigin() ); if ( item != $null_entity ) { item.setOrigin( getLocalJointPos( attachJoint ) - org ); item.bindToJoint( self, getKey( "joint_attach" ), 1 ); item.setGameTeam( getGameTeam() ); item.vSetManualDeploy(); } } vector vehicle_magog_npc::GetItemAttachPoint() { if ( item == $null_entity ) { return '0 0 0'; } vector org; if ( itemAttachJoint != -1 ) { org = item.getJointPos( itemAttachJoint ); } else { org = item.getWorldOrigin(); org_z += item.getFloatKey( "attach_height" ); } return org; } void vehicle_magog_npc::KillAllInBoundingBox() { sys.wait( 3 ); eachFrame { vector mins = getAbsMins(); vector maxs = getAbsMaxs(); // sys.debugBounds( '1 1 1', mins, maxs, 0.f ); entitiesInBounds( mins, maxs, MASK_ALL, 1 ); float count = filterEntitiesByClass( "sdTransport", 1 ); float i; for( i = 0; i < count; i++ ) { entity ent = getBoundsCacheEntity( i ); if( ent == self || ent == item || ent == $null_entity ) { continue; } if( !ent.vIgnoreMagogCollsion() ) { if ( touchesBounds( ent ) ) { ent.applyDamage( $null_entity, self, '0 1 0', GetDamage( "damage_magog_npc_collide" ), 60000.f, $null_entity ); } } } } } boolean vehicle_magog_npc::vIgnoreMagogCollsion() { return true; } #define MAGOC_NPC_SWINGLENGTHSCALE 2 #define MAGOC_NPC_SWINGMASS 1000 #define MAGOC_NPC_SWINGMASSEMPTY 1000 #define MAGOC_NPC_SWINGSCALE 75 #define MAGOC_NPC_SWINGSCALEEMPTY 75 #define MAGOC_NPC_SWINGFRICTION 1 void vehicle_magog_npc::RopeThread() { vector dist = getLocalJointPos( hatchJoint ) - getLocalJointPos( ropeJoint ); vector pos = dist; float length = sys.vecLength( dist ); vector momentum = g_vectorZero; // sys.print( "dist:" + dist + "\n" ); while ( true ) { sys.waitFrame(); float mass = MAGOC_NPC_SWINGMASS; float swingScale = MAGOC_NPC_SWINGSCALE; if ( item == $null_entity ) { mass = MAGOC_NPC_SWINGMASSEMPTY; swingScale = MAGOC_NPC_SWINGSCALEEMPTY; } float inverseMass = 1 / mass; float gravity = 1066 * mass; float len = length * MAGOC_NPC_SWINGLENGTHSCALE; len = len * len; float frameTime = sys.getFrameTime(); vector selfV = getLinearVelocity(); selfV_z = 0; vector force = selfV * swingScale; force_z += gravity; vector path = sys.vecNormalize( pos ); path = path * ( -force_z / path_z ); force += path; momentum += ( ( force - ( momentum * MAGOC_NPC_SWINGFRICTION ) ) * frameTime ); vector v = momentum * inverseMass; pos_x = pos_x + ( v_x * frameTime ); pos_y = pos_y + ( v_y * frameTime ); pos_z = sys.sqrt( len - ( ( pos_x * pos_x ) + ( pos_y * pos_y ) ) ); // sys.print( "pos_x: " + pos_x + "\n" ); // sys.print( "v: " + v + "\n" ); vector modelPos = worldToModelSpace( pos ); setJointPos( ropeJoint, JOINTMOD_WORLD_OVERRIDE, getLocalJointPos( hatchJoint ) - modelPos ); modelPos = sys.vecNormalize( modelPos ); vector forward = '1 0 0' - ( modelPos_x * modelPos ); forward = sys.vecNormalize( forward ); forward = sys.vecToAngles( forward ); // objectStable = sys.vecLength( v ) < 0.5f && sys.fabs( pos_z - ( length * MAGOC_NPC_SWINGLENGTHSCALE ) ) < 0.5f; setJointAngle( attachJoint, JOINTMOD_WORLD, forward ); // sys.debugLine( g_colorRed, getJointPos( hatchJoint ), getJointPos( hatchJoint ) - pos, 0.f ); } } boolean vehicle_magog_npc::PerformDrop() { // special cases if ( deployPlayerIndex == -1 ) { return true; } if ( item != $null_entity ) { if ( item.getIntKey( "always_drop" ) == 1 ) { return true; } } player p = sys.getClient( deployPlayerIndex ); if ( getEntityAllegiance( p ) != TA_FRIEND ) { return false; } if( sys.getTerritoryForPoint( targetPos, getGameTeam(), 1.f, 0.f ) == $null_entity ) { return false; } return true; } void vehicle_magog_npc::vCancelDeployForPlayer( float playerIndex ) { if ( playerIndex != deployPlayerIndex ) { return; } // deployables already gone if ( dropped ) { return; } player p = sys.getClient( deployPlayerIndex ); objManager.PlaySoundForPlayer( getKey( "snd_cancel_deploy" ), p ); ClearRequest(); if ( !sys.isClient() ) { setState( "Return" ); } } void vehicle_magog_npc::UpdateEffects( vector pos ) { groundEffects = true;//( absMins.z - traceObject.endpos.z ) < groundEffectsThreshhold; vector end = pos; end_z -= groundEffectsThreshhold; sys.tracePoint( pos, end, MASK_SOLID | MASK_OPAQUE, self ); if ( sys.getTime() >= ( lastGroundEffectsTime + 0.1f ) && groundEffects && sys.getTraceFraction() < 1.0f ) { string surfaceTypeName = sys.getTraceSurfaceType(); playOriginEffect( "fx_groundeffect", surfaceTypeName, sys.getTraceEndPos(), sys.getTraceNormal(), 0 ); lastGroundEffectsTime = sys.getTime(); } }