iw6-scripts-dev/maps/mp/mp_mine.gsc
2024-12-11 11:28:08 +01:00

1320 lines
34 KiB
Plaintext

#include maps\mp\_utility;
#include common_scripts\utility;
#include maps\mp\agents\_agent_utility;
CONST_KILLSTREAK_NAME = "mine_level_killstreak";
CONST_KILLSTREAK_WEAPON = "killstreak_minemarker_mp";
CONST_DEBUG_NAME = "Mine Killstreak";
CONST_KILLSTREAK_LOC_NAME = &"MP_MINE_LEVEL_KILLSTREAK";
CONST_KILLSTREAK_PICKUP = &"MP_MINE_LEVEL_KILLSTREAK_PICKUP";
CONST_CRATE_WEIGHT = 90;
main()
{
maps\mp\mp_mine_precache::main();
maps\createart\mp_mine_art::main();
maps\mp\mp_mine_fx::main();
maps\mp\_load::main();
// AmbientPlay( "ambient_mp_setup_template" );
maps\mp\_compass::setupMiniMap( "compass_map_mp_mine" );
setdvar( "r_lightGridEnableTweaks", 1 );
setdvar( "r_lightGridIntensity", 1.33 );
SetDvar( "r_tessellationCutoffFalloffBase", 600 );
SetDvar( "r_tessellationCutoffDistanceBase", 2000 );
SetDvar( "r_tessellationCutoffFalloff", 600 );
SetDvar( "r_tessellationCutoffDistance", 2000 );
SetDvar( "r_reactiveMotionWindFrequencyScale", 0.1);
SetDvar( "sm_sunSampleSizeNear", 0.43);
game["attackers"] = "allies";
game["defenders"] = "axis";
game[ "allies_outfit" ] = "urban";
game[ "axis_outfit" ] = "woodland";
// wheel anims are set in the script_noteworthy. wheelSpeed=0 -> idle; wheelSpeed=2 -> fast
level.minecartWheelAnims = [ "mp_cart_idle_anim", "mp_cart_spin_slow_anim", "mp_cart_spin_mid_anim", "mp_cart_spin_fast_anim" ];
thread setupElevator();
thread MineCartSetup("cart1", "cart1TrackStart", "cart1AttachedModels","cart1dmg", "cart1_inside", "cart1_front" );
thread MineCartSetup("cart2", "cart2TrackStart", "cart2AttachedModels","cart2dmg", "cart2_interior", "cart2_front" );
thread setupElevatorKillTrigger();
initKillstreak();
thread maps\mp\_dlcalienegg::setupEggForMap( "alienEasterEgg" );
wildlife();
thread ambientAnimations();
level._effect[ "gear_blood" ] = LoadFX( "vfx/moments/mp_zerosub/vfx_blood_explosion" );
thread setupPushTrigger( "pushTrigger01", (10, 90, 0) );
thread setupPushTrigger( "pushTrigger02", (10, 180, 0) );
// thread setupPushTrigger( "pushTrigger03", (10, 90, 0) );
thread setupPushTrigger( "pushTrigger04", (10, 270, 0) );
}
setupGameTypeFlags( elevator )
{
level.mineBFlag = undefined;
objectives = [];
if ( level.gameType == "dom" || level.gameType == "siege" )
{
objectives = GetEntArray( "flag_primary", "targetname" );
}
else
{
return;
}
// cache the result so we don't have to find it each time
foreach ( f in objectives )
{
if ( IsDefined( f.script_label ) && f.script_label == "_b" )
{
level.mineBFlag = f;
break;
}
}
if ( IsDefined( level.mineBFlag ) )
{
level thread updateBFlagObjIcon();
domFlag = getDomFlagB();
domflag EnableLinkTo();
domflag LinkTo( ELEVATOR );
while (!IsDefined(domflag.useObj))
{
waitframe();
}
foreach ( vis in domflag.useObj.visuals )
{
vis LinkTo( ELEVATOR );
}
}
}
getDomFlagB()
{
return level.mineBFlag;
}
BASE_EFFECT_OFFSET_GOING_DOWN = 1.5;
BASE_EFFECT_OFFSET_GOING_UP = 16;
updateBFlagFxPos( goingUp, loop )
{
level endon( "mp_mine_elevator_stopped" );
bFlag = getDomFlagB();
if ( !IsDefined( bFlag ) )
return;
offset = ter_op( goingUp, BASE_EFFECT_OFFSET_GOING_UP, BASE_EFFECT_OFFSET_GOING_DOWN );
while ( true )
{
fxOrigin = bFlag.origin + ( 0, 0, offset );
bFlag.useObj.baseEffectPos = fxOrigin;
if ( IsDefined( bFlag.useObj.neutralFlagFx ) )
{
bFlag.useObj.neutralFlagFx.origin = fxOrigin;
TriggerFX( bFlag.useObj.neutralFlagFx );
}
foreach ( player in level.players )
{
if ( IsDefined( player._domFlagEffect ) && IsDefined( player._domFlagEffect[ "_b" ] ) )
{
player._domFlagEffect[ "_b" ].origin = fxOrigin;
TriggerFX( player._domFlagEffect[ "_b" ] );
}
}
if ( !loop )
{
break;
}
wait 0.25;
}
}
updateBFlagObjIcon()
{
bFlag = getDomFlagB();
while ( !IsDefined( bFlag.useObj ) )
{
waitframe();
}
// Force the objective icons to update
tag_origin = spawn_tag_origin();
tag_origin show();
tag_origin.origin = bFlag.origin + ( 0, 0, 100 );
tag_origin LinkTo( bFlag );
bFlag.useObj.objIconEnt = tag_origin;
bFlag.useObj maps\mp\gametypes\_gameobjects::updateWorldIcons();
}
setupElevator()
{
ELEVATOR = GetEnt("elevator", "targetname");
GEARS = GetEnt("elevatorGears", "targetname");
BLOCKER = GetEnt("elevatorPathNodeHolders", "targetname");
BLOCKERTOP = GetEnt("elevatorPathNodeTop", "targetname");
BLOCKERMID = GetEnt("elevatorPathNodeMid", "targetname");
BLOCKERBOT = GetEnt("elevatorPathNodeBot", "targetname");
MOVETIME = 6; //how long it takes for elevator to move from floor to floor
WAITTIME = 20; //how long elevator stays at floor
STRUCTTOP = getstruct("elevatorTop", "targetname");
STRUCTBOT = getstruct("elevatorBot", "targetname");
//Link attached models to elevator
ELEVATORMODELS = GetEntArray( "elevatorAttachedModels", "targetname" );
foreach ( detail in ELEVATORMODELS )
{
detail LinkTo( ELEVATOR );
}
ELEVATORKILL = GetEnt("elevatorDamage", "targetname");
ELEVATORKILL EnableLinkTo();
ELEVATORKILL LinkTo(ELEVATOR);
GEARKILL = ELEVATOR linkTrigger( "elevatorGearCrushTrigger" );
BEAMKILL = GetEnt("elevatorSquish", "targetname");
BEAMKILL.dmg = 0;
//make all path blockers non solid so dogs and stuff can run through them.
ELEVATOR ConnectPaths();
BLOCKER Hide();
BLOCKER NotSolid();
BLOCKERTOP Hide();
BLOCKERTOP NotSolid();
//BLOCKERMID Hide();
//BLOCKERMID NotSolid();
//BLOCKERBOT Hide();
//BLOCKERBOT NotSolid();
setupGameTypeFlags( ELEVATOR );
//Elevator Starts in TOP position, so enable those paths
blockerConnect(BLOCKERTOP);
// setup sound entities
leftSoundEnt = GetEnt( "elevatorWheelLeft", "targetname" );
leftSoundEnt LinkTo( GEARS );
rightSoundEnt = GetEnt( "elevatorWheelRight", "targetname" );
rightSoundEnt LinkTo( GEARS );
wait 10;
//movement loop
while (true)
{
//MOVE DOWN
GEARS RotatePitch( -1 * 256.1, MOVETIME, 1, 1);
GEARS MoveTo((-59, 256, 287), MOVETIME, 1, 1);
ELEVATOR MoveTo(STRUCTBOT.origin, MOVETIME, 1, 1);
ELEVATORKILL.dmg = 1000;
ELEVATOR.destroyDroneOnCollision = true;
ELEVATOR.destroyAirdropOnCollision = true;
level thread updateBFlagFxPos( false, true );
// elevator down sound
leftSoundEnt PlaySoundOnMovingEnt( "mine_elev_big_01" );
rightSoundEnt PlaySoundOnMovingEnt( "mine_elev_big_02" );
blockerConnect(BLOCKERMID);
wait 2;
//blockerDisconnect(BLOCKERTOP);
wait 2;
blockerDisconnect(BLOCKERTOP);
blockerConnect(BLOCKERBOT);
wait 2;
blockerDisconnect(BLOCKERMID);
level notify( "mp_mine_elevator_stopped" );
level updateBFlagFxPos( false, false );
wait WAITTIME;
//MOVE UP
GEARS RotatePitch( 1 * 256.1, MOVETIME, 1, 1);
GEARS MoveTo((-59, 256, 543), MOVETIME, 1, 1);
ELEVATOR MoveTo(STRUCTTOP.origin, MOVETIME, 1, 1);
ELEVATORKILL.dmg = 0;
ELEVATOR.destroyDroneOnCollision = false;
ELEVATOR.destroyAirdropOnCollision = false;
level thread updateBFlagFxPos( true, true );
// elevator up sound
leftSoundEnt PlaySoundOnMovingEnt( "mine_elev_big_01" );
rightSoundEnt PlaySoundOnMovingEnt( "mine_elev_big_02" );
blockerConnect(BLOCKERMID);
wait 2;
GEARKILL thread crushPlayerInTrigger();
BEAMKILL.dmg = 1000;
blockerDisconnect(BLOCKERBOT);
blockerConnect(BLOCKERTOP);
wait 2;
//blockerConnect(BLOCKERTOP);
wait 2;
blockerDisconnect(BLOCKERMID);
GEARKILL notify( "stopCrushing" );
BEAMKILL.dmg = 0;
level notify( "mp_mine_elevator_stopped" );
level updateBFlagFxPos( false, false );
wait WAITTIME;
}
/*if( level.gametype == "horde" )
return;
elevatorCfg = SpawnStruct();
elevatorCfg.name = "elevator";
// elevatorCfg.destinations = [ "elevatorBot", "elevatorTop" ];
elevatorCfg.destinations = "elevatorDestination";
elevatorCfg.destinationNames = ["elevatorTop","elevatorBot"];
elevatorCfg.buttons = "elevatorButton";
elevatorCfg.models = "elevatorAttachedModels";
elevatorCfg.moveTime = 5.0;
elevatorCfg.startSfx = "scn_elevator_startup";
elevatorCfg.stopSfx = "scn_elevator_stopping";
elevatorCfg.loopSfx = "scn_elevator_moving_lp";
// elevatorCfg.beepSfx = "scn_elevator_beep";
elevatorCfg.onMoveCallback = ::elevatorStartGears;
// add start and stop functions
elevator = maps\mp\_elevator_v2::init_elevator( elevatorCfg );
//if( level.gametype == "dom" )
//{
// wait(10);
// level.domFlags[1] LinkTo( elevator );
//}
gears = GetEntArray( "elevatorGears", "targetname" );
foreach ( gear in gears )
{
//gear LinkTo( elevator );
}
elevator.pathBlockers[ "elevatorMid" ] = GetEnt( "elevatorPathNodeMid", "targetname" );
elevator maps\mp\_elevator_v2::elevatorClearPath( elevator.curFloor );
*/
}
blockerConnect(BLOCKENT)
{
BLOCKENT Show();
BLOCKENT Solid();
BLOCKENT ConnectPaths();
BLOCKENT Hide();
BLOCKENT NotSolid();
}
blockerDisconnect(BLOCKENT)
{
BLOCKENT Show();
BLOCKENT Solid();
BLOCKENT DisconnectPaths();
}
elevatorStartGears( floorName ) // self == elevator
{
// can't rotate things that are linked?
gears = GetEntArray( "elevatorGears", "targetname" );
if ( floorName == self.destinationNames[0] )
{
// going down
foreach ( gear in gears )
{
gear RotatePitch( 1 * 360, self.moveTime, 0, 0 );
gear MoveTo((-59, 256, 543), self.moveTime, 0, 0);
}
//wait( .5 );
self maps\mp\_elevator_v2::elevatorClearPath( "elevatorMid" );
wait ( 5 );
self maps\mp\_elevator_v2::elevatorBlockPath( "elevatorMid" );
}
else
{
// going up
foreach ( gear in gears )
{
gear RotatePitch( -1 * 360, self.moveTime, 0, 0 );
gear MoveTo((-59, 256, 287), self.moveTime, 0, 0);
}
//wait( .5 );
self maps\mp\_elevator_v2::elevatorClearPath( "elevatorMid" );
wait ( 5 );
self maps\mp\_elevator_v2::elevatorBlockPath( "elevatorMid" );
}
}
MINE_CART_SLOW_SPEED_LIMIT = 250;
MineCartSetup(CartName, FirstNode, LinkedModels, DamageTrigger, insideTriggerName, frontTriggerName )
{
MINE_CART = GetEnt( CartName, "targetname" );
MINE_CART_SPEED = 1.0/MINE_CART_SLOW_SPEED_LIMIT;
MINE_CART_DESTINATION = getstruct( FirstNode, "targetname" );
// MINE_CART_DAMAGE = MINE_CART linkTrigger( DamageTrigger );
insideTrigger = MINE_CART linkTrigger( insideTriggerName );
MINE_CART thread setupMineCartInsideTrigger( insideTrigger );
frontTrigger = MINE_CART linkTrigger( frontTriggerName );
MINE_CART thread setupMineCartFrontTrigger( frontTrigger );
CartModels = GetEntArray( LinkedModels, "targetname" );
foreach ( detail in CartModels )
{
detail LinkTo( MINE_CART );
detail.destroyDroneOnCollision = false;
detail.destroyAirdropOnCollision = true;
}
MINE_CART.destroyDroneOnCollision = false; // this flag allows drones to clip through for a couple of seconds after spawning.
MINE_CART.destroyAirdropOnCollision = true;
killCamEnt = Spawn( "script_model", MINE_CART.origin + ( 0, 0, 60 ) );
killCamEnt LinkTo( MINE_CART );
MINE_CART.killCamEnt = killCamEnt;
MINE_CART.killCamEnt SetScriptMoverKillCam( "explosive" );
MINE_CART.unresolved_collision_func = ::cart_unresolved_collision_func;
MINE_CART.unresolved_collision_notify_min = 6;
MINE_CART mineCartSetupSparks();
MINE_CART moveTo(MINE_CART_DESTINATION.origin, .1, 0, 0);
MINE_CART rotateTo(MINE_CART_DESTINATION.angles, .1, 0, 0);
MINE_CART.cartmodel = CartModels[0];
MINE_CART mineCartHandleEvents( MINE_CART_DESTINATION.script_noteworthy );
MINE_CART.speed = 0;
wait(.5);
curObjID = maps\mp\gametypes\_gameobjects::getNextObjID();
Objective_Add( curObjID, "active", MINE_CART.origin, "mine_cart_icon" );
Objective_OnEntityWithRotation( curObjID, MINE_CART );
MINE_CART.curObjID = curObjID;
while(true)
{
if(IsDefined(MINE_CART_DESTINATION.script_label))
{
speed = float(MINE_CART_DESTINATION.script_label);
MINE_CART_SPEED = 1.0/speed;
MINE_CART.speed = speed;
MINE_CART mineCartPlaySparksOnSpeedChange( int(MINE_CART_DESTINATION.script_label) );
}
if(MINE_CART_DESTINATION.targetname == "cart2TrackStart")
{
thread MineCartElevatorMove(MINE_CART_SPEED);
}
MINE_CART_DESTINATION = MineCartMove(MINE_CART, MINE_CART_DESTINATION, MINE_CART_SPEED);
//wait(MINE_CART_SPEED);
}
}
MineCartMove(Cart, CurrentNode, CartSpeed)
{
Cart endon( "death" );
NEXT_NODE = getstruct(CurrentNode.target, "targetname" );
MOVE_TIME = abs(Distance(Cart.origin, NEXT_NODE.origin)*CartSpeed);
Cart MoveTo(NEXT_NODE.origin, MOVE_TIME, 0, 0);
Cart RotateTo(NEXT_NODE.angles, MOVE_TIME, 0, 0);
wait(MOVE_TIME);
// we have arrived at the next node
Cart mineCartHandleEvents( NEXT_NODE.script_noteworthy );
return NEXT_NODE;
}
MineCartElevatorMove(ElevatorSpeed)
{
CARTELEVATE = GetEnt("cart2Elevator", "targetname");
ELEVATENODEBOT = getstruct("cart2TrackStart", "targetname" );
ELEVATENODETOP = getstruct("cart2ElevatorTop", "targetname" );
BLOCKERTOP = GetEnt("cartElevatorPathNodeTop", "targetname");
BLOCKERBOT = GetEnt("cartElevatorPathNodeBot", "targetname");
ELEVATEMOVETIME = abs(Distance(ELEVATENODEBOT.origin, ELEVATENODETOP.origin)*ElevatorSpeed);
CARTELEVATE.unresolved_collision_kill = true;
// move up
CARTELEVATE.destroyDroneOnCollision = false;
CARTELEVATE PlaySoundOnMovingEnt( "minecart2_elevator_up" );
CARTELEVATE MoveTo(ELEVATENODETOP.origin, ELEVATEMOVETIME, 0, 0);
blockerDisconnect(BLOCKERBOT);
wait(ELEVATEMOVETIME);
blockerConnect(BLOCKERTOP);
wait(5);
blockerDisconnect(BLOCKERTOP);
// move down
CARTELEVATE.destroyDroneOnCollision = true;
CARTELEVATE PlaySoundOnMovingEnt( "minecart2_elevator_down" );
CARTELEVATE MoveTo(ELEVATENODEBOT.origin, ELEVATEMOVETIME, 0, 0);
wait(ELEVATEMOVETIME-2);
trigger_on("cart2ElevatorKill", "targetname" );
wait(2);
trigger_off("cart2ElevatorKill", "targetname" );
blockerConnect(BLOCKERBOT);
}
setupElevatorKillTrigger()
{
trigger_off("cart2ElevatorKill", "targetname" );
}
linkTrigger( triggerName )
{
trigger = GetEnt( triggerName, "targetname" );
if ( IsDefined( trigger ) )
{
trigger EnableLinkTo();
trigger LinkTo( self );
}
return trigger;
}
setupMineCartFrontTrigger( frontTrigger )
{
level endon( "game_ended" );
if ( !IsDefined( frontTrigger ) )
return;
while ( true )
{
frontTrigger waittill( "trigger", otherPlayer );
if ( IsPlayer( otherPlayer ) || IsAgent( otherPlayer ) )
{
// react only when the cart is moving fast enough
if ( isReallyAlive( otherPlayer ) && self.speed >= MINE_CART_SLOW_SPEED_LIMIT )
{
if ( otherPlayer IsMantling() )
continue;
// find a valid attacker in the cart
enemyRider = undefined;
if ( self.playersInCart.size > 0 )
{
foreach ( rider in self.playersInCart )
{
if ( isReallyAlive( rider ) && otherPlayer isEnemy( rider ) )
{
enemyRider = rider;
break;
}
}
}
damageDirection = otherPlayer.origin - self.origin;
attacker = undefined;
inflictor = undefined;
damageVal = 20;
if ( IsDefined( enemyRider ) )
{
damageVal = 100;
attacker = enemyRider;
inflictor = self.killCamEnt;
}
else if ( level.hardcoreMode )
{
damageVal = 100;
}
else if ( IsAgent( otherPlayer ) )
{
damageVal = 50;
}
// unhappy about calling callback directly, but there's no way to specify weapon to DoDamage
damageCallback = level.callbackPlayerDamage;
if ( IsAgent( otherPlayer ) )
{
damageCallback = maps\mp\agents\_agent_common::CodeCallback_AgentDamaged;
}
otherPlayer thread [[ damageCallback ]](
inflictor,
attacker,
damageVal,
0,
"MOD_EXPLOSIVE",
"iw6_minecart_mp",
self.origin,
damageDirection,
"none",
0
);
wait(.2);
}
}
wait( 0.05 );
}
}
setupMineCartInsideTrigger( insideTrigger )
{
level endon( "game_ended" );
self.playersInCart = [];
if ( !IsDefined( insideTrigger ) )
return;
while ( true )
{
insideTrigger waittill( "trigger", player );
if ( IsPlayer( player ) && isReallyAlive( player ) )
{
entNum = player GetEntityNumber();
if ( !IsDefined( self.playersInCart[ entNum ] ) )
{
self.playersInCart[ entNum ] = player;
if ( self.playersInCart.size == 1 )
{
self thread waitForRiderExit( insideTrigger );
}
}
}
wait (0.05);
}
}
waitForRiderExit( insideTrigger )
{
level endon ("game_ended");
while ( self.playersInCart.size > 0 )
{
foreach ( index, player in self.playersInCart )
{
if ( !IsDefined( player )
|| !isReallyAlive( player )
|| !player IsTouching( insideTrigger )
)
{
self.playersInCart[ index ] = undefined;
}
}
wait ( 0.05 );
}
}
mineCartSetupSparks()
{
level._effect[ "cart_sparks" ] = LoadFX( "vfx/moments/mp_mine/vfx_track_sparks_child" );
level._effect[ "cart_sparks_loop" ] = LoadFX( "vfx/moments/mp_mine/vfx_track_sparks_loop" );
self.lastSpeed = 10;
self.sparkTimeStamp = 0;
}
CONST_SPARK_SPEED_LIMIT = 20;
CONST_SPARK_FREQUENCY = 3500; // in ms
mineCartPlaySparksOnSpeedChange( targetSpeed )
{
// check velocity relative to target nodeSpeed
if ( IsDefined( targetSpeed )
&& GetTime() > self.sparkTimeStamp
&& abs( targetSpeed - self.lastSpeed ) > CONST_SPARK_SPEED_LIMIT
)
{
self.lastSpeed = targetSpeed;
self.sparkTimeStamp = GetTime() + CONST_SPARK_FREQUENCY;
self thread mineCartPlaySparks( "cart_sparks" );
}
}
CONST_REAR_SCALE = 1;
CONST_RIGHT_SCALE = 18;
CONST_TAG_WHEEL_L = "tag_wheelL";
CONST_TAG_WHEEL_R = "tag_wheelR";
mineCartPlaySparks( fxName )
{
// play sound
/*
forward = -1 * AnglesToForward( self.angles );
right = AnglesToRight( self.angles );
up = AnglesToUp( self.angles );
lrpos = self.origin + CONST_REAR_SCALE * forward - CONST_RIGHT_SCALE * right;
rrpos = self.origin + CONST_REAR_SCALE * forward + CONST_RIGHT_SCALE * right;
PlayFX( getfx( "cart_sparks" ), lrpos, forward, up );
wait( 0.05 );
PlayFX( getfx( "cart_sparks" ), rrpos, forward, up );
// Sphere( lrpos, 5, (1, 0, 0), false, 20 );
// Sphere( rrpos, 5, (0, 1, 0), false, 20 );
*/
PlayFXOnTag( getfx( fxName ), self.cartmodel, CONST_TAG_WHEEL_L );
PlayFXOnTag( getfx( fxName ), self.cartmodel, CONST_TAG_WHEEL_R );
}
mineCartStopSparks( fxName )
{
StopFXOnTag( getfx( fxName ), self.cartmodel, CONST_TAG_WHEEL_L );
StopFXOnTag( getfx( fxName ), self.cartmodel, CONST_TAG_WHEEL_R );
}
mineCartHandleEvents( node_noteworthy ) // self == cart
{
if ( !IsDefined( node_noteworthy ) )
return;
tokens = StrTok( node_noteworthy, "," );
foreach ( token in tokens )
{
if ( isStrStart( token, "sfx=" ) )
{
sfxName = GetSubStr( token, 4, token.size );
self PlaySoundOnMovingEnt( sfxName );
}
else if ( isStrStart( token, "loop=" ) )
{
sfxName = GetSubStr( token, 5, token.size );
self PlayLoopSound( sfxName );
}
else if ( token == "loopstop" )
{
self StopLoopSound();
}
else if ( token == "vfx" )
{
self thread mineCartPlaySparks( "cart_sparks" );
}
else if ( token == "vfxStart" )
{
self thread mineCartPlaySparks( "cart_sparks_loop" );
}
else if ( token == "vfxStop" )
{
self mineCartStopSparks( "cart_sparks_loop" );
}
else if ( isStrStart( token, "wheelSpeed=" ) )
{
wheelAnimId = Int( GetSubStr( token, 11, token.size ) );
if ( wheelAnimId < level.minecartWheelAnims.size )
{
self.cartmodel ScriptModelPlayAnim( level.minecartWheelAnims[ wheelAnimId ] );
}
}
}
}
// --------------------------------------------------
// Killstreaks
// --------------------------------------------------
initKillstreak()
{
level.mapCustomKillstreakFunc = ::customKillstreakFunc;
level.mapCustomCrateFunc = ::customCrateFunc;
level.mapCustomBotKillstreakFunc = ::customBotKillstreakFunc;
/#
AddDebugCommand( "bind p \"set scr_givekillstreak " + CONST_KILLSTREAK_NAME + "\"\n" );
#/
}
customCrateFunc()
{
if(!IsDefined(game["player_holding_level_killstrek"]))
game["player_holding_level_killstrek"] = false;
if(!allowLevelKillstreaks() || game["player_holding_level_killstrek"])
return;
maps\mp\killstreaks\_airdrop::addCrateType( "airdrop_assault",
CONST_KILLSTREAK_NAME,
CONST_CRATE_WEIGHT,
maps\mp\killstreaks\_airdrop::killstreakCrateThink,
maps\mp\killstreaks\_airdrop::get_friendly_crate_model(),
maps\mp\killstreaks\_airdrop::get_enemy_crate_model(),
CONST_KILLSTREAK_PICKUP
);
level thread watchForCrateUse();
}
watchForCrateUse()
{
while( true )
{
level waittill("createAirDropCrate", dropCrate);
if( IsDefined( dropCrate ) && IsDefined( dropCrate.crateType ) && dropCrate.crateType == CONST_KILLSTREAK_NAME )
{
maps\mp\killstreaks\_airdrop::changeCrateWeight("airdrop_assault", CONST_KILLSTREAK_NAME, 0);
captured = wait_for_capture(dropCrate);
if(!captured)
{
//reEnable warhawk mortars care packages if it expires with out anyone picking it up
maps\mp\killstreaks\_airdrop::changeCrateWeight( "airdrop_assault", CONST_KILLSTREAK_NAME, CONST_CRATE_WEIGHT );
}
else
{
//Once its picked up it needs to remain off.
game["player_holding_level_killstrek"] = true;
break;
}
}
}
}
//death and capture are sent on the same frame but death is processed first :(
wait_for_capture(dropCrate)
{
result = watch_for_air_drop_death(dropCrate);
return !IsDefined(result); //If !isdefined the captured notify was also sent.
}
watch_for_air_drop_death(dropCrate)
{
dropCrate endon("captured");
dropCrate waittill("death");
waittillframeend;
return true;
}
customKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/Care Package/" + CONST_DEBUG_NAME + "\" \"set scr_devgivecarepackage " + CONST_KILLSTREAK_NAME + "; set scr_devgivecarepackagetype airdrop_assault\"\n");
AddDebugCommand("devgui_cmd \"MP/Killstreak/Level Event:5/" + CONST_DEBUG_NAME + "\" \"set scr_givekillstreak " + CONST_KILLSTREAK_NAME + "\"\n");
// initAirdropKS();
maps\mp\killstreaks\mp_wolfpack_killstreak::init();
// this is a hack so the mine cart will show a correct killcam icon
level.killstreakWeildWeapons[ "iw6_minecart_mp" ] = "iw6_minecart_mp";
}
customBotKillstreakFunc()
{
AddDebugCommand("devgui_cmd \"MP/Bots(Killstreak)/Level Events:5/" + CONST_DEBUG_NAME + "\" \"set scr_testclients_givekillstreak " + CONST_KILLSTREAK_NAME + "\"\n");
maps\mp\bots\_bots_ks::bot_register_killstreak_func( CONST_KILLSTREAK_NAME, maps\mp\bots\_bots_ks::bot_killstreak_simple_use );
// maps\mp\bots\_bots_ks::bot_register_killstreak_func( CONST_KILLSTREAK_NAME, maps\mp\bots\_bots_ks::bot_killstreak_drop_outside );
}
ambientAnimations()
{
wait( 3 );
wheels = GetEnt( "spinny_wheels", "targetname" );
if ( IsDefined( wheels ) )
{
wheels ScriptModelPlayAnim( "mp_mine_spinning_wheels" );
}
}
wildlife()
{
thread maps\interactive_models\vulture_mp::vulture_circling((565, -1870, 1500), 3);
thread maps\interactive_models\vulture_mp::vulture_circling((-300, 1210, 1650), 2);
/#thread maps\interactive_models\vulture_mp::vultures_toggle_thread();#/
thread maps\interactive_models\batcave::vfxBatCaveWaitInit( "bats_flyaway_1", 1, "vfx_bats_flyaway_1", (-2028.06, 464.427, 413.921) );
thread maps\interactive_models\batcave::vfxBatCaveWaitInit( "bats_flyaway_2", 2, "vfx_bats_flyaway_2", (-264.3, 927.7, 397.8), 2 );
}
crushPlayerInTrigger() // self == gear
{
level endon( "game_ended" );
self endon( "stopCrushing" );
while ( true )
{
self waittill( "trigger", player );
if ( isReallyAlive( player ) )
{
player DoDamage( 1000, player.origin, undefined, undefined, "MOD_CRUSH" );
player notify( "notify_moving_platform_invalid" );
if ( IsPlayer( player ) || IsAgent( player ) )
{
thread cleanupCrushedbody( player GetCorpseEntity() );
}
}
}
}
cleanupCrushedBody( body )
{
PlayFX( getfx( "gear_blood" ), body.origin, -1 * AnglesToForward( body.angles ), AnglesToUp( body.angles ) );
wait( 0.7 );
if ( IsDefined( body ) )
{
PlayFX( getfx( "gear_blood" ), body.origin, -1 * AnglesToForward( body.angles ), AnglesToUp( body.angles ) );
body Hide();
}
}
cart_unresolved_collision_func(player, bAllowSuicide)
{
if ( IsPlayer( player ) && player IsLinked() )
{
player Unlink();
}
// disable node teleporting for the cart and allow players to clip through the cart briefly
// self maps\mp\_movers::unresolved_collision_nearest_node( player, true );
self maps\mp\_movers::unresolved_collision_owner_damage( player );
}
// 2014-07-03 wallace: there are some low overhangs on the mine cart tracks (mostly track1) that are too late to change.
// Instead, we'll push players who hit these triggers away and hopefully off the minecart.
setupPushTrigger( triggerName, pushAngles )
{
level endon( "game_ended" );
trigger = GetEnt( triggerName, "targetname" );
if ( !IsDefined( trigger ) )
return;
while ( true )
{
trigger waittill( "trigger", player );
if ( IsPlayer( player ) || IsAgent( player ) )
{
// if the player is using a box or crate, allow him to be disconnected
if ( player IsLinked() )
{
player Unlink();
player.startUseMover = undefined;
}
dir = 100 * AnglesToForward( pushAngles );
player SetVelocity( dir );
// Sphere( player.origin, 10, (1, 0, 0), false, 1000 );
// Line( player.origin, player.origin + dir, (1, 0, 0), 1, false, 1000 );
}
wait 0.1;
}
}
// ---------------------------------------------------
// airstrike
// ---------------------------------------------------
/*
addDropObj( name, baseModel, fallSfx, fallVfx, impactSfx, impactVfx, impactModel, impactRadius )
{
obj = [];
obj["model"] = baseModel;
obj["fallSfx"] = fallSfx;
if ( IsDefined( fallVfx ) )
{
obj["fallTrailVfx"] = getfx( fallVfx );
}
obj["impactSfx"] = impactSfx;
if ( IsDefined( impactVfx ) )
{
obj["impactVfx"] = getfx( impactVfx );
}
obj["impactModel"] = impactModel;
obj["impactRadius"] = impactRadius;
// self.droppedObjs[ self.droppedObjs.size ] = obj;
self.droppedObjs[ name ] = obj;
}
initAirdropKS()
{
config = SpawnStruct();
config.modelNames = [];
config.modelNames[ "allies" ] = "vehicle_ac130_low_mp";
config.modelNames[ "axis" ] = "vehicle_ac130_low_mp";
config.inboundSfx = "veh_ac130_dist_loop";
// veh_ac130_sonic_boom
config.compassIconFriendly = "compass_objpoint_c130_friendly";
config.compassIconEnemy = "compass_objpoint_c130_enemy";
config.noLightFx = true;
// sonic boom?
config.speed = 2000;
config.halfDistance = 12000;
config.heightRange = 500;
//config.attackTime = 2.0;
// config.outboundFlightAnim = "airstrike_mp_roll";
config.onAttackDelegate = ::dropBombs;
config.onFlybyCompleteDelegate = ::cleanupFlyby;
config.chooseDirection = true;
config.selectLocationVO = "KS_hqr_airstrike";
config.inboundVO = "KS_ast_inbound";
config.chooseDirection = false;
config.droppedObjs = [];
// config addDropObj( "mine_anvil_bomb", undefined, "falling_trail", undefined, "cart_explode", undefined, 200 );
config addDropObj( "piano", "mine_piano_bomb", undefined, "falling_trail", undefined, "cart_explode", "undefined", 300 );
config addDropObj( "bomb", "mine_large_bomb", undefined, "falling_trail", undefined, "cart_explode", undefined, 300 );
config.bombModel = "projectile_cbu97_clusterbomb";
config.numBombs = 8;
// should be 2x the effect radius to have no gaps/overlap
config.distanceBetweenBombs = 350;
config.effectRadius = 200;
config.effectHeight = 120;
level.planeConfigs[ CONST_KILLSTREAK_NAME ] = config;
level.killstreakFuncs[CONST_KILLSTREAK_NAME] = ::onUse;
level.killstreakWeildWeapons[ CONST_KILLSTREAK_WEAPON ] = CONST_KILLSTREAK_NAME;
}
onUse( lifeId, streakName )
{
assert( isDefined( self ) );
// check for active air_superiority strikes
otherTeam = getOtherTeam( self.team );
result = selectAirstrikeLocationWithGrenade( lifeId, streakName, getKillstreakWeapon( streakName ), ::doStrike );
return ( IsDefined( result ) && result );
}
doStrike( lifeId, location, directionYaw, streakName )
{
wait ( 1 );
directionYaw = self.angles[1] + 90;
planeFlyHeight = maps\mp\killstreaks\_plane::getPlaneFlyHeight();
dirVector = AnglesToForward( (0, directionYaw, 0) );
doOneFlyby( streakName, lifeId, location, dirVector, planeFlyHeight );
self waittill( "airstrike_flyby_complete" );
// play outbound vo
}
doOneFlyby( streakName, lifeId, targetPos, dir, flyHeight )
{
config = level.planeConfigs[ streakName ];
// absolute height should be derived from the heightEnt
flightPath = maps\mp\killstreaks\_plane::getFlightPath( targetPos, dir, config.halfDistance, true, flyHeight, config.speed, 0, streakName );
// Box( targetPos, dir[1], (0, 0, 1), false, 200);
// may want to break this up into spawn, move, cleanup components
// so that we can reuse the plane
level thread maps\mp\killstreaks\_plane::doFlyby( lifeId, self, lifeId,
flightPath["startPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["endPoint"] + (0, 0, randomInt(config.heightRange) ),
flightPath["attackTime"],
flightPath["flyTime"],
dir,
streakName );
}
cleanupFlyby( owner, plane, streakName )
{
owner notify( "airstrike_flyby_complete" );
}
dropBombs( pathEnd, flyTime, beginAttackTime, owner, streakName ) // self == plane
{
self endon( "death" );
wait ( 0.95 * beginAttackTime);
config = level.planeConfigs[ streakName ];
numBombsLeft = config.numBombs;
timeBetweenBombs = config.distanceBetweenBombs / config.speed;
while (numBombsLeft > 0)
{
bombPos = pickRandomTargetPoint( self.origin, 50 );
self thread dropOneBomb( owner, streakName, bombPos, "bomb" );
numBombsLeft--;
wait ( 0.1 );
}
bombPos = pickRandomTargetPoint( self.origin, 50 );
self thread dropOneBomb( owner, streakName, bombPos, "piano" );
}
pickBombType( config )
{
index = RandomInt( config.droppedObjs.size );
return config.droppedObjs[ index ];
}
CONST_DRAW_TIME = 200;
GRAVITY_UNITS_PER_SEC = 800;
GRAVITY_UNITS_PER_SEC_DIV = 0.00125;
dropOneBomb( owner, streakName, spawnPoint, bombType ) // self == plane
{
plane = self;
config = level.planeConfigs[ streakName ];
planeDir = AnglesToForward( plane.angles );
// bombConfig = pickBombType( config );
bombConfig = config.droppedObjs[ bombType ];
bomb = spawnBomb( bombConfig["model"], spawnPoint, plane.angles );
bomb.owner = owner;
trace = BulletTrace( bomb.origin, bomb.origin + (0,0,-1000000), false, plane );
impactPosition = trace["position"];
// calculate time it takes to fall distance to impac
timeSquared = Length( bomb.origin - impactPosition ) * 2 * GRAVITY_UNITS_PER_SEC_DIV;
fallTime = sqrt( timeSquared );
/#
// Sphere( bomb.origin, 10, (0, 1, 0), false, CONST_DRAW_TIME );
// Line( bomb.origin, impactPosition, (0, 1, 0), 1, false, CONST_DRAW_TIME );
// Sphere( bomb.origin, 20, (0, 1, 0), false, CONST_DRAW_TIME );
#/
bomb MoveGravity( (0, 0, 0), fallTime );
// add trail vfx
// add falling sfx
wait ( fallTime );
PlayFX( bombConfig["impactVfx"], bomb.origin );
bomb onBombImpact( owner, self, impactPosition, streakName, bombConfig );
}
spawnBomb( modelName, origin, angles )
{
bomb = Spawn( "script_model", origin );
bomb.angles = angles;
bomb SetModel( modelname );
return bomb;
}
onBombImpact( owner, plane, position, streakName, bombConfig ) // self == bomb?
{
config = level.planeConfigs[ streakName ];
PlayFX( bombConfig["impactVfx"], position );
// play impact sfx
// damage radius - need amount, weapon
self RadiusDamage( position, bombConfig["impactRadius"], 200, 80, owner, "MOD_CRUSH", CONST_KILLSTREAK_WEAPON );
/#
// Sphere( position, 10, (1, 0, 0), false, CONST_DRAW_TIME );
// Sphere( position, bombConfig["impactRadius"], (1, 0, 0), false, CONST_DRAW_TIME );
#/
self Hide();
wait ( 5 );
self Delete();
}
pickRandomTargetPoint( targetPoint, strikeRadius )
{
x = RandomFloatRange( -1 * strikeRadius, strikeRadius );
y = RandomFloatRange( -1 * strikeRadius, strikeRadius );
return targetPoint + (x, y, 0);
}
selectAirstrikeLocationWithGrenade( lifeId, killstreakName, weaponName, onLocationSelected )
{
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "death" );
self notify( "selectAirstrikeLocationWithGrenade" );
self endon( "selectAirstrikeLocationWithGrenade" );
self childthread waitForMarkerUse( lifeId, killstreakName, weaponName, onLocationSelected );
self childthread waitForWeaponSwitch( killstreakName, weaponName );
result = self waittill_any_return( "airstrike_selected", "airstrke_canceled" );
return ( result == "airstrike_selected" );
}
waitForMarkerUse( lifeId, killstreakName, markerName, onLocationSelected )
{
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "death" );
self endon( "airstrke_canceled" );
self.threwAirDropMarker = undefined;
while( true )
{
self waittill( "grenade_pullback", weaponName );
if ( weaponName != markerName )
continue;
self _disableUsability();
// need to thread something to enableUsability in case airstrike is cancelled
self thread restoreUsability();
self waittill( "grenade_fire", marker, weaponName );
self _enableUsability();
if ( weaponName != markerName )
continue;
marker.owner = self;
marker.weaponName = weaponName;
self TakeWeapon( weaponName );
marker thread detonateOnStuck( lifeId, killstreakName, onLocationSelected );
break;
}
}
waitForWeaponSwitch( killstreakName, weaponName )
{
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "death" );
self endon( "airstrike_selected" );
currentWeapon = self GetCurrentWeapon();
// first, wait until we raise the marker
while ( currentWeapon != weaponName )
{
self waittill( "weapon_change", currentWeapon );
}
// then wait until we switch away from the marker
while ( currentWeapon == weaponName )
{
self waittill( "weapon_change", currentWeapon );
}
// check if we selected airstrike?
if ( !IsDefined( self.threwAirDropMarker ) || !self.threwAirDropMarker )
{
self notify( "airstrke_canceled" );
}
}
detonateOnStuck( lifeId, killstreakName, onLocationSelected ) // self == grenade
{
owner = self.owner;
owner.threwAirDropMarker = true;
self waittill( "missile_stuck" );
position = self.origin;
self Detonate();
BadPlace_Cylinder( "", 10, position, 600, 100, "axis", "allies" );
// validate the grenade location?
owner notify( "airstrike_selected" );
owner thread [[ onLocationSelected ]]( lifeId, position, 0, killstreakName );
}
restoreUsability()
{
level endon( "game_ended" );
self endon( "disconnect" );
self endon( "death" );
self endon( "airstrke_canceled" );
self endon( "airstrike_selected" );
self waittill( "weapon_change" );
self _enableUsability();
}
deleteAfterTime( time )
{
self endon ( "death" );
wait ( time );
self Delete();
}
*/