object supply_crate { void preinit(); void syncFields(); void init(); void destroy(); float OnUpdateCrosshairInfo( entity p ); void OnKilled( entity inflictor, entity attacker, float damage, vector direction, float location ); void vSetDeployableOwner( entity p ); void OnOwnerChanged(); void vOnDeploy(); void OnDeployedChanged() { vOnDeploy(); } void Idle(); void Supply(); void LogLifeTime(); void SetupCommandMap(); void UpdateGUI(); void Fizzle(); void RemoveThread( float removeTime ); void vRemoveObject() { Fizzle(); } boolean vIsSupplyDrop() { return true; } float vGetSupplyDropCreationTime(); boolean vBlockVehicleSpawn() { return true; } void vOnBindMasterDestroyed(); void BoundsKillThread(); void DropSoundThread(); void HideParachute(); void OnFizzleChanged(); void OnSupplyCountChanged(); void vBindToEntity( entity ent ); boolean vDropItemTrace( vector tStart, vector tEnd, entity passEntity, vector targetPos ); boolean ValidDropTrace( vector originalPos ); boolean vAllowDrop(); float GetSupplyCrateIndex(); float GetSupplyCrateCount(); float supplyCount; float supplyMax; float lastSupplyCount; float creationTime; float repairRange; float commandMapHandle; float commandMapHandleEnemy; string packageName; player owner; entity vGetOwner() { return owner; } handle objectName; float usedProficiency; handle statUsed; handle statTimeUsed; handle statNumDestroyed; boolean removeThreadActive; boolean deployed; boolean fizzle; entity bindEntity; boolean allowDrop; } void supply_crate::syncFields() { syncBroadcast( "supplyCount" ); syncBroadcast( "owner" ); syncBroadcast( "deployed" ); syncBroadcast( "fizzle" ); syncCallback( "owner", "OnOwnerChanged" ); syncCallback( "deployed", "OnDeployedChanged" ); syncCallback( "fizzle", "OnFizzleChanged" ); syncCallback( "supplyCount", "OnSupplyCountChanged" ); } void supply_crate::preinit() { repairRange = MetresToInches( getFloatKeyWithDefault( "repair_range", 2.5f ) ); packageName = getKey( "pck_items" ); supplyMax = getFloatKeyWithDefault( "max_supply", 50.f ); supplyCount = supplyMax; lastSupplyCount = supplyMax; objectName = sys.localizeString( getKey( "object_name" ) ); usedProficiency = GetProficiency( getKey( "prof_used" ) ); creationTime = sys.getTime(); commandMapHandle = -1; commandMapHandleEnemy = -1; statUsed = -1; statTimeUsed = -1; statNumDestroyed = -1; string statName = getKey( "stat_name" ); if ( statName != "" ) { statUsed = sys.allocStatInt( statName + "_uses" ); statTimeUsed = sys.allocStatInt( statName + "_time_used" ); statNumDestroyed = sys.allocStatInt( statName + "_killed" ); } creationTime = sys.getTime(); setContents( CONTENTS_RENDERMODEL | CONTENTS_PLAYERCLIP | CONTENTS_VEHICLECLIP | CONTENTS_FLYERHIVECLIP ); } void supply_crate::init() { SetupCommandMap(); if ( !sys.isClient() ) { owner.binAdd( self ); player p = owner; if ( p != $null_entity ) { p.setPlayerSupplyCrateState( self, false ); } } setState( "Idle" ); } void supply_crate::destroy() { sys.freeCMIcon( self, commandMapHandle ); sys.freeCMIcon( self, commandMapHandleEnemy ); LogLifeTime(); sys.killThread( "RemoveThread_" + getName() ); if ( !sys.isClient() ) { player p = owner; if ( p != $null_entity ) { p.setPlayerSupplyCrateState( self, true ); } } if( owner == sys.getLocalPlayer() ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateHealth0", 0 ); sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateSupplies0", 0 ); float count = GetSupplyCrateCount(); if ( count < 2 ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateHealth1", 0 ); sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateSupplies1", 0 ); } } if ( owner != $null_entity ) { owner.binRemove( self ); } } void supply_crate::vOnDeploy() { if ( !sys.isClient() ) { deployed = true; G_GiveDeployBonus( owner, self ); } float hitGroundEffect = getFloatKey( "play_hitground_effect" ); if ( hitGroundEffect ) { startSound( "snd_drop", SND_DEPLOYABLE_IDLE ); playEffect( "fx_hitground", "origin", false ); } } void supply_crate::LogLifeTime() { if ( owner == $null_entity ) { return; } float t = sys.getTime() - creationTime; sys.increaseStatInt( statTimeUsed, owner.getEntityNumber(), t ); } void supply_crate::Idle() { if ( sys.isClient() ) { return; } sys.wait( 0.1f ); thread BoundsKillThread(); while ( true ) { if ( isAtRest() ) { if ( bindEntity != $null_entity ) { if ( !sys.isClient() ) { bind( bindEntity ); } } if ( !removeThreadActive ) { thread DropSoundThread(); } break; } sys.waitFrame(); } sys.killThread( "BoundsKillThread_" + getName() ); while ( true ) { sys.wait( 1.f ); if ( !removeThreadActive ) { Supply(); } } } void supply_crate::Supply() { vector mins, maxs; mins = getAbsMins() - '32 32 0'; maxs = getAbsMaxs() + '32 32 8'; entitiesInBounds( mins, maxs, CONTENTS_BODY | CONTENTS_SLIDEMOVER, 1.0f ); filterEntitiesByClass( "idPlayer", 1 ); float count = filterEntitiesByAllegiance( TA_FLAG_FRIEND, 1 ); float i; for ( i = 0; i < count; i++ ) { entity p = getBoundsCacheEntity( i ); if ( p.getHealth() <= 0 ) { continue; } if ( p.givePackage( packageName ) ) { supplyCount = supplyCount - 1; OnSupplyCountChanged(); if ( owner != $null_entity ) { if ( owner != p ) { if ( usedProficiency != -1 ) { owner.giveProficiency( usedProficiency, 1.f, $null, "supply crate used" ); } sys.increaseStatInt( statUsed, owner.getEntityNumber(), 1 ); } } if ( supplyCount == 0 ) { vRemoveObject(); } } } } void supply_crate::SetupCommandMap() { commandMapHandle = sys.allocCMIcon( self, getFloatKey( "icon_sort_cm" ) ); 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.setCMIconFlag( commandMapHandle, CMF_ALWAYSKNOWN | CMF_TEAMONLY ); commandMapHandleEnemy = sys.allocCMIcon( self, getFloatKey( "icon_sort_cm" ) ); sys.setCMIconDrawMode( commandMapHandleEnemy, DM_ROTATED_MATERIAL ); sys.setCMIconSize( commandMapHandleEnemy, commandMapSize ); sys.setCMIconColorMode( commandMapHandleEnemy, CM_ALLEGIANCE ); sys.setCMIconMaterial( commandMapHandleEnemy, GetMaterial( getKey( "mtr_commandmap" ) ) ); sys.setCMIconFlag( commandMapHandleEnemy, CMF_ENEMYONLY ); } float supply_crate::OnUpdateCrosshairInfo( entity p ) { if ( !sys.doClientSideStuff() ) { return 1.f; } float allegiance = getEntityAllegiance( p ); vector color = GetAllegianceColor( allegiance ); chSetNumLines( 0 ); float index; index = chAddLine(); chSetLineColor( index, color, 1.f ); chSetLineTextIndex( index, objectName ); chSetLineType( index, CI_TEXT ); chSetLineSize( index, 0, 0 ); index = chAddLine(); chSetLineColor( index, color, 0.5f ); chSetLineType( index, CI_BAR ); chSetLineFraction( index, getHealth() / getMaxHealth() ); chSetLineSize( index, 100, CROSSHAIR_INFO_BAR_HEIGHT ); if ( allegiance == TA_FRIEND ) { index = chAddLine(); chSetLineTextIndex( index, g_locStr_Supplies ); chSetLineColor( index, color, 1.f ); chSetLineType( index, CI_TEXT ); chSetLineSize( index, 0, 0 ); index = chAddLine(); chSetLineColor( index, color, 0.5f ); chSetLineType( index, CI_BAR ); chSetLineFraction( index, supplyCount / supplyMax ); chSetLineSize( index, 100, CROSSHAIR_INFO_BAR_HEIGHT ); } return 1.f; } void supply_crate::OnKilled( entity inflictor, entity attacker, float damage, vector direction, float location ) { if ( !sys.isClient() ) { player attackPlayer = attacker; if ( attackPlayer != $null_entity ) { if ( getEntityAllegiance( inflictor ) == TA_ENEMY ) { sys.increaseStatInt( statNumDestroyed, attackPlayer.getEntityNumber(), 1 ); } } Fizzle(); entity next; entity current; for ( current = getNextTeamEntity(); current != $null_entity; current = next ) { next = current.getNextTeamEntity(); current.vOnBindMasterDestroyed(); } } } void supply_crate::vSetDeployableOwner( entity p ) { owner = p; OnOwnerChanged(); } void supply_crate::UpdateGUI() { sys.threadName( "UpdateGUI_" + getName() ); while( true ) { if ( !removeThreadActive ) { float index = GetSupplyCrateIndex(); if ( index == 0 ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateHealth0", getHealth() / getMaxHealth() ); sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateSupplies0", supplyCount / supplyMax ); if ( GetSupplyCrateCount() == 1 ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateHealth1", 0 ); sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateSupplies1", 0 ); } } else if ( index == 1 ) { sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateHealth1", getHealth() / getMaxHealth() ); sys.setGUIFloat( GUI_GLOBALS_HANDLE, "gameHud.supplyCrateSupplies1", supplyCount / supplyMax ); } } sys.waitFrame(); } } void supply_crate::OnOwnerChanged() { if ( owner == sys.getLocalPlayer() ) { sys.killThread( "UpdateGUI_" + getName() ); thread UpdateGUI(); } } void supply_crate::BoundsKillThread() { float damageIndex = GetDamage( getKey( "dmg_crush" ) ); while ( true ) { vector mins = getAbsMins(); vector maxs = getAbsMaxs(); mins += '8 8 -32'; maxs -= '8 8 0'; float count = entitiesInBounds( mins, maxs, MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, 1 ); float index; for ( index = 0; index < count; index++ ) { entity other = getBoundsCacheEntity( index ); if ( other == self ) { continue; } if ( other != $null_entity ) { other.applyDamage( $null_entity, self, '0 0 -1', damageIndex, 1.f, $null_entity ); } vehicle_base v = other; if ( v != $null_entity ) { vRemoveObject(); } } if ( removeThreadActive ) { return; } sys.waitFrame(); } } void supply_crate::Fizzle() { if ( removeThreadActive ) { return; } startSound( "snd_drop", SND_DEPLOYABLE ); playEffect( "fx_fizzle", "", 0 ); HideParachute(); hide(); disablePhysics(); forceDisableClip(); removeThreadActive = true; fizzle = true; if ( !sys.isClient() ) { thread RemoveThread( 2.0 ); } } void supply_crate::RemoveThread( float removeTime ) { sys.wait( removeTime ); remove(); } float supply_crate::vGetSupplyDropCreationTime() { return creationTime; } void supply_crate::DropSoundThread() { startSound( "snd_drop", SND_DEPLOYABLE ); } void supply_crate::HideParachute() { if ( !sys.isClient() ) { float index; float count = entitiesOfCollection( "parachutes" ); for ( index = 0; index < count; index++ ) { entity other = getBoundsCacheEntity( index ); if ( other.vGetOwner() == self ) { other.vCancel(); return; } } } } void supply_crate::OnFizzleChanged() { if ( sys.isClient() ) { Fizzle(); } } void supply_crate::OnSupplyCountChanged() { entity p = sys.getLocalPlayer(); if ( p == $null_entity ) { return; } if ( supplyCount < lastSupplyCount ) { startSound( "snd_supply", SND_DEPLOYABLE_IDLE ); } lastSupplyCount = supplyCount; } void supply_crate::vOnBindMasterDestroyed() { if ( !sys.isClient() ) { unbind(); Fizzle(); } } void supply_crate::vBindToEntity( entity ent ) { bindEntity = ent; } boolean supply_crate::vDropItemTrace( vector tStart, vector tEnd, entity passEntity, vector targetPos ) { allowDrop = true; float frac = sys.tracePoint( tStart, tEnd, CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); vector pos = sys.getTraceEndPos() + '0 0 1'; // just a tiny bit off of ground as it might not be flat if ( frac == 1.0f || sys.getTraceNormal() * vec3_up < 0.99f ) { if ( sys.fabs( pos_z - targetPos_z ) > 128 ) { allowDrop = false; } return true; } // try to avoid clipping into buildings if ( !sys.checkContents( pos, getMins(), getMaxs(), CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ) ) { if ( sys.fabs( pos_z - targetPos_z ) > 128 ) { allowDrop = false; } return true; } vector size = getSize(); pos_x = pos_x + size_x / 2; pos_y = pos_y + size_y / 2; vector cratePos; cratePos_x = pos_x - size_x; cratePos_y = pos_y - size_y; cratePos_z = pos_z; sys.trace( cratePos + '0 0 2048', cratePos + '0 0 -128', getMins(), getMaxs(), CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); if ( ValidDropTrace( cratePos ) ) { return true; } cratePos_x = pos_x; cratePos_y = pos_y - size_y; sys.trace( cratePos + '0 0 2048', cratePos + '0 0 -128', getMins(), getMaxs(), CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); if ( ValidDropTrace( cratePos ) ) { return true; } cratePos_x = pos_x - size_x; cratePos_y = pos_y; sys.trace( cratePos + '0 0 2048', cratePos + '0 0 -128', getMins(), getMaxs(), CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); if ( ValidDropTrace( cratePos ) ) { return true; } cratePos_x = pos_x; cratePos_y = pos_y; sys.trace( cratePos + '0 0 2048', cratePos + '0 0 -128', getMins(), getMaxs(), CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); if ( ValidDropTrace( cratePos ) ) { return true; } sys.tracePoint( tStart, tEnd, CONTENTS_SOLID | CONTENTS_MOVEABLECLIP | CONTENTS_PLAYERCLIP, passEntity ); if ( sys.fabs( pos_z - targetPos_z ) > 128 ) { allowDrop = false; } return true; } boolean supply_crate::ValidDropTrace( vector originalPos ) { vector pos = sys.getTraceEndPos(); if ( sys.fabs( pos_z - originalPos_z ) > 4 ) { return false; } if ( sys.getTraceFraction() == 1.0f || ( sys.getTraceNormal() * vec3_up < 0.99f ) || ( sys.getTraceSurfaceFlags() & SURF_NOIMPACT ) || ( sys.getTraceEntity() != $null_entity && sys.getTraceEntity() != sys.getEntity( "worldspawn" ) ) ) { return false; } return true; } float supply_crate::GetSupplyCrateIndex() { if ( owner == $null_entity ) { return -1; } float i; float num = owner.binGetSize(); float index = 0; for ( i = 0; i < num; i++ ) { entity other = owner.binGet( i ); if ( other.inCollection( "supply_crate" ) ) { if ( other == self ) { return index; } else { index++; } } } return -1; } float supply_crate::GetSupplyCrateCount() { if ( owner == $null_entity ) { return 0; } float i; float num = owner.binGetSize(); float count = 0; for ( i = 0; i < num; i++ ) { entity other = owner.binGet( i ); if ( other.inCollection( "supply_crate" ) ) { count++; } } return count; } boolean supply_crate::vAllowDrop() { return allowDrop; }