1795 lines
48 KiB
Plaintext
1795 lines
48 KiB
Plaintext
#include common_scripts\utility;
|
|
#include maps\mp\_utility;
|
|
#include maps\mp\alien\_utility;
|
|
#include maps\mp\agents\_agent_utility;
|
|
#include maps\mp\alien\_persistence;
|
|
#include maps\mp\alien\_perk_utility;
|
|
|
|
|
|
COLLECTIBLES_TABLE = "mp/alien/collectibles.csv"; // collectable itmes definition
|
|
|
|
TABLE_ITEM_INDEX = 0; // Indexing
|
|
TABLE_ITEM_REF = 1; // reference string
|
|
TABLE_ITEM_MODEL = 2; // xmodel string
|
|
TABLE_ITEM_NAME = 3; // localization name
|
|
TABLE_ITEM_DESC = 4; // localization desc, use hint
|
|
TABLE_ITEM_COUNT = 5; // count per spawn
|
|
TABLE_ITEM_PARTS = 6; // parts array
|
|
TABLE_ITEM_OWNERSHIP = 7; // ownership type, soulbound/shared/droppable
|
|
TABLE_ITEM_RESPAWN = 8; // respawn timer, 0 = no respawn, float value
|
|
TABLE_ITEM_RESPAWN_MAX = 9; // respawn times, 0 = always, 1 = respawns once
|
|
TABLE_ITEM_VIS = 10; // visibility condition string
|
|
TABLE_ITEM_UNLOCK = 11; // unlock requirement
|
|
TABLE_ITEM_PLAYER_MAX = 12; // player inventory max carry count
|
|
|
|
TABLE_ITEM_PERSIST = 13; // Does not get removed from the world after use
|
|
TABLE_ITEM_XP = 14; // XP reward for collecting
|
|
TABLE_ITEM_COST = 15; // cost of pickup if it requires, 0 = no cost
|
|
|
|
ITEM_MIN_SPAWN_DISTANCE_SQR = 1024.0 * 1024.0;
|
|
|
|
DROP_TO_GROUND_UP_DIST = 32;
|
|
DROP_TO_GROUND_DOWN_DIST = -32;
|
|
DROP_TO_GROUND_PROPANE_TANK_DOWN_DIST = -500;
|
|
|
|
CONST_PISTOL_BEAST_AMMO_COST = 1500; //Cost for ammo at a weapon if player is running pistols only and earn your keep
|
|
|
|
pre_load()
|
|
{
|
|
if ( !alien_mode_has( "collectible" ) )
|
|
return;
|
|
|
|
if ( !isdefined( level.alien_collectibles_table ) )
|
|
level.alien_collectibles_table = COLLECTIBLES_TABLE;
|
|
|
|
// precache item assets
|
|
collectibles_model_precache();
|
|
|
|
// setup fx
|
|
level._effect[ "Fire_Cloud" ] = loadfx( "vfx/gameplay/alien/vfx_alien_gas_fire");
|
|
level._effect[ "Propane_explosion" ] = loadfx( "vfx/gameplay/alien/vfx_alien_propane_tank_explosion" );
|
|
level._effect[ "Propane_explosion_cheap" ] = loadfx( "vfx/gameplay/alien/vfx_alien_propane_tank_exp_cheaper" );
|
|
level._effect[ "Propane_explosion_cheapest" ] = loadfx( "vfx/gameplay/alien/vfx_alien_propane_tank_exp_cheapest" );
|
|
|
|
// init collectibles table
|
|
level.collectibles = [];
|
|
collectibles_table_init( 0, 99 ); // items
|
|
collectibles_table_init( 100, 199 ); // weapons
|
|
|
|
// precache collectible pickup strings ( must match [desc] of stringtable: mp/alien/collectibles.csv )
|
|
all_hints_array = [[level.hintprecachefunc]]();
|
|
|
|
foreach ( item in level.collectibles )
|
|
{
|
|
foreach ( key, hint in all_hints_array )
|
|
{
|
|
if ( item.desc == key )
|
|
{
|
|
item.desc = get_localized_hint( item, hint );
|
|
break; // item.desc is now a localized string, can't compare to regular strings anymore
|
|
}
|
|
}
|
|
}
|
|
|
|
level.pistol_ammo_cost = CONST_PISTOL_BEAST_AMMO_COST;
|
|
}
|
|
|
|
get_localized_hint( item, hint )
|
|
{
|
|
if ( is_chaos_mode() && item.isWeapon )
|
|
return &"ALIEN_CHAOS_WEAPON_PICKUP_HINT";
|
|
|
|
return hint;
|
|
}
|
|
|
|
collectibles_model_precache()
|
|
{
|
|
PreCacheModel( "propane_tank_aliens_iw6" );
|
|
}
|
|
|
|
|
|
post_load()
|
|
{
|
|
if ( !alien_mode_has( "collectible" ) )
|
|
return;
|
|
|
|
// init collectibles in the world
|
|
collectibles_world_init();
|
|
|
|
level.collectibles_lootcount = 0;
|
|
level.alien_loot_initialized = true;
|
|
}
|
|
|
|
// sets up player for loot collection - fresh start every spawn, lose everything!
|
|
player_loot_init()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
|
|
if ( !alien_mode_has( "loot" ) )
|
|
return;
|
|
|
|
self.lootbag = [];
|
|
self.has_health_pack = false;
|
|
|
|
self notify( "loot_initialized" );
|
|
|
|
level.fireCloudDuration = getDvarInt( "scr_fireCloudDuration", "9" );
|
|
level.fireCloudRadius = getDvarInt( "scr_fireCloudRadius", "125" );
|
|
level.fireCloudHeight = getDvarInt( "scr_fireCloudHeight", "120" );
|
|
level.fireCloudTickDamage = getDvarInt( "scr_fireCloudTickDamage", "100" ); // alien tick is fixed to 1 second interval
|
|
level.fireCloudHiveTickDamage = getDvarInt( "scr_fireCloudHiveTickDamage", "100" ); // hive tick is fixed to 0.25 second interval
|
|
level.fireCloudPlayerTickDamage = getDvarInt( "scr_fireCloudPlayerTickDamage", "3" ); // player tick is fixed to 1 second interval
|
|
level.fireCloudLingerTime = getDvarInt( "scr_fireCloudLingerTime", "6" ); // seconds alien gets burned after touching fire
|
|
|
|
}
|
|
|
|
collectibles_world_init()
|
|
{
|
|
assertex( isdefined( level.collectibles ), "Collectibles not initialized" );
|
|
level.itemexplodethisframe = false;
|
|
level.collectibles_worldcount = [];
|
|
|
|
// grab all collectible items
|
|
items = getstructarray( "item", "targetname" );
|
|
|
|
if ( isDefined( level.additional_boss_weapon ) )
|
|
{
|
|
additional_boss_weapon = [[ level.additional_boss_weapon]]();
|
|
if ( isDefined( additional_boss_weapon ) )
|
|
items = array_add( items,additional_boss_weapon );
|
|
}
|
|
|
|
if ( is_chaos_mode() )
|
|
items = maps\mp\alien\_chaos::swap_weapon_items( items );
|
|
|
|
level.world_items = items;
|
|
|
|
foreach ( world_item in level.world_items )
|
|
{
|
|
assertex( isdefined( world_item.script_noteworthy ), "Item at " + world_item.origin + " is missing script_noteworthy as item type" );
|
|
world_item.item_ref = world_item.script_noteworthy;
|
|
assertex( item_exist( world_item.item_ref ), "Item: " + world_item.item_ref + " does not exist in collectibles, update collectibles.csv, and check if max_item_index is set to include number of items in table" );
|
|
|
|
world_item setup_item_data();
|
|
|
|
level.collectibles_worldcount[ world_item.item_ref ] = level.collectibles[ world_item.item_ref ].count;
|
|
}
|
|
|
|
init_throwableItems();
|
|
|
|
area_name = get_current_area_name();
|
|
if ( is_chaos_mode() )
|
|
area_name = get_chaos_area();
|
|
|
|
thread spawn_items_in_area( area_name );
|
|
|
|
if ( isDefined( level.enter_area_func ) )
|
|
[[level.enter_area_func]]( area_name );
|
|
}
|
|
|
|
init_throwableItems()
|
|
{
|
|
level.thrown_entities = [];
|
|
|
|
level.throwable_items = [];
|
|
// weapon name
|
|
level.throwable_items [ "alienpropanetank_mp" ] = init_throwable( 10000, "propane_tank_aliens_iw6", true, &"ALIEN_COLLECTIBLES_PICKUP_PROPANE_TANK", ::propaneTankWatchUse );
|
|
}
|
|
|
|
init_throwable( force, model, canBePickedUp, hintString, pickUpFunc )
|
|
{
|
|
item_data = spawnStruct();
|
|
item_data.force = force; // the forward force that is applied when the item is thrown
|
|
item_data.model = model; // the item model
|
|
item_data.canBePickedUp = canBePickedUp; // boolean. whether the item can be picked back up again
|
|
item_data.hintString = hintString; // if the item can be picked up back, the hint string that is applied
|
|
item_data.pickUpFunc = pickUpFunc; // if the item can be picked up back, call back function on the item
|
|
|
|
return item_data;
|
|
}
|
|
|
|
|
|
spawn_items_in_area( area )
|
|
{
|
|
randomized_items = array_randomize( level.world_items );
|
|
|
|
foreach ( world_item in randomized_items )
|
|
{
|
|
if ( !world_item.data["persist"] && area != "all" && !array_contains( world_item.areas, area ) )
|
|
continue;
|
|
|
|
if ( level.collectibles_worldcount[ world_item.item_ref ] > 0 )
|
|
{
|
|
if ( isDefined( world_item.item_ent ) )
|
|
continue;
|
|
|
|
world_item spawn_item();
|
|
world_item thread item_pickup_listener();
|
|
level.collectibles_worldcount[ world_item.item_ref ]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
remove_items_in_area( area )
|
|
{
|
|
foreach( world_item in level.world_items )
|
|
{
|
|
if ( isDefined( world_item.item_ent ) )
|
|
{
|
|
if ( !world_item.data["persist"] && area != "all" && !array_contains( world_item.areas, area ) )
|
|
continue;
|
|
|
|
world_item remove_world_item();
|
|
level.collectibles_worldcount[ world_item.item_ref ]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// this keeps track of data (such as count, respawn times etc) per item
|
|
setup_item_data( )
|
|
{
|
|
self.override = SpawnStruct();
|
|
|
|
if ( !isdefined( self.script_noteworthy ) )
|
|
self.script_noteworthy = self.item_ref;
|
|
|
|
self.isLoot = is_collectible_loot( self.item_ref );
|
|
self.isWeapon = is_collectible_weapon( self.item_ref );
|
|
self.isItem = is_collectible_item( self.item_ref );
|
|
|
|
self.areas = self get_item_areas();
|
|
|
|
// overrides
|
|
if ( isdefined( self.script_parameters ) )
|
|
{
|
|
// parsing parameters
|
|
// format: "key=value key=value"
|
|
string_toks = StrTok( self.script_parameters, " " );
|
|
foreach ( token in string_toks )
|
|
{
|
|
string_tok = StrTok( self.script_parameters, "=" );
|
|
|
|
if ( string_tok.size == 0 )
|
|
continue;
|
|
|
|
assertex( string_tok.size == 2, "Incorrect format for override parameter, script_parameters 'key=value key=value ...'" );
|
|
|
|
key = string_tok[ 0 ];
|
|
value = string_tok[ 1 ];
|
|
|
|
switch ( key )
|
|
{
|
|
case "respawn_max":
|
|
self.override.respawn_max = int( value );
|
|
break;
|
|
|
|
case "unlock":
|
|
self.override.unlock = int( value );
|
|
break;
|
|
|
|
default:
|
|
AssertMsg( "You can not override: " + key + " on " + self.item_ref );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// init item data, tracker of item data
|
|
self.data = [];
|
|
self.data[ "count" ] = level.collectibles[ self.item_ref ].count;
|
|
self.data[ "respawn_count" ] = level.collectibles[ self.item_ref ].respawn_max;
|
|
|
|
self.data[ "times_collected" ] = 0;
|
|
self.data[ "last_collector" ] = undefined;
|
|
self.data[ "vis" ] = true; // visibility
|
|
self.data[ "unlock" ] = 1;
|
|
self.data[ "persist" ] = level.collectibles[ self.item_ref ].persist;
|
|
self.data[ "cost" ] = level.collectibles[ self.item_ref ].cost;
|
|
|
|
if ( isdefined( self.override ) )
|
|
{
|
|
if ( isdefined( self.override.respawn_max ) )
|
|
self.data[ "respawn_count" ] = self.override.respawn_max;
|
|
if ( isdefined( self.override.unlock ) )
|
|
self.data[ "unlock" ] = self.override.unlock;
|
|
}
|
|
}
|
|
|
|
get_item_areas()
|
|
{
|
|
if ( !isDefined( level.world_areas ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
my_areas = [];
|
|
|
|
foreach ( area_name, area_volume in level.world_areas )
|
|
{
|
|
if ( IsPointInVolume( self.origin, area_volume ) )
|
|
{
|
|
my_areas[ my_areas.size ] = area_name;
|
|
}
|
|
}
|
|
|
|
return my_areas;
|
|
}
|
|
|
|
spawn_world_item( dropToGround, needPressXToUse )
|
|
{
|
|
// self is world item struct
|
|
item_ref = self.item_ref;
|
|
|
|
item_ent = spawn( "script_model", get_world_item_spawn_pos( dropToGround ) );
|
|
item_ent SetModel( level.collectibles[ item_ref ].model );
|
|
|
|
if ( is_chaos_mode() && is_true( self.isweapon ) )
|
|
item_ent.weapon_ref = getsubstr( item_ref, 7 ); // to remove "weapon_"
|
|
|
|
if ( IsDefined( self.angles ) )
|
|
{
|
|
item_ent.angles = self.angles;
|
|
}
|
|
else
|
|
{
|
|
item_ent.angles = (0, 0, 0);
|
|
}
|
|
|
|
self.item_ent = item_ent;
|
|
|
|
if ( needPressXToUse )
|
|
{
|
|
make_item_ent_useable( self.item_ent, get_item_desc( item_ref ) );
|
|
self.use_ent = self.item_ent;
|
|
}
|
|
else
|
|
{
|
|
self.use_ent = spawn( "trigger_radius", item_ent.origin, 0, 32, 32 );
|
|
}
|
|
|
|
if ( should_explode_on_damage ( item_ref ) )
|
|
self.item_ent thread explodeOnDamage( false );
|
|
|
|
self notify ( "spawned" );
|
|
|
|
/# // debug
|
|
if ( getdvarint( "debug_collectibles" ) == 1 )
|
|
maps\mp\alien\_debug::debug_collectible( self );
|
|
#/
|
|
if ( alien_mode_has( "outline" ) )
|
|
{
|
|
if ( GetSubStr( item_ref, 0, 6 ) == "weapon" )
|
|
{
|
|
maps\mp\alien\_outline_proto::add_to_outline_weapon_watch_list ( item_ent, self.data[ "cost" ] );
|
|
}
|
|
else
|
|
{
|
|
maps\mp\alien\_outline_proto::add_to_outline_watch_list ( item_ent, self.data[ "cost" ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
get_world_item_spawn_pos( dropToGround )
|
|
{
|
|
VERTICAL_OFFSET = ( 0, 0, 16 );
|
|
|
|
if ( dropToGround )
|
|
return ( drop_to_ground( self.origin, DROP_TO_GROUND_UP_DIST, DROP_TO_GROUND_DOWN_DIST ) + VERTICAL_OFFSET );
|
|
else
|
|
return self.origin;
|
|
}
|
|
|
|
make_item_ent_useable( item_ent, hintString )
|
|
{
|
|
item_ent SetCursorHint( "HINT_NOICON" );
|
|
item_ent SetHintString( hintString );
|
|
item_ent MakeUsable();
|
|
}
|
|
|
|
should_explode_on_damage ( item_ref )
|
|
{
|
|
switch ( item_ref )
|
|
{
|
|
case "item_alienpropanetank_mp":
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
spawn_item()
|
|
{
|
|
spawn_world_item( false, true );
|
|
}
|
|
|
|
spawn_loot( item_owner )
|
|
{
|
|
spawn_world_item( true, false );
|
|
|
|
self.item_ent.loot_owner = item_owner;
|
|
level.collectibles_lootcount++;
|
|
|
|
self thread loot_collection_timeout();
|
|
// self thread item_fx();
|
|
}
|
|
|
|
loot_collection_timeout()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "deleted" );
|
|
|
|
self.loot_collection_timeout = 5; // time out in seconds
|
|
|
|
while ( self.loot_collection_timeout )
|
|
{
|
|
wait 1;
|
|
self.loot_collection_timeout--;
|
|
}
|
|
}
|
|
|
|
|
|
clear_item_pickup()
|
|
{
|
|
self endon( "disconnect" );
|
|
wait ( 1 );
|
|
self.picking_up_item = false;
|
|
}
|
|
|
|
// item_pickup_listener( touch pickup )
|
|
item_pickup_listener()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "timedout" ); // only by loot items
|
|
level endon ( "game_ended" );
|
|
|
|
while ( true )
|
|
{
|
|
self.use_ent waittill( "trigger", owner );
|
|
|
|
owner notify ( "cancel_watch" ); //cancel dpad listener
|
|
owner notify( "kill_spendhint"); //kill the potential hint for throwing a deployable
|
|
|
|
owner.picking_up_item = true;
|
|
owner thread clear_item_pickup();
|
|
|
|
if ( owner [[ get_func_cangive( self.item_ref ) ]]( self ) )
|
|
{
|
|
// TODO: get new sound for MP
|
|
//IPrintLnBold( self.item_ref );
|
|
switch ( self.item_ref )
|
|
{
|
|
case "item_alienpropanetank_mp":
|
|
//IPrintLnBold( "alien case" );
|
|
owner playLocalSound( "weap_pickup_propanetank_plr" );
|
|
break;
|
|
default:
|
|
//IPrintLnBold( "default case" );
|
|
owner PlayLocalSound( "extinction_item_pickup" );
|
|
}
|
|
|
|
owner [[ get_func_give( self.item_ref ) ]]( self );
|
|
|
|
self.data[ "last_collector" ] = owner;
|
|
self.data[ "times_collected" ]++;
|
|
level.collectibles_worldcount[ self.item_ref ]++;
|
|
owner notify( "loot_pickup", self );
|
|
}
|
|
else
|
|
{
|
|
// failed to give
|
|
wait 0.05;
|
|
continue;
|
|
}
|
|
|
|
if ( self.data[ "persist" ] > 0 )
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
self remove_world_item();
|
|
|
|
// if respawns for finite times
|
|
if ( self.data[ "respawn_count" ] <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// wait for respawn
|
|
//wait level.collectibles[ self.item_ref ].respawn;
|
|
|
|
level waittill( "alien_cycle_ended" );
|
|
|
|
self.data[ "respawn_count" ]--;
|
|
// Disabling respawning
|
|
//spawn_random_item( false, self.item_ref );
|
|
}
|
|
// end, no loop
|
|
return;
|
|
}
|
|
}
|
|
|
|
// touch or use
|
|
loot_pickup_listener( item_owner, touch )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "timedout" ); // only by loot items
|
|
level endon ( "game_ended" );
|
|
|
|
isLoot = is_collectible_loot( self.item_ref );
|
|
|
|
if( !isdefined( touch ) )
|
|
touch = false;
|
|
|
|
// if not touch based, we apply press (X) use based
|
|
if ( !touch )
|
|
{
|
|
make_item_ent_useable( self.item_ent, get_item_desc( self.item_ref ) );
|
|
}
|
|
|
|
while ( true )
|
|
{
|
|
self.use_ent waittill( "trigger", owner );
|
|
|
|
if ( !isdefined( owner ) || !isplayer( owner ) )
|
|
{
|
|
wait 0.05;
|
|
continue;
|
|
}
|
|
|
|
if ( owner [[ get_func_cangive( self.item_ref ) ]]( self ) )
|
|
{
|
|
// TODO: get new sound for MP
|
|
owner PlayLocalSound( "extinction_item_pickup" );
|
|
|
|
owner thread [[ get_func_give( self.item_ref ) ]]( self );
|
|
owner notify( "loot_pickup", self );
|
|
}
|
|
else
|
|
{
|
|
// failed to give
|
|
wait 0.05;
|
|
continue;
|
|
}
|
|
|
|
/#
|
|
if ( getdvarint( "debug_collectibles" ) == 1
|
|
&& isLoot
|
|
&& isdefined( item_owner )
|
|
&& item_owner != owner
|
|
&& isplayer( item_owner )
|
|
&& isplayer( owner ) )
|
|
{
|
|
IPrintLn( owner.name + " took " + item_owner.name + "'s loot [" + self.item_ref + "]" );
|
|
}
|
|
#/
|
|
|
|
if ( self.data[ "persist" ] > 0 )
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
self remove_loot();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// removes loot if not picked up in time
|
|
loot_pickup_timeout( owner, loot_timeout )
|
|
{
|
|
self endon( "death" );
|
|
level endon ( "game_ended" );
|
|
|
|
countdown = loot_timeout;
|
|
while ( countdown )
|
|
{
|
|
wait 1;
|
|
countdown--;
|
|
}
|
|
|
|
/#
|
|
if ( getdvarint( "debug_collectibles" ) == 1 )
|
|
{
|
|
player_name = "unknown player";
|
|
if ( isdefined( owner ) && isplayer( owner ) && isdefined( owner.name ) )
|
|
player_name = owner.name;
|
|
|
|
iprintln( player_name + "'s loot [" + self.item_ref + "] timed out" );
|
|
}
|
|
#/
|
|
|
|
if ( self.data[ "persist" ] > 0 )
|
|
{
|
|
self notify( "timedout" );
|
|
self remove_world_item();
|
|
}
|
|
}
|
|
|
|
// remove loot, world model, and touch trigger
|
|
remove_loot()
|
|
{
|
|
remove_world_item();
|
|
level.collectibles_lootcount--;
|
|
}
|
|
|
|
remove_world_item()
|
|
{
|
|
if ( alien_mode_has( "outline" ) )
|
|
maps\mp\alien\_outline_proto::remove_from_outline_watch_list ( self.item_ent );
|
|
|
|
self.item_ent delete();
|
|
|
|
if ( isdefined( self.use_ent ) ) // use_ent could be different from item_ent
|
|
self.use_ent delete();
|
|
}
|
|
|
|
item_min_distance_from_players()
|
|
{
|
|
return !any_player_nearby( self.origin, ITEM_MIN_SPAWN_DISTANCE_SQR );
|
|
}
|
|
|
|
is_item( ref )
|
|
{
|
|
return IsSubStr( ref, "item" );
|
|
}
|
|
|
|
|
|
collectibles_table_init( index_start, index_end )
|
|
{
|
|
// populate table
|
|
for ( i = index_start; i < index_end; i++ )
|
|
{
|
|
ref = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_REF );
|
|
if ( ref == "" )
|
|
break;
|
|
|
|
item = SpawnStruct();
|
|
item.index = i;
|
|
item.ref = ref;
|
|
item.model = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_MODEL );
|
|
item.name = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_NAME );
|
|
item.desc = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_DESC );
|
|
|
|
item.count = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_COUNT ) );
|
|
item.ownership = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_OWNERSHIP );
|
|
item.respawn = float( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_RESPAWN ) );
|
|
item.respawn_max = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_RESPAWN_MAX ) );
|
|
item.vis = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_VIS ) );
|
|
item.unlock = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_UNLOCK ) );
|
|
item.player_max = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_PLAYER_MAX ) );
|
|
item.persist = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_PERSIST ) );
|
|
item.cost = int( TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_COST ) );
|
|
item.cost_display = TableLookup( level.alien_collectibles_table, TABLE_ITEM_INDEX, i, TABLE_ITEM_COST );
|
|
item.func_give = get_func_give( item.ref );
|
|
item.func_cangive = get_func_cangive( item.ref );
|
|
|
|
item.isLoot = is_collectible_loot( item.ref );
|
|
item.isWeapon = is_collectible_weapon( item.ref );
|
|
item.isItem = is_collectible_item( item.ref );
|
|
|
|
if ( is_chaos_mode() )
|
|
item.cost = 0;
|
|
|
|
// data structs, not world item structs
|
|
level.collectibles[ item.ref ] = item;
|
|
}
|
|
}
|
|
|
|
is_collectible_loot( item_ref )
|
|
{
|
|
if ( isdefined( level.collectibles )
|
|
&& isdefined( level.collectibles[ item_ref ] )
|
|
&& isdefined( level.collectibles[ item_ref ].isLoot )
|
|
)
|
|
{
|
|
return level.collectibles[ item_ref ].isLoot;
|
|
}
|
|
else
|
|
{
|
|
return ( GetSubStr( item_ref, 0, 5 ) == "loot_" );
|
|
}
|
|
}
|
|
|
|
is_collectible_weapon( item_ref )
|
|
{
|
|
if ( isdefined( level.collectibles )
|
|
&& isdefined( level.collectibles[ item_ref ] )
|
|
&& isdefined( level.collectibles[ item_ref ].isWeapon )
|
|
)
|
|
{
|
|
return level.collectibles[ item_ref ].isWeapon;
|
|
}
|
|
else
|
|
{
|
|
return ( GetSubStr( item_ref, 0, 7 ) == "weapon_" );
|
|
}
|
|
}
|
|
|
|
is_collectible_item( item_ref )
|
|
{
|
|
if ( isdefined( level.collectibles )
|
|
&& isdefined( level.collectibles[ item_ref ] )
|
|
&& isdefined( level.collectibles[ item_ref ].isItem )
|
|
)
|
|
{
|
|
return level.collectibles[ item_ref ].isItem;
|
|
}
|
|
else
|
|
{
|
|
return ( GetSubStr( item_ref, 0, 5 ) == "item_" );
|
|
}
|
|
}
|
|
|
|
item_exist( ref )
|
|
{
|
|
return isdefined( level.collectibles[ ref ] );
|
|
}
|
|
|
|
get_item_desc( ref )
|
|
{
|
|
return level.collectibles[ ref ].desc;
|
|
}
|
|
|
|
get_item_name( ref )
|
|
{
|
|
return level.collectibles[ ref ].name;
|
|
}
|
|
|
|
get_maxstock( ref )
|
|
{
|
|
return level.collectibles[ ref ].player_max;
|
|
}
|
|
|
|
// ====================================
|
|
// give and cangive functions
|
|
// ====================================
|
|
|
|
give_default( item )
|
|
{
|
|
return;
|
|
}
|
|
|
|
cangive_default( item )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
get_func_give( ref )
|
|
{
|
|
AssertEx( IsDefined( ref ) && ref !="" , "The item ref name must be defined and not empty string." );
|
|
|
|
if ( item_exist( ref ) )
|
|
return level.collectibles[ ref ].func_give;
|
|
|
|
func_give = ::give_default;
|
|
|
|
switch ( ref )
|
|
{
|
|
|
|
case "item_alienpropanetank_mp":
|
|
func_give = ::give_throwable_weapon;
|
|
break;
|
|
|
|
|
|
default:
|
|
//AssertMsg( "Unhandled item: " + ref + " is looking for func_give" );
|
|
break;
|
|
}
|
|
|
|
if ( strtok( ref, "_" )[ 0 ] == "weapon" )
|
|
{
|
|
func_give = ::give_weapon;
|
|
}
|
|
|
|
return func_give;
|
|
}
|
|
|
|
get_func_cangive( ref )
|
|
{
|
|
AssertEx( IsDefined( ref ) && ref !="" , "The item ref name must be defined and not empty string." );
|
|
|
|
if ( item_exist( ref ) )
|
|
return level.collectibles[ ref ].func_cangive;
|
|
|
|
func_cangive = ::cangive_default;
|
|
|
|
switch ( ref )
|
|
{
|
|
|
|
case "item_alienpropanetank_mp":
|
|
func_cangive = ::cangive_throwable_weapon;
|
|
break;
|
|
|
|
default:
|
|
//AssertMsg( "Unhandled item: " + ref + " is looking for func_give" );
|
|
break;
|
|
}
|
|
|
|
if ( strtok( ref, "_" )[ 0 ] == "weapon" )
|
|
{
|
|
func_cangive = ::cangive_weapon;
|
|
}
|
|
|
|
return func_cangive;
|
|
}
|
|
|
|
// ====================================
|
|
// [weapons]
|
|
// ====================================
|
|
give_weapon( item, is_locker_weapon )
|
|
{
|
|
should_take_weapon = undefined;
|
|
ref = item.item_ref;
|
|
weapon_ref = getsubstr( ref, 7 ); // to remove "weapon_"
|
|
cost = item.data[ "cost" ];
|
|
|
|
if ( self perk_GetPistolOverkill() == false )
|
|
{
|
|
max_primaries = 2;
|
|
}
|
|
else
|
|
{
|
|
max_primaries = 3;
|
|
}
|
|
|
|
if ( isDefined( self.numAdditionalPrimaries ) ) //special primary weapnons that should not count towards max primaries
|
|
{
|
|
max_primaries += self.numAdditionalPrimaries;
|
|
}
|
|
|
|
base_weapon = getRawBaseWeaponName( weapon_ref );
|
|
has_special_ammo = self player_has_specialized_ammo( base_weapon );
|
|
current_attachments = [];
|
|
|
|
if( !self has_weapon_variation( weapon_ref ) && !self has_pistols_only_relic_and_no_deployables() )
|
|
{
|
|
cur_primary_weapon = self get_replaceable_weapon();
|
|
|
|
if ( IsDefined( cur_primary_weapon ) )
|
|
{
|
|
cur_primary_clip = self GetWeaponAmmoClip( cur_primary_weapon );
|
|
cur_primary_stock = self GetWeaponAmmoStock( cur_primary_weapon );
|
|
should_take_weapon = true;
|
|
//Take current weapon from player
|
|
if ( is_chaos_mode() )
|
|
{
|
|
replaceable_weapon = cur_primary_weapon;
|
|
self TakeWeapon( replaceable_weapon );
|
|
}
|
|
|
|
if ( !self.hasRiotShieldequipped && cur_primary_weapon != "aliensoflam_mp" )
|
|
{
|
|
if ( ( self hasweapon( "aliensoflam_mp" ) || self.hasRiotShield || self has_special_weapon() ) && self GetWeaponsListPrimaries().size < (max_primaries + 1 ) )
|
|
{
|
|
should_take_weapon = false;
|
|
}
|
|
|
|
if ( self hasweapon( "aliensoflam_mp" ) && self.hasRiotshield && self GetWeaponsListPrimaries().size < (max_primaries + 2 ) )
|
|
{
|
|
should_take_weapon = false;
|
|
}
|
|
|
|
if ( isDefined( level.custom_give_weapon_func ) )
|
|
{
|
|
should_take_weapon_test = [[level.custom_give_weapon_func]]( max_primaries );
|
|
if ( isDefined( should_take_weapon_test ) )
|
|
should_take_weapon = should_take_weapon_test;
|
|
}
|
|
|
|
if ( should_take_weapon )
|
|
{
|
|
self TakeWeapon( cur_primary_weapon );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( isDefined( cur_primary_weapon) && getWeaponClass( cur_primary_weapon ) != "weapon_pistol" && should_take_weapon == true )
|
|
current_attachments = GetWeaponAttachments( cur_primary_weapon );
|
|
// cost the purchaser
|
|
self take_player_currency( cost, false, "weapon", weapon_ref );
|
|
|
|
if ( is_chaos_mode() )
|
|
maps\mp\alien\_chaos::update_weapon_pickup( self, weapon_ref );
|
|
else if(!is_true(is_locker_weapon) && isDefined( cur_primary_weapon ) )
|
|
weapon_ref = self return_weapon_with_like_attachments( weapon_ref, current_attachments );
|
|
else if( is_true(is_locker_weapon) )
|
|
weapon_ref = self ark_attachment_transfer_to_locker_weapon( weapon_ref, current_attachments, should_take_weapon );
|
|
|
|
self giveweapon( weapon_ref );
|
|
self SwitchToWeapon( weapon_ref );
|
|
if ( !is_true( is_locker_weapon ) )
|
|
self scale_ammo_based_on_nerf( weapon_ref );
|
|
self give_pistol_ammo_if_nerf_active();
|
|
level notify( "new_weapon_purchased",self );
|
|
}
|
|
else
|
|
{
|
|
if ( !self HasWeapon ( weapon_ref ) ) // player has a variation of this weapon
|
|
weapon_ref = get_weapon_ref ( weapon_ref );
|
|
|
|
if ( self has_pistols_only_relic_and_no_deployables() )
|
|
weapon_ref = get_current_pistol();
|
|
|
|
assert ( isDefined ( weapon_ref ) );
|
|
|
|
max_ammo = WeaponMaxAmmo( weapon_ref );
|
|
limited_ammo_scalar = self maps\mp\alien\_prestige::prestige_getMinAmmo();
|
|
scaled_ammo = int( limited_ammo_scalar * max_ammo);
|
|
current_ammo = self GetAmmoCount( weapon_ref );
|
|
|
|
if ( current_ammo < scaled_ammo )
|
|
{
|
|
if ( has_special_ammo )
|
|
return;
|
|
self GiveMaxAmmo( weapon_ref );
|
|
self SwitchToWeapon( weapon_ref );
|
|
self SetWeaponAmmoStock( weapon_ref, scaled_ammo );
|
|
|
|
if ( !self has_pistols_only_relic_and_no_deployables() )
|
|
{
|
|
self give_pistol_ammo_if_nerf_active();
|
|
}
|
|
else
|
|
{
|
|
cost = level.pistol_ammo_cost;
|
|
}
|
|
self clearLowerMessage ( "ammo_warn" );
|
|
self setLowerMessage( "ammo_taken",&"ALIEN_COLLECTIBLES_DEPLOYABLE_AMMO_TAKEN",3 );
|
|
// cost the purchaser
|
|
self take_player_currency( cost, false, "weapon", weapon_ref );
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( !has_special_ammo )
|
|
{
|
|
self clearLowerMessage ( "ammo_warn" );
|
|
self setLowerMessage( "ammo_taken",&"ALIEN_COLLECTIBLES_AMMO_MAX",3 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scale_ammo_based_on_nerf( weapon_ref )
|
|
{
|
|
//checks the nerf for min_ammo and returns the amount
|
|
nerf_min_ammo_scalar = self maps\mp\alien\_deployablebox_functions::check_for_nerf_min_ammo();
|
|
if ( nerf_min_ammo_scalar != 1.0 )
|
|
{
|
|
max_stock = WeaponMaxAmmo( weapon_ref );
|
|
self SetWeaponAmmoStock( weapon_ref, int( max_stock * nerf_min_ammo_scalar ) );
|
|
}
|
|
}
|
|
|
|
give_pistol_ammo_if_nerf_active( )
|
|
{
|
|
//give .25 of max ammo to pistols since the player cant throw ammo crates down with this nerf
|
|
if ( self maps\mp\alien\_prestige::prestige_getNoDeployables() == 1 )
|
|
{
|
|
weap_list = self GetWeaponsListPrimaries();
|
|
foreach ( weap in weap_list )
|
|
{
|
|
weap_class = getWeaponClass( weap );
|
|
if ( weap_class == "weapon_pistol" )
|
|
{
|
|
max_stock = WeaponMaxAmmo( weap );
|
|
scaled_ammo = int( max_stock * 0.25 );
|
|
cur_ammo = self GetAmmoCount( weap );
|
|
if ( scaled_ammo > cur_ammo )
|
|
self SetWeaponAmmoStock( weap, scaled_ammo ); //give .25 of max ammo to pistols since the player cant throw ammo crates down with this nerf
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
get_replaceable_weapon()
|
|
{
|
|
// if holding more than one weapon remove the current
|
|
primary_weapons = self GetWeaponsListPrimaries();
|
|
|
|
if ( is_chaos_mode() )
|
|
{
|
|
foreach ( weapon in primary_weapons )
|
|
{
|
|
weap_class = getWeaponClass( weapon );
|
|
switch ( weap_class )
|
|
{
|
|
case "weapon_smg":
|
|
case "weapon_assault":
|
|
case "weapon_sniper":
|
|
case "weapon_dmr":
|
|
case "weapon_lmg":
|
|
case "weapon_shotgun":
|
|
case "weapon_projectile":
|
|
return ( weapon );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( self perk_GetPistolOverkill() == false )
|
|
{
|
|
max_primaries = 2;
|
|
}
|
|
else
|
|
{
|
|
max_primaries = 3;
|
|
}
|
|
|
|
if ( isDefined( self.numAdditionalPrimaries ) ) //special primary weapnons that should not count towards max primaries
|
|
{
|
|
max_primaries += self.numAdditionalPrimaries;
|
|
}
|
|
|
|
if ( primary_weapons.size >= max_primaries )
|
|
{
|
|
current_weapon = self GetCurrentWeapon();
|
|
if ( WeaponInventoryType( current_weapon ) == "altmode" )
|
|
{
|
|
current_weapon = get_weapon_name_from_alt( current_weapon );
|
|
}
|
|
// if current weapon held is a weapon that can be taken away
|
|
if ( IsDefined( current_weapon ) && WeaponInventoryType( current_weapon ) == "primary" )
|
|
{
|
|
return current_weapon;
|
|
}
|
|
else
|
|
{
|
|
// find a primary weapon to take away
|
|
weapon_list = self GetWeaponsList( "primary" );
|
|
foreach ( weapon in weapon_list )
|
|
{
|
|
if ( WeaponClass( weapon ) != "item" && WeaponClass( weapon ) != "pistol" && WeaponType( weapon ) != "riotshield" )
|
|
return weapon;
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
get_weapon_name_from_alt( weapon )
|
|
{
|
|
if ( WeaponInventoryType( weapon ) != "altmode" )
|
|
{
|
|
assertmsg( "Get weapon name from alt called on non alt weapon." );
|
|
return weapon;
|
|
}
|
|
|
|
// Assume alt weapon names are always in the format
|
|
// of: "alt_iw5_scar_mp_m230"
|
|
return GetSubStr( weapon, 4 );
|
|
}
|
|
|
|
cangive_weapon( item )
|
|
{
|
|
ref = item.item_ref;
|
|
weapon_ref = getsubstr( ref, 7 ); // to remove "weapon_"
|
|
|
|
cur_weapons = self GetWeaponsListPrimaries();
|
|
currentweapon = self GetCurrentWeapon();
|
|
currentweapon_class = self getWeaponClass( currentweapon );
|
|
|
|
if ( is_chaos_mode() )
|
|
{
|
|
if ( maps\mp\alien\_chaos::is_weapon_recently_picked_up( self, weapon_ref ) || self has_special_weapon() )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
|
|
//check for the pistol perk that allows 3 main weapons
|
|
if ( self perk_GetPistolOverkill() == false )
|
|
{
|
|
max_primaries = 2;
|
|
}
|
|
else
|
|
{
|
|
max_primaries = 3;
|
|
}
|
|
|
|
if ( isDefined( self.numAdditionalPrimaries ) )
|
|
{
|
|
max_primaries += self.numAdditionalPrimaries;
|
|
}
|
|
|
|
//Begin Checks to allow weapons to be purchased
|
|
if ( self maps\mp\alien\_prestige::prestige_getPistolsOnly() == 1 )
|
|
{
|
|
if ( self maps\mp\alien\_prestige::prestige_getNoDeployables() != 1 )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_NERFED", 3 );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( self IsSwitchingWeapon() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon == "none" ) //you should never be able to buy a weapon ( or anything ) when your current weapon is "none"
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( isDefined ( level.custom_cangive_weapon_func ) )
|
|
{
|
|
if ( ![[level.custom_cangive_weapon_func]]( cur_weapons, currentweapon, currentweapon_class, max_primaries ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
if ( self has_special_weapon() )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon_class == "weapon_pistol" && cur_weapons.size >= max_primaries && !self.hasRiotShield && !self HasWeapon( "aliensoflam_mp" ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon_class == "weapon_pistol" && cur_weapons.size >= ( max_primaries + 1 ) && self.hasRiotShield && !self HasWeapon( "aliensoflam_mp" ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon_class == "weapon_pistol" && cur_weapons.size >= ( max_primaries + 1 ) && self hasweapon ( "aliensoflam_mp" ) && !self.hasRiotShield )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon_class == "weapon_pistol" && cur_weapons.size >= ( max_primaries + 2 ) && self.hasRiotShield && self HasWeapon( "aliensoflam_mp" ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon == "aliensoflam_mp" && cur_weapons.size >= ( max_primaries + 1 ) && !self.hasRiotShieldEquipped )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( currentweapon == "aliensoflam_mp" && cur_weapons.size >= ( max_primaries + 2 ) && self.hasRiotShieldEquipped )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( self.hasRiotShieldEquipped && cur_weapons.size >= ( max_primaries + 1 ) && !self HasWeapon( "aliensoflam_mp" ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( self.hasRiotShieldEquipped && cur_weapons.size >= ( max_primaries + 1 ) && self HasWeapon( "aliensoflam_mp" ) )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HAS_SPECIALWEAPON", 3 );
|
|
return false;
|
|
}
|
|
|
|
if ( !self is_holding_deployable() )
|
|
{
|
|
if ( self has_pistols_only_relic_and_no_deployables() )
|
|
has_enough = player_has_enough_currency( level.pistol_ammo_cost );
|
|
else
|
|
has_enough = player_has_enough_currency( item.data[ "cost" ] );
|
|
|
|
if ( !has_enough )
|
|
{
|
|
self clearLowerMessage ( "ammo_warn" );
|
|
self setLowerMessage( "no_money", &"ALIEN_COLLECTIBLES_NO_MONEY", 3 );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 );
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
give_throwable_weapon( item )
|
|
{
|
|
ref = item.item_ref;
|
|
item_ref = getsubstr( ref, 5 ); // to remove "item_"
|
|
|
|
self _giveWeapon( item_ref );
|
|
self SwitchToWeapon( item_ref );
|
|
self DisableWeaponSwitch();
|
|
self displayThrowMessage();
|
|
}
|
|
|
|
cangive_throwable_weapon( item )
|
|
{
|
|
ref = item.item_ref;
|
|
weapon_ref = getsubstr( ref, 5 ); // to remove "item_"
|
|
|
|
if ( self isChangingWeapon() || self is_holding_deployable() || self has_special_weapon() || self GetCurrentPrimaryWeapon() == "aliensoflam_mp" )
|
|
{
|
|
self setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 );
|
|
return false;
|
|
}
|
|
if( !self HasWeapon( weapon_ref ) && !self is_holding_deployable() && !self has_special_weapon() )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
//==================================================================================
|
|
// PROPANE TANK WEAPON
|
|
//==================================================================================
|
|
|
|
watchThrowableItems() //self = player
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
|
|
itemTeam = self.pers["team"];
|
|
|
|
while ( 1 )
|
|
{
|
|
self waittill( "grenade_fire",throwableitem, weapname );
|
|
if ( isGrenade ( weapname ) )
|
|
continue;
|
|
|
|
forward_direction = anglesToForward( self getPlayerAngles() );
|
|
spawnPos = bulletTrace( self geteye(), self geteye() + forward_direction * 64, true, self,false,false,true );
|
|
|
|
spawnPos["position"] = self geteye() + forward_direction * 30;
|
|
if ( spawnPos["fraction"] < 1 )
|
|
{
|
|
if ( weapname == "alienpropanetank_mp" )
|
|
{
|
|
spawnPos["position"] = self geteye() + forward_direction;
|
|
}
|
|
}
|
|
if ( isThrowableItem( weapname ) )
|
|
{
|
|
self TakeWeapon( weapname );
|
|
self EnableWeaponSwitch();
|
|
throwableitem delete();
|
|
level thread watchThrowableItemStopped( weapname, itemTeam, self getEye(), self getPlayerAngles(), self , spawnPos );
|
|
}
|
|
}
|
|
}
|
|
|
|
watchThrowableItemStopped( weapname, itemTeam, playerEye, playerAngles, owner , spawnPos )
|
|
{
|
|
level endon( "game_ended" );
|
|
|
|
waitframe(); // wait for the grenade item to be deleted
|
|
|
|
item_data = level.throwable_items[ weapname ];
|
|
|
|
forward_direction = anglesToForward( playerAngles );
|
|
spawnAngles = anglesToUp( playerAngles ); // temp. This is for the propane to fly out sideway. Need an university method
|
|
|
|
physics_model = spawn( "script_model", spawnPos["position"] );
|
|
physics_model.angles = vectorToAngles( spawnAngles );
|
|
physics_model setmodel( item_data.model );
|
|
physics_model.owner = owner;
|
|
|
|
physics_model endon( "death" );
|
|
|
|
add_to_thrown_entity_list( physics_model );
|
|
physics_model thread clean_up_on_death();
|
|
|
|
waitframe(); // wait for the angles to be set before applying physics
|
|
|
|
force = item_data.force ;
|
|
if ( spawnPos["fraction"] < 1 )
|
|
force = 5;
|
|
|
|
physics_model PhysicsLaunchServer( ( 0,0,0 ), forward_direction * force );
|
|
|
|
wait ( 0.5 ); // There is no good notify to wait for. "physics_finished" is too late.
|
|
|
|
physics_model thread explodeOnDamage( true );
|
|
|
|
if ( item_data.canBePickedUp )
|
|
{
|
|
make_item_ent_useable( physics_model, item_data.hintString );
|
|
physics_model thread [[item_data.pickUpFunc]]( weapname );
|
|
}
|
|
|
|
//blow up if too close to an IMS - stupid hacks
|
|
if ( !isDefined( level.placedIMS ) || level.placedIMS.size < 1 )
|
|
return;
|
|
|
|
distcheck = 24*24; // 24 unit check
|
|
foreach ( ims in level.placedIMS )
|
|
{
|
|
if ( DistanceSquared( physics_model.origin, ims.origin ) < distcheck )
|
|
physics_model notify( "damage", 100, physics_model.owner );
|
|
}
|
|
}
|
|
|
|
add_to_thrown_entity_list( item )
|
|
{
|
|
if ( alien_mode_has( "outline" ) )
|
|
maps\mp\alien\_outline_proto::add_to_outline_watch_list ( item, 0 );
|
|
|
|
level.thrown_entities[ level.thrown_entities.size ] = item;
|
|
}
|
|
|
|
clean_up_on_death()
|
|
{
|
|
level endon( "game_ended" );
|
|
self waittill( "death" );
|
|
if ( alien_mode_has( "outline" ) )
|
|
maps\mp\alien\_outline_proto::remove_from_outline_watch_list ( self );
|
|
|
|
level.thrown_entities = array_remove( level.thrown_entities, self );
|
|
}
|
|
|
|
propaneTankWatchUse( weapname )
|
|
{
|
|
level endon( "game_ended" );
|
|
self endon( "death" );
|
|
|
|
while ( true )
|
|
{
|
|
self waittill ( "trigger", player );
|
|
|
|
player notify( "cancel_watch" ); //cancel dpad listener
|
|
player notify( "kill_spendhint"); //kill the potential hint for throwing a deployable
|
|
player.picking_up_item = true;
|
|
player thread clear_item_pickup();
|
|
|
|
if ( !isPlayer ( player ) || player hasweapon( weapname ) )
|
|
{
|
|
continue;
|
|
}
|
|
if ( player is_holding_deployable() || player has_special_weapon() )
|
|
{
|
|
player setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 );
|
|
continue;
|
|
}
|
|
|
|
if ( player isChangingWeapon() || player GetCurrentPrimaryWeapon() == "aliensoflam_mp" )
|
|
{
|
|
player setLowerMessage( "cant_buy", &"ALIEN_COLLECTIBLES_PLAYER_HOLDING", 3 );
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
player playLocalSound( "weap_pickup_propanetank_plr" );
|
|
player _giveWeapon( weapname );
|
|
player SwitchToWeapon( weapname );
|
|
player DisableWeaponSwitch();
|
|
player displayThrowMessage();
|
|
self delete();
|
|
}
|
|
|
|
isThrowableItem( weaponName )
|
|
{
|
|
return isDefined( level.throwable_items[ weaponName ] );
|
|
}
|
|
|
|
displayThrowMessage()
|
|
{
|
|
self setLowerMessage( "throw_item", &"ALIEN_COLLECTIBLES_THROW_ITEM", 3 );
|
|
}
|
|
|
|
explodeOnDamage( should_explode_on_hive_explode )
|
|
{
|
|
self endon( "death" );
|
|
|
|
self setcandamage( true );
|
|
self.maxhealth = 100000;
|
|
self.health = self.maxhealth;
|
|
|
|
hive_exploded_propane = false;
|
|
while ( true )
|
|
{
|
|
self waittill( "damage", damage, attacker, direction_vec, point, type, modelName, tagName, partName, iDFlags, weapon );
|
|
|
|
if ( should_explode_on_hive_explode && is_hive_explosion( attacker, type ) )
|
|
{
|
|
hive_exploded_propane = true;
|
|
break;
|
|
}
|
|
|
|
if ( !isPlayer ( attacker ) && !isplayer ( attacker.owner ) )
|
|
continue;
|
|
|
|
if ( isDefined ( type ) && type == "MOD_MELEE" )
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if ( isDefined( attacker ) && isPlayer( attacker ) )
|
|
self.owner = attacker;
|
|
else if ( isDefined( attacker.owner ) && isPlayer( attacker.owner ) ) //for grenade turrets
|
|
self.owner = attacker.owner;
|
|
|
|
if ( isdefined( level.recent_propane_explosions ) && level.recent_propane_explosions > 4 )
|
|
PlayFx( level._effect[ "Propane_explosion_cheapest" ], drop_to_ground( self.origin, DROP_TO_GROUND_UP_DIST, DROP_TO_GROUND_PROPANE_TANK_DOWN_DIST ) );
|
|
else if ( isdefined( level.recent_propane_explosions ) && level.recent_propane_explosions > 2 )
|
|
PlayFx( level._effect[ "Propane_explosion_cheap" ], drop_to_ground( self.origin, DROP_TO_GROUND_UP_DIST, DROP_TO_GROUND_PROPANE_TANK_DOWN_DIST ) );
|
|
else
|
|
PlayFx( level._effect[ "Propane_explosion" ], drop_to_ground( self.origin, DROP_TO_GROUND_UP_DIST, DROP_TO_GROUND_PROPANE_TANK_DOWN_DIST ) );
|
|
|
|
|
|
max_damage = 1000 * self.owner perk_GetTrapDamageScalar();
|
|
min_damage = 1000 * self.owner perk_GetTrapDamageScalar();
|
|
|
|
// rasing the damage origin so the propane tank model isn't blocking the damage it gives, propane model height is 21 units
|
|
damage_origin = self.origin + ( 0, 0, 22 );
|
|
damage_radius = 256;
|
|
|
|
if ( hive_exploded_propane )
|
|
RadiusDamage( damage_origin, damage_radius, max_damage, min_damage, attacker, "MOD_EXPLOSIVE", "alienpropanetank_mp" );
|
|
else
|
|
RadiusDamage( damage_origin, damage_radius, max_damage, min_damage, self.owner, "MOD_EXPLOSIVE", "alienpropanetank_mp" );
|
|
|
|
self Playsound( "grenade_explode_metal" );
|
|
|
|
level thread fireCloudMonitor( self.owner, level.fireCloudDuration, self.origin );
|
|
|
|
level thread firecloudsfx( level.fireCloudDuration, self.origin );
|
|
|
|
if ( alien_mode_has( "outline" ) )
|
|
maps\mp\alien\_outline_proto::remove_from_outline_watch_list ( self );
|
|
|
|
self delete();
|
|
}
|
|
|
|
is_hive_explosion( attacker, type )
|
|
{
|
|
if ( !isDefined( attacker ) || !isDefined( attacker.classname ) )
|
|
return false;
|
|
|
|
return ( attacker.classname == "scriptable" && type == "MOD_EXPLOSIVE" );
|
|
}
|
|
|
|
firecloudsfx( duration, position )
|
|
{
|
|
soundorg1 = spawn( "script_origin", position );
|
|
soundorg2 = spawn( "script_origin", position );
|
|
wait 0.01;
|
|
soundorg1 PlayLoopSound( "fire_trap_fire_lp" );
|
|
wait duration;
|
|
soundorg2 playsound( "fire_trap_fire_end_lp" );
|
|
wait 0.5;
|
|
soundorg1 stoploopsound();
|
|
wait 5;
|
|
soundorg1 delete();
|
|
soundorg2 delete();
|
|
}
|
|
|
|
fireCloudMonitor( attacker, duration, position )
|
|
{
|
|
fireCloudRadius = level.fireCloudRadius;
|
|
fireCloudHeight = level.fireCloudHeight;
|
|
fireCloudTickDamage = level.fireCloudTickDamage * ( attacker full_damage_scaler() );
|
|
fireCloudLingerTime = level.fireCloudLingerTime;
|
|
fireCloudPlayerTickDamage = level.fireCloudPlayerTickDamage;
|
|
fireCloudHiveTickDamage = level.fireCloudHiveTickDamage * ( attacker full_damage_scaler() );
|
|
|
|
// ==== init for tracking recent propane explosions ====
|
|
if ( !isdefined( level.recent_propane_explosions ) )
|
|
level.recent_propane_explosions = 0;
|
|
// add this to recent propane tank explosions
|
|
level.recent_propane_explosions++;
|
|
|
|
// spawn trigger radius for the effect areas
|
|
fireEffectArea = spawn( "trigger_radius", position, 0, fireCloudRadius, fireCloudHeight );
|
|
fireEffectArea.owner = attacker;
|
|
|
|
gasFire = SpawnFx( level._effect[ "Fire_Cloud" ], position );
|
|
triggerFx( gasFire );
|
|
|
|
fireTotalTime = 0.0; // keeps track of the total time the fire cloud has been "alive"
|
|
fireTickTime = 0.25; // sampling rate
|
|
fireInitialWait = 1; // wait this long before the cloud starts ticking for damage
|
|
fireTickCounter = 0; // just an internal counter to count fire damage ticks
|
|
|
|
wait(fireInitialWait );
|
|
fireTotalTime +=fireInitialWait;
|
|
|
|
duration = duration * ( attacker perk_GetTrapDurationScalar() );
|
|
level thread maps\mp\alien\_utility::mark_dangerous_nodes( position, fireCloudRadius, duration );
|
|
|
|
while ( fireTotalTime < duration )
|
|
{
|
|
//apply damage to aliens in the fire cloud
|
|
foreach ( agent in level.agentArray )
|
|
{
|
|
if ( IsDefined( agent.isActive )
|
|
&& agent.isActive
|
|
&& isalive( agent )
|
|
&& ( agent istouching( fireEffectArea ) )
|
|
&& ( !isdefined( agent.burning ) || !agent.burning ) )
|
|
{
|
|
agent thread fire_cloud_burn_alien( fireCloudTickDamage, attacker, fireCloudLingerTime , fireEffectArea );
|
|
}
|
|
}
|
|
|
|
//apply damage to players in the fire cloud
|
|
// [IMPORTANT] Player's tick rate is 1 second fixed. Fixed due to excessive view kick of fast ticks
|
|
foreach ( player in level.players )
|
|
{
|
|
if( isalive( player ) && player istouching( fireEffectArea ) && ( !isdefined( player.burning ) || !player.burning ) )
|
|
player thread fire_cloud_burn_player( fireCloudPlayerTickDamage );
|
|
}
|
|
|
|
if ( isDefined( level.thrown_entities ) )
|
|
{
|
|
//apply damage to propane tanks in the fire cloud
|
|
foreach ( item in level.thrown_entities )
|
|
{
|
|
if( isDefined( item ) && item istouching( fireEffectArea ) )
|
|
item DoDamage ( fireCloudPlayerTickDamage, position, attacker );
|
|
}
|
|
}
|
|
|
|
//apply damage to blocker hive
|
|
if ( isdefined( level.blocker_hives ) )
|
|
{
|
|
foreach ( blocker_hive in level.blocker_hives )
|
|
{
|
|
blocker_hive_struct = maps\mp\alien\_spawnlogic::get_blocker_hive( blocker_hive );
|
|
if ( !isdefined( blocker_hive_struct ) )
|
|
continue;
|
|
|
|
attackable_ent = blocker_hive_struct.attackable_ent;
|
|
if( isDefined( attackable_ent ) && distance( attackable_ent.origin, position ) <= fireCloudRadius * 0.8 )
|
|
{
|
|
if ( IsDefined( attackable_ent.health ) && attackable_ent.health > 0 && ( !isdefined( attackable_ent.burn_mitigation ) || !attackable_ent.burn_mitigation ) )
|
|
{
|
|
attackable_ent thread burn_mitigation( 0.2 );
|
|
attackable_ent DoDamage ( fireCloudHiveTickDamage, position, attacker );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wait( fireTickTime );
|
|
fireTotalTime += fireTickTime;
|
|
fireTickCounter += 1;
|
|
}
|
|
|
|
// clear this from recent propane tank explosions
|
|
level.recent_propane_explosions = int( max( 0, level.recent_propane_explosions - 1 ) ); // remove
|
|
|
|
//clean up
|
|
fireEffectArea delete();
|
|
gasfire delete();
|
|
}
|
|
|
|
burn_mitigation( duration )
|
|
{
|
|
self notify( "burn_mitigating" );
|
|
self endon( "burn_mitigating" );
|
|
|
|
self endon( "death" );
|
|
self.burn_mitigation = true;
|
|
wait duration;
|
|
self.burn_mitigation = false;
|
|
}
|
|
|
|
full_damage_scaler()
|
|
{
|
|
// self is player
|
|
return ( self perk_GetTrapDamageScalar() ) * level.alien_health_per_player_scalar[ level.players.size ];
|
|
}
|
|
|
|
// tick is forced as 1 second interval to prevent spam of small damages
|
|
fire_cloud_burn_player( tick_damage )
|
|
{
|
|
// self is victim player
|
|
|
|
// only one instance of burn
|
|
self notify( "fire_cloud_burning" );
|
|
self endon( "fire_cloud_burning" );
|
|
self endon( "last_stand" );
|
|
|
|
self.burning = true;
|
|
self thread reset_burn_on_death();
|
|
|
|
if( !self maps\mp\alien\_perk_utility::has_perk( "perk_rigger", [ 0,1,2,3,4 ] ) )
|
|
self DoDamage( tick_damage, self.origin );
|
|
wait 1;
|
|
self.burning = undefined;
|
|
}
|
|
|
|
fire_cloud_burn_alien( interval_damage, attacker, duration, fire_damage_trigger )
|
|
{
|
|
// self is victim alien
|
|
|
|
// only one instance of burn
|
|
self notify( "fire_cloud_burning" );
|
|
self endon( "fire_cloud_burning" );
|
|
self endon( "death" );
|
|
|
|
if ( !isdefined( duration ) )
|
|
duration = 6;
|
|
|
|
self.burning = true;
|
|
self thread reset_burn_on_death();
|
|
|
|
self maps\mp\alien\_alien_fx::alien_fire_on();
|
|
|
|
elapsed_time = 0;
|
|
while ( elapsed_time < duration )
|
|
{
|
|
// create marker for passing in damage type
|
|
attacker.burning_victim = true;
|
|
|
|
if ( isDefined ( fire_damage_trigger ) ) //in case the damage trigger gets deleted during this while loop
|
|
self DoDamage( interval_damage, self.origin, attacker, fire_damage_trigger );
|
|
else
|
|
self DoDamage( interval_damage, self.origin, attacker );
|
|
|
|
elapsed_time += 1;
|
|
wait 1;
|
|
}
|
|
|
|
self maps\mp\alien\_alien_fx::alien_fire_off();
|
|
|
|
self.burning = undefined;
|
|
}
|
|
|
|
reset_burn_on_death()
|
|
{
|
|
self waittill_any( "death", "last_stand" );
|
|
self.burning = undefined;
|
|
}
|
|
|
|
|
|
advance_to_next_area()
|
|
{
|
|
current_area = get_current_area_name();
|
|
remove_items_in_area( current_area );
|
|
remove_thrown_entity_in_area( current_area );
|
|
if ( isDefined( level.leave_area_func ) )
|
|
[[level.leave_area_func]]( current_area );
|
|
|
|
inc_current_area_index();
|
|
next_area = get_current_area_name();
|
|
spawn_items_in_area( next_area );
|
|
if ( isDefined( level.enter_area_func ) )
|
|
[[level.enter_area_func]]( next_area );
|
|
}
|
|
|
|
remove_thrown_entity_in_area( area )
|
|
{
|
|
foreach( thrown_entity in level.thrown_entities )
|
|
{
|
|
entity_in_areas = thrown_entity get_item_areas();
|
|
|
|
if ( array_contains( entity_in_areas, area ) )
|
|
{
|
|
level.thrown_entities = array_remove( level.thrown_entities, thrown_entity );
|
|
if ( alien_mode_has( "outline" ) )
|
|
maps\mp\alien\_outline_proto::remove_from_outline_watch_list ( thrown_entity );
|
|
|
|
thrown_entity delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
isGrenade( weapName )
|
|
{
|
|
weapClass = weaponClass( weapName );
|
|
weapType = weaponInventoryType( weapName );
|
|
|
|
if ( weapClass != "grenade" )
|
|
return false;
|
|
|
|
if ( weapType != "offhand" )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
has_weapon_variation( weapon_name )
|
|
{
|
|
primaries = self GetWeaponsListPrimaries();
|
|
|
|
foreach ( weapon in primaries )
|
|
{
|
|
baseweapon = get_base_weapon_name( weapon );
|
|
if ( IsSubStr ( weapon_name,baseweapon ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
get_weapon_ref ( weapon_name )
|
|
{
|
|
primaries = self GetWeaponsListPrimaries();
|
|
|
|
foreach ( weapon in primaries )
|
|
{
|
|
baseweapon = get_base_weapon_name( weapon );
|
|
if ( IsSubStr ( weapon_name,baseweapon ) )
|
|
{
|
|
return weapon;
|
|
}
|
|
}
|
|
return undefined;
|
|
|
|
}
|
|
|
|
check_for_player_near_weapon()
|
|
{
|
|
self endon( "disconnect" );
|
|
check_distance = 145*145;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( (isDefined ( self.inlaststand ) && self.inlaststand ) || isDefined ( self.usingRemote ) || is_true ( self.isCarrying ) )
|
|
{
|
|
wait ( .25 );
|
|
continue;
|
|
}
|
|
|
|
foreach ( index, item in level.outline_weapon_watch_list )
|
|
{
|
|
if ( isDefined( item ) && distancesquared( item.origin, self.origin ) < check_distance )
|
|
{
|
|
if ( !isDefined( item.targetname ) )
|
|
self setLowerMessage( "ammo_warn",&"ALIENS_PRESTIGE_PISTOLS_ONLY_AMMO_DIST",undefined,10 );
|
|
while( self player_should_see_ammo_message( item,check_distance,false ) )
|
|
{
|
|
wait ( .25 );
|
|
}
|
|
}
|
|
self clearLowerMessage ( "ammo_warn" );
|
|
}
|
|
wait 1;
|
|
}
|
|
}
|
|
|
|
player_should_see_ammo_message( item ,check_distance, ignore_carrying_check )
|
|
{
|
|
if ( distancesquared( item.origin, self.origin ) > check_distance )
|
|
return false;
|
|
|
|
if ( self.inlaststand )
|
|
return false;
|
|
|
|
if ( isDefined ( self.usingRemote ) )
|
|
return false;
|
|
|
|
if ( is_true ( ignore_carrying_check ) )
|
|
return true;
|
|
else if ( is_true ( self.isCarrying ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|