/*********************************************************************** projectile_teleporter ***********************************************************************/ object projectile_teleporter : projectile_missile { void syncFields(); void init(); void preinit(); void destroy(); void DoExplodeEffect( entity collisionEnt ); void vActivateTeleportation(); void vCancelTeleportation(); float vGetPliersProgressBarValue( float action ); boolean vCheckActionCode( entity p, float actionCode ); void vArm( entity p ); void DoTeleportation(); float OnActivate( entity p, float distance ); float GetActivateCode( entity p, float distance ); float OnUpdateCrosshairInfo( entity p ); void SetupCommandmapIcon(); void ClearCommandmapIcon(); void OnOwnerChanged(); void DropOwnerItems(); player owner; void vSetOwner( entity other ) { owner = other; OnOwnerChanged(); } entity vGetOwner() { return owner; } void OwnerCheck(); float SweepCheck( vector worldOrigin, float radius, float startAngle, float angleIncrement, vector ownerMins, vector ownerMaxs ); void FindPosition( boolean sweepRange ); void SetupContents(); vector lastValidPosition; vector portPosition; float portTime; float failedPortTime; void OnPortPositionChanged(); void OnFailedPortChanged(); float disarmCurrent; float disarmMaxCount; float commandmapIcon; float destroyedToolTip; float chargePerUse; void vSetCharge( float amount ) { chargePerUse = amount; } float useMeToolTip1; float useMeToolTip2; float badLocationToolTip; boolean doingTeleport; boolean vIsTeleporting() { return doingTeleport; } boolean localIsOwner; } #define BAD_PORT_POSITION '-999999 -999999 -999999' void projectile_teleporter::init() { setNetworkSynced( true ); SetupContents(); lastValidPosition = getWorldOrigin(); failedPortTime = 0; setState( "Idle" ); } void projectile_teleporter::preinit() { commandmapIcon = -1; disarmMaxCount = getFloatKeyWithDefault( "disarm_count", 20 ); destroyedToolTip = GetToolTip( getKey( "tt_destroyed" ) ); useMeToolTip1 = GetToolTip( getKey( "tt_useMeToolTip1" ) ); useMeToolTip2 = GetToolTip( getKey( "tt_useMeToolTip2" ) ); badLocationToolTip = GetToolTip( getKey( "tt_badLocation" ) ); // crazy number it would never reach (heh, hopefully!) portPosition = BAD_PORT_POSITION; thread OwnerCheck(); } void projectile_teleporter::destroy() { ClearCommandmapIcon(); if ( owner != $null_entity ) { if ( !sys.isClient() ) { owner.binRemove( self ); owner.setTeleporterState( true ); sys.broadcastToolTip( destroyedToolTip, owner, wstr_empty, wstr_empty, wstr_empty, wstr_empty ); } if ( owner == sys.getLocalPlayer() ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.teleporterActive", 0 ); } } } void projectile_teleporter::syncFields() { sync( "disarmCurrent" ); syncBroadcast( "owner" ); syncBroadcast( "lastValidPosition" ); syncBroadcast( "portTime" ); syncBroadcast( "failedPortTime" ); syncBroadcast( "portPosition" ); syncBroadcast( "chargePerUse" ); syncCallback( "owner", "OnOwnerChanged" ); syncCallback( "portPosition", "OnPortPositionChanged" ); syncCallback( "failedPortTime", "OnFailedPortChanged" ); } void projectile_teleporter::SetupContents() { setContents( 0 ); setClipmask( MASK_PROJECTILE | CONTENTS_BODY | CONTENTS_SLIDEMOVER ); } void projectile_teleporter::OnFailedPortChanged() { if ( failedPortTime > 0 ) { // invalid position, play a sound to indicate that if ( owner == sys.getLocalPlayer() ) { sys.startSoundDirect( getKey( "snd_invalid" ), SND_WEAPON_FIRE_LOCAL ); if ( !owner.isToolTipPlaying() ) { owner.sendToolTip( badLocationToolTip ); } } } } void projectile_teleporter::DoExplodeEffect( entity collisionEnt ) { } void projectile_teleporter::DropOwnerItems() { if ( owner == $null_entity ) { return; } float count = entitiesOfCollection( "carryables" ); float i; for ( i = 0; i < count; i++ ) { carryable_item other = getBoundsCacheEntity( i ); if ( other == $null_entity ) { continue; } if ( other.carrier != owner ) { continue; } other.Drop(); } } void projectile_teleporter::DoTeleportation() { // upright the model, and make it reserve the space the player // will need when he ports in, so things can't get in the way during the process // I don't like snapping the origin & angles like this :/ setContents( CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY ); if ( !sys.isClient() ) { owner.setTeleporterState( true ); setOrigin( portPosition ); setAngles( '0 0 0' ); setBoxClipModel( owner.getMins(), owner.getMaxs(), 10000.0f ); freeze( 1 ); } doingTeleport = true; vector pos = portPosition; vector angles = owner.getViewAngles(); angles_x = angles_z = 0; vector forward = sys.angToForward( angles ); // Play effects both for fading out at our current position and fading in at the destination player p = owner; boolean doneFX = false; if ( p != $null_entity ) { if ( p.AI_CROUCH ) { sys.playWorldEffect( getKey( "fx_teleport_start_crouch" ), '1 1 1', owner.getWorldOrigin(), forward ); sys.playWorldEffect( getKey( "fx_teleport_end_crouch" ), '1 1 1', pos, forward ); doneFX = true; } else if ( p.AI_PRONE ) { sys.playWorldEffect( getKey( "fx_teleport_start_prone" ), '1 1 1', owner.getWorldOrigin(), forward ); sys.playWorldEffect( getKey( "fx_teleport_end_prone" ), '1 1 1', pos, forward ); doneFX = true; } } if ( !doneFX ) { sys.playWorldEffect( getKey( "fx_teleport_start" ), '1 1 1', owner.getWorldOrigin(), forward ); sys.playWorldEffect( getKey( "fx_teleport_end" ), '1 1 1', pos, forward ); } // Wait half a sec before teleporting // And then another half a sec before showing the player again // so there is some time to have fancy effects hide(); owner.freeze( 1 ); float portEndTime = portTime + 0.5f; if ( portEndTime > sys.getTime() ) { sys.wait( portEndTime - sys.getTime() ); } owner.hide(); float contents; if ( !sys.isClient() ) { forceDisableClip(); DropOwnerItems(); owner.setWorldOrigin( pos ); owner.setLinearVelocity( g_vectorZero ); // check to see if the player is now embedded in something contents = sys.checkContents( pos, owner.getMins(), owner.getMaxs(), MASK_PLAYERSOLID, owner ); if ( contents ) { // kill the owner owner.applyDamage( self, owner, g_vectorZero, GetDamage( getKey( "dmg_blocked" ) ), 1.0f, $null_entity ); } } if ( owner.getHealth() > 0 ) { portEndTime = portTime + 1.f; if ( portEndTime > sys.getTime() ) { sys.wait( portEndTime - sys.getTime() ); } } owner.show(); owner.freeze( 0 ); if ( !sys.isClient() ) { // double-check to see if the player is now embedded in something contents = sys.checkContents( pos, owner.getMins(), owner.getMaxs(), MASK_PLAYERSOLID, owner ); if ( contents ) { // kill the owner owner.applyDamage( self, owner, g_vectorZero, GetDamage( getKey( "dmg_blocked" ) ), 1.0f, $null_entity ); } owner.binRemove( self ); owner = $null_entity; OnOwnerChanged(); remove(); } } void projectile_teleporter::SetupCommandmapIcon() { ClearCommandmapIcon(); commandmapIcon = sys.allocCMIcon( self, getFloatKey( "icon_sort_cm" ) ); sys.setCMIconMaterial( commandmapIcon, GetMaterial( getKey( "mtr_icon" ) ) ); sys.setCMIconFlag( commandmapIcon, CMF_ALWAYSKNOWN ); sys.setCMIconSize( commandmapIcon, getFloatKey( "icon_size_cm" ) ); } void projectile_teleporter::ClearCommandmapIcon() { if ( commandmapIcon != -1 ) { sys.freeCMIcon( self, commandmapIcon ); commandmapIcon = -1; } } void projectile_teleporter::OnOwnerChanged() { if ( owner == sys.getLocalPlayer() ) { SetupCommandmapIcon(); localIsOwner = true; sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.teleporterActive", 1 ); } else { ClearCommandmapIcon(); if ( localIsOwner ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.teleporterActive", 0 ); } } } void projectile_teleporter::OnPortPositionChanged() { if ( portPosition != BAD_PORT_POSITION ) { if ( owner != $null_entity ) { owner.teleportSucceeded = true; } owner.EnergyBar_Remove( chargePerUse ); thread DoTeleportation(); } else { if ( !sys.isClient() ) { // communicates failure to the client failedPortTime = sys.getTime(); OnFailedPortChanged(); } } } void projectile_teleporter::vActivateTeleportation() { if ( !sys.isClient() ) { // do a more thorough check to see if theres a position we can spawn to FindPosition( true ); // ensure that the last valid position is still clear float contents = sys.checkContents( lastValidPosition, owner.getMins(), owner.getMaxs(), MASK_PLAYERSOLID, self ); if ( !contents ) { portPosition = lastValidPosition; } else { portPosition = BAD_PORT_POSITION; } portTime = sys.getTime(); OnPortPositionChanged(); } } void projectile_teleporter::vCancelTeleportation() { if ( !doingTeleport && !sys.isClient() ) { owner.binRemove( self ); owner = $null_entity; OnOwnerChanged(); remove(); } } float projectile_teleporter::SweepCheck( vector worldOrigin, float radius, float startAngle, float angleIncrement, vector ownerMins, vector ownerMaxs ) { float angle = startAngle; float contents = 1; vector testOrigin; vector testAngles; vector offset; while ( contents != 0 && angle < 360.0f ) { testAngles = g_vectorZero; testAngles_y = angle; offset = radius * 0.5f * sys.angToForward( testAngles ); testOrigin = worldOrigin + offset; contents = sys.checkContents( testOrigin, ownerMins, ownerMaxs, MASK_PLAYERSOLID, self ); if ( !contents ) { lastValidPosition = testOrigin; } angle += angleIncrement; } return contents; } void projectile_teleporter::FindPosition( boolean sweepRange ) { if ( owner == $null_entity ) { return; } vector ownerMins = owner.getMins(); vector ownerMaxs = owner.getMaxs(); vector worldOrigin = getWorldOrigin(); // do a "sweep around the entity" check // checks at a few different positions to find a nearby empty point vector myMins = owner.getMins(); vector myMaxs = owner.getMaxs(); vector mySize = myMaxs - myMins; mySize_z = 0.0f; float myDiagSize = sys.vecLength( mySize ); // rough check the centre float contents = sys.checkContents( worldOrigin, ownerMins, ownerMaxs, MASK_PLAYERSOLID, self ); if ( !contents ) { lastValidPosition = worldOrigin; } else { contents = SweepCheck( worldOrigin, myDiagSize, 0.0f, 120.0f, ownerMins, ownerMaxs ); if ( contents && sweepRange ) { contents = SweepCheck( worldOrigin, myDiagSize, 0.0f, 120.0f, ownerMins, ownerMaxs ); if ( contents ) { contents = SweepCheck( worldOrigin, myDiagSize, 60.0f, 120.0f, ownerMins, ownerMaxs ); if ( contents ) { contents = SweepCheck( worldOrigin, myDiagSize, 30.0f, 120.0f, ownerMins, ownerMaxs ); if ( contents ) { contents = SweepCheck( worldOrigin, myDiagSize, 90.0f, 120.0f, ownerMins, ownerMaxs ); } } } } } } void projectile_teleporter::OwnerCheck() { if ( !sys.isClient() ) { while ( owner == $null_entity ) { sys.waitFrame(); } owner.setTeleporterState( false ); while ( true ) { // // Check if a player can be spawned here // vector velocity = getLinearVelocity(); float velSqr = sys.vecLengthSquared( velocity ); if ( velSqr > 1500.0f ) { FindPosition( false ); } if ( velSqr < 10.f ) { if ( !noTrail ) { stopEffect( "fx_trail" ); noTrail = true; } } // if the position is too far from the actual position of the object, ignore it vector myOrigin = getWorldOrigin(); float distSq = sys.vecLengthSquared( myOrigin - lastValidPosition ); if ( distSq > 60.0f*60.0f ) { lastValidPosition = BAD_PORT_POSITION; } // // Check if the owner is still alive // if ( owner == $null_entity ) { remove(); } else { if ( owner.getHealth() <= 0.0f ) { remove(); } } sys.waitFrame(); } } } float projectile_teleporter::OnActivate( entity p, float distance ) { float code = GetActivateCode( p, distance ); if ( code == AK_NONE ) { return 0.f; } p.vSelectActionItem( code ); return 1.f; } float projectile_teleporter::GetActivateCode( entity p, float distance ) { if ( p.getViewingEntity() != p || distance > DISTANCE_FOR_ACTION ) { return AK_NONE; } if ( p.getHealth() <= 0 ) { return AK_NONE; } float allegiance = getEntityAllegiance( p ); if ( allegiance == TA_ENEMY ) { return AK_ARM; } return AK_NONE; } float projectile_teleporter::OnUpdateCrosshairInfo( entity p ) { if ( !sys.doClientSideStuff() ) { return 1.f; } float allegiance = getEntityAllegiance( p ); vector color = GetAllegianceColor( allegiance ); float distance = chGetDistance(); float range = InchesToMetres( distance ); chSetNumLines( 0 ); float index; if ( p != $null_entity ) { // see if theres a valid action to perform float code = GetActivateCode( p, distance ); if ( code != AK_NONE && p.vHasActionItem( code ) ) { index = chAddLine(); chSetLineMaterial( index, p.vGetActionIcon( code ) ); chSetLineType( index, CI_IMAGE ); chSetLineSize( index, 64, 64 ); chSetLineColor( index, g_colorWhite, 0.9f ); if ( p.isLocalPlayer() ) { if ( !p.isToolTipPlaying() ) { if ( sys.getTime() - p.getCrosshairStartTime() > 1.f ) { if ( p.getCurrentWeapon() != p.vGetActionItem( code ) ) { p.sendToolTip( useMeToolTip1 ); } else { p.sendToolTip( useMeToolTip2 ); } } } } } } index = chAddLine(); chSetLineTextIndex( index, g_locStr_TeleportBeacon ); chSetLineColor( index, color, 1.f ); chSetLineType( index, CI_TEXT ); chSetLineSize( index, 0, 0 ); if ( range <= 100 ) { index = chAddLine(); chSetLineText( index, G_BuildRangeStr( range ) ); chSetLineColor( index, color, 1.f ); chSetLineType( index, CI_TEXT ); chSetLineSize( index, 0, 0 ); } return 1.f; } float projectile_teleporter::vGetPliersProgressBarValue( float action ) { if ( action == AC_DISARM ) { return disarmCurrent / disarmMaxCount; } return 0.f; } boolean projectile_teleporter::vCheckActionCode( entity p, float actionCode ) { if ( actionCode == AC_DISARM ) { if ( getEntityAllegiance( p ) != TA_ENEMY ) { return false; } return disarmCurrent < disarmMaxCount; } return false; } void projectile_teleporter::vArm( entity p ) { float count = 1; team_base team = p.getGameTeam(); if ( team.HasDisarmBonus( p ) ) { count = count * 1.25f; } disarmCurrent = disarmCurrent + count; if ( sys.isClient() ) { return; } if ( disarmCurrent >= disarmMaxCount ) { if ( owner != $null_entity ) { if ( !sys.isClient() ) { sys.broadcastToolTip( destroyedToolTip, owner, wstr_empty, wstr_empty, wstr_empty, wstr_empty ); } } remove(); } }