/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include #include "debugconsole/console.h" #include "freespace.h" #include "globalincs/linklist.h" #include "hud/hud.h" #include "hud/hudlock.h" #include "hud/hudtarget.h" #include "iff_defs/iff_defs.h" #include "io/timer.h" #include "network/multi.h" #include "network/multimsgs.h" #include "object/object.h" #include "parse/parselo.h" #include "ship/ship.h" #include "weapon/emp.h" #include "weapon/weapon.h" // ---------------------------------------------------------------------------------------------------- // EMP EFFECT DEFINES/VARS // // intensity of the playing effect float Emp_intensity = -1.0f; // current intensity of the EMP effect (normalized to EMP_INTENSITY_MAX) float Emp_decr = 0.0f; // how much to decrement the effect per second // timestamp until we should randomly choose another target int Emp_wacky_target_timestamp = -1; // max time we'll disrupt turrets on big ships #define MAX_TURRET_DISRUPT_TIME 7500 // conventient for determining if EMP is active #define EMP_ACTIVE_LOCAL() (Emp_intensity > 0.0f) // for keeping track of messed up text #define EMP_WACKY_TEXT_LEN 256 typedef struct wacky_text { char str[EMP_WACKY_TEXT_LEN]; int stamp; } wacky_text; wacky_text Emp_wacky_text[NUM_TEXT_STAMPS]; // for randomly inserting characters #define NUM_RANDOM_CHARS 51 char Emp_random_char[NUM_RANDOM_CHARS] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', '4', 'h', '8', '_', '$', ')', '-', '~', 'u', 'q', '.', 'x', 'h', '&', '%', '*', '1', '3', 't', 'h', 'o', 'p', '@', 'h', 'i','v', '+', '=', '|', '{', '}', ':', ';', '^', 'l', 'z', 'u', 'v', '<', '>', '?', '5', '8' }; // EMP EFFECTS ON PLAYERS ----- // 1.) Lose target lock if any, along with ability to lock on // 2.) Display EMP-BLAST icon or something (maybe flash it) // 3.) at wacky intervals, target random ships (which he cannot lock on) // 4.) Randomly flicker HUD gauges // 5.) Randomly swap/mess-up HUD text // EMP EFFECTS ON SHIPS ------- // 1.) Lightning effect proportional to the emp effect // ---------------------------------------------------------------------------------------------------- // EMP EFFECT FUNCTIONS // // maybe reformat a string void emp_maybe_reformat_text(char *text, size_t max_len, int gauge_id); // randomize the chars in a string void emp_randomize_chars(char *str); // initialize the EMP effect for the mission void emp_level_init() { int idx; // reset all vars Emp_intensity = 0.0f; Emp_wacky_target_timestamp = -1; for(idx=0; idxobjnum]; if (target->flags[Object::Object_Flags::Should_be_dead]) continue; if(target->type != OBJ_WEAPON){ continue; } Assert(target->instance >= 0); if(target->instance < 0){ continue; } Assert(Weapons[target->instance].weapon_info_index >= 0); if(Weapons[target->instance].weapon_info_index < 0){ continue; } // if we have a bomb weapon wip_target = &Weapon_info[Weapons[target->instance].weapon_info_index]; if((wip_target->weapon_hitpoints > 0) && !(wip_target->wi_flags[Weapon::Info_Flags::No_emp_kill])) { // get the distance between the detonation and the target object vm_vec_sub(&dist, &target->pos, pos); dist_mag = vm_vec_mag(&dist); // if the bomb was within 1/4 of the outer radius, castrate it if(dist_mag <= (outer_radius * 0.25f)){ // memset(&target->phys_info, 0, sizeof(physics_info)); Weapons[target->instance].weapon_flags.set(Weapon::Weapon_Flags::Dead_in_water); mprintf(("EMP killing weapon\n")); } } } // if I'm only a client in a multiplayer game, do nothing if(MULTIPLAYER_CLIENT){ return; } // See if there are any friendly ships present, if so return without preventing msg for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; target = &Objects[so->objnum]; if(target->type != OBJ_SHIP){ continue; } Assert(Objects[so->objnum].instance >= 0); if(Objects[so->objnum].instance < 0){ continue; } Assert(Ships[Objects[so->objnum].instance].ship_info_index >= 0); if(Ships[Objects[so->objnum].instance].ship_info_index < 0){ continue; } // if the ship is a cruiser or cap ship, only apply the EMP effect to turrets if(Ship_info[Ships[target->instance].ship_info_index].is_big_or_huge()) { float capship_emp_time = use_emp_time_for_capship_turrets ? (emp_time * 1000.0f) : MAX_TURRET_DISRUPT_TIME; moveup = &Ships[target->instance].subsys_list; if(moveup->next != NULL){ moveup = moveup->next; } while(moveup != &Ships[target->instance].subsys_list){ // if this is a turret, disrupt it if((moveup->system_info != NULL) && (moveup->system_info->type == SUBSYSTEM_TURRET)){ vec3d actual_pos; // get the distance to the subsys vm_vec_unrotate(&actual_pos, &moveup->system_info->pnt, &target->orient); vm_vec_add2(&actual_pos, &target->pos); vm_vec_sub(&dist, &actual_pos, pos); dist_mag = vm_vec_mag(&dist); // if for some reason, the object was outside the blast, radius if(dist_mag > outer_radius){ // next item moveup = moveup->next; continue; } // compute a scale factor for the emp effect scale_factor = 1.0f; if(dist_mag >= inner_radius){ scale_factor = 1.0f - (dist_mag / outer_radius); } scale_factor -= Ship_info[Ships[target->instance].ship_info_index].emp_resistance_mod; if (scale_factor < 0.0f) { moveup = moveup->next; continue; } // disrupt the turret ship_subsys_set_disrupted(moveup, (int)(capship_emp_time * scale_factor)); mprintf(("EMP disrupting subsys %s on ship %s (%f, %f)\n", moveup->system_info->subobj_name, Ships[Objects[so->objnum].instance].ship_name, scale_factor, capship_emp_time * scale_factor)); } // next item moveup = moveup->next; } } // otherwise coat the whole ship with the effect. mmmmmmmmm. else { // get the distance between the detonation and the target object vm_vec_sub(&dist, &target->pos, pos); dist_mag = vm_vec_mag(&dist); // if for some reason, the object was outside the blast, radius if(dist_mag > outer_radius){ continue; } // compute a scale factor for the emp effect scale_factor = 1.0f; if(dist_mag >= inner_radius){ scale_factor = 1.0f - (dist_mag / outer_radius); } scale_factor -= Ship_info[Ships[target->instance].ship_info_index].emp_resistance_mod; if (scale_factor < 0.0f) { continue; } // calculate actual EMP effect values actual_intensity = emp_intensity * scale_factor; actual_time = emp_time * scale_factor; mprintf(("EMP effect s : %f, i : %f, t : %f\n", scale_factor, actual_intensity, actual_time)); // if this effect happened to be on me, start it now if((target == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){ emp_start_local(actual_intensity, actual_time); } // if this is a multiplayer game, notify other players of the effect if(Game_mode & GM_MULTIPLAYER){ Assert(MULTIPLAYER_MASTER); send_emp_effect(target->net_signature, actual_intensity, actual_time); } // now be sure to start the emp effect for the ship itself emp_start_ship(target, actual_intensity, actual_time); } } } // start the emp effect for the passed ship (setup lightning arcs, timestamp, etc) // NOTE : if this ship is also me, I should call emp_start_local() as well void emp_start_ship(object *ship_objp, float intensity, float time) { ship *shipp; ai_info *aip; float start_intensity; // make sure this is a ship Assert(ship_objp->type == OBJ_SHIP); Assert(ship_objp->instance >= 0); shipp = &Ships[ship_objp->instance]; // determining pre-existing EMP intensity (if any) start_intensity = shipp->emp_intensity < 0.0f ? 0.0f : shipp->emp_intensity; // setup values (capping them if necessary) (make sure that we un-normalize start_intensity) if(intensity + (start_intensity * EMP_INTENSITY_MAX) >= EMP_INTENSITY_MAX){ intensity = EMP_INTENSITY_MAX - 1.0f; } else { intensity += (start_intensity * EMP_INTENSITY_MAX); } intensity /= EMP_INTENSITY_MAX; if(time >= EMP_TIME_MAX){ time = EMP_TIME_MAX - 0.1f; } shipp->emp_intensity = intensity; shipp->emp_decr = intensity / time; // multiplayer clients should bail now if(MULTIPLAYER_CLIENT){ return; } // do any initial AI effects Assert(shipp->ai_index >= 0); aip = &Ai_info[shipp->ai_index]; // lose his current target set_target_objnum(aip, -1); set_targeted_subsys(aip, NULL, -1); } // process a ship for this frame int mod_val = 7; void emp_process_ship(ship *shipp) { object *objp; ai_info *aip; Assert(shipp != NULL); if(shipp == NULL){ return; } Assert(shipp->objnum >= 0); if(shipp->objnum < 0){ return; } objp = &Objects[shipp->objnum]; // if the emp intensity is < 0, there is no effect if(shipp->emp_intensity < 0.0f){ shipp->emp_intensity = -1.0f; return; } // reduce the emp effect shipp->emp_intensity -= shipp->emp_decr * flFrametime; // multiplayer clients should bail here if(MULTIPLAYER_CLIENT){ return; } // if this is a player ship, don't do anything wacky if(objp->flags[Object::Object_Flags::Player_ship]){ return; } // lose lock time, etc, etc. Assert(shipp->ai_index >= 0); aip = &Ai_info[shipp->ai_index]; aip->aspect_locked_time = 0.0f; // hasn't gotten aspect lock at all aip->current_target_is_locked = 0; // isn't locked on his current target aip->ai_flags.remove(AI::AI_Flags::Seek_lock); aip->nearest_locked_object = -1; // nothing near me, so I won't launch countermeasures // if he's not a fighter or bomber, bail now if(!(Ship_info[shipp->ship_info_index].is_fighter_bomber())){ return; } // if he's docked, or ordered to not move, bail now if (object_is_docked(objp) || (aip->mode == AIM_STILL) || (aip->mode == AIM_PLAY_DEAD)){ return; } // pick targets randomly and wackily so that the ship flies crazily :) if(((int)f2fl(Missiontime) + (int)(EMP_INTENSITY_MAX * shipp->emp_intensity)) % mod_val == 0){ int ship_lookup = ship_get_random_team_ship(iff_get_attackee_mask(shipp->team)); // if we got a valid ship object to target if((ship_lookup >= 0) && (Ships[ship_lookup].objnum >= 0) && !(Objects[Ships[ship_lookup].objnum].flags[Object::Object_Flags::Protected]) && (SHIP_INDEX(shipp) != ship_lookup)){ // attack the object ai_attack_object(objp, &Objects[Ships[ship_lookup].objnum]); } } } void emp_start_local(float intensity, float time) { emp_start_local(intensity, time, ""); } // start the emp effect for MYSELF (intensity == arbitrary intensity variable, time == time the effect will last) // NOTE : time should be in seconds void emp_start_local(float intensity, float time, const SCP_string &text) { int idx; float start_intensity; // determine pre-existing EMP intensity (if any) start_intensity = Emp_intensity < 0.0f ? 0.0f : Emp_intensity; // cap all values (make sure that we un-normalize start_intensity) if(intensity + (start_intensity * EMP_INTENSITY_MAX) >= EMP_INTENSITY_MAX){ intensity = EMP_INTENSITY_MAX - 1.0f; } else { intensity += (start_intensity * EMP_INTENSITY_MAX); } if(time >= EMP_TIME_MAX){ time = EMP_TIME_MAX - 0.1f; } // setup all vars Emp_intensity = intensity / EMP_INTENSITY_MAX; // lose my current target if any if(Player_ai != NULL){ Player_ai->target_objnum = -1; } // lose any lock we have or are getting hud_lock_reset(); // reset HUD gauge text wackiness stuff for(idx=0; idx 0) display = text; // start the emp icon flashing if display text is not "none" if (stricmp(display.c_str(), "none") != 0) hud_start_text_flash(display.c_str(), 5000); // determine how much we have to decrement the effect per second Emp_decr = Emp_intensity / time; // play a flash game_flash( std::get<0>(Emp_pain_flash_color) * Emp_pain_flash_factor, std::get<1>(Emp_pain_flash_color) * Emp_pain_flash_factor, std::get<2>(Emp_pain_flash_color) * Emp_pain_flash_factor ); } // stop the emp effect cold void emp_stop_local() { // kill off various EMP stuff Emp_intensity = -1.0f; Emp_wacky_target_timestamp = -1; } // if the EMP effect is active int emp_active_local() { return EMP_ACTIVE_LOCAL(); } // process some stuff every frame (before frame is rendered) void emp_process_local() { if(!emp_active_local()){ return; } // decrement the intensity a bit Emp_intensity -= (flFrametime * Emp_decr); // see if we should choose a random target if((Emp_wacky_target_timestamp == -1) || timestamp_elapsed(Emp_wacky_target_timestamp)){ // choose a target (if not the "first" time) if(Emp_wacky_target_timestamp != -1){ hud_target_random_ship(); } // reset the timestamp Emp_wacky_target_timestamp = timestamp((int)frand_range(100.0f, 750.0f * (1.0f - Emp_intensity))); } } // randomly say yes or no to a gauge, if emp is not active, always say yes int emp_should_blit_gauge() { // if the EMP effect is not active, always blit if(!emp_active_local()){ return 1; } // otherwise, randomly say no return frand_range(0.0f, 1.0f) > Emp_intensity; } // emp hud string void emp_hud_string(int x, int y, int gauge_id, const char *str, size_t len, int resize_mode, float scale) { // maybe bail if (!*str) return; // if the emp effect is not active, don't even bother messing with the text if(emp_active_local()){ auto tmp_len = std::min(len, i2sz(255)); // use a copied string rather than the original char tmp[256] = ""; strncpy(tmp, str, tmp_len); tmp[tmp_len] = '\0'; emp_maybe_reformat_text(tmp, tmp_len, gauge_id); // jitter the coords emp_hud_jitter(&x, &y); // print the string out gr_string(x, y, tmp, resize_mode, scale, len); } else { // print the original string out gr_string(x, y, str, resize_mode, scale, len); } } // maybe reformat a string void emp_maybe_reformat_text(char *text, size_t /*max_len*/, int gauge_id) { wacky_text *wt; // if the EMP effect is not active, never reformat it if(!emp_active_local()){ return; } // randomly _don't_ apply text craziness if(frand_range(0.0f, 1.0f) > Emp_intensity){ return; } // if the gauge is EG_NULL, empty the string if(gauge_id == EG_NULL){ strcpy(text, ""); return; } // if this gauge has not been wacked out, or if the timestamp has expired, we // neeed to wack it out again Assert((gauge_id >= EG_NULL) && (gauge_id < NUM_TEXT_STAMPS)); wt = &Emp_wacky_text[gauge_id]; if((wt->stamp == -1) || timestamp_elapsed(wt->stamp)){ // reformat specific gauges differently switch(gauge_id){ // weapons case EG_WEAPON_TITLE: case EG_WEAPON_P1: case EG_WEAPON_P2: case EG_WEAPON_P3: case EG_WEAPON_S1: case EG_WEAPON_S2: { int wep_index = weapon_get_random_player_usable_weapon(); if (wep_index >= 0) { strcpy_s(wt->str, Weapon_info[wep_index].get_display_name()); } break; } // escort list case EG_ESCORT1: case EG_ESCORT2: case EG_ESCORT3: { // choose a random ship int shipnum = ship_get_random_targetable_ship(); if (shipnum >= 0) { strcpy_s(wt->str, Ships[shipnum].get_display_name()); } break; } // directives title case EG_OBJ_TITLE: strcpy_s(wt->str, ""); break; // directives themselves case EG_OBJ1: case EG_OBJ2: case EG_OBJ3: case EG_OBJ4: case EG_OBJ5: strcpy_s(wt->str, text); emp_randomize_chars(wt->str); break; // target box info case EG_TBOX_EXTRA1: case EG_TBOX_EXTRA2: case EG_TBOX_EXTRA3: case EG_TBOX_CLASS: case EG_TBOX_DIST: case EG_TBOX_CARGO: case EG_TBOX_HULL: case EG_TBOX_NAME: case EG_TBOX_INTEG: strcpy_s(wt->str, text); emp_randomize_chars(wt->str); break; // squadmsg menu case EG_SQ1: case EG_SQ2: case EG_SQ3: case EG_SQ4: case EG_SQ5: case EG_SQ6: case EG_SQ7: case EG_SQ8: case EG_SQ9: case EG_SQ10: strcpy_s(wt->str, text); emp_randomize_chars(wt->str); break; // default default : return; } // recalculate the timestamp wt->stamp = timestamp((int)frand_range(100.0f, 750.0f * (1.0f - Emp_intensity))); // copy the text strcpy(text, wt->str); } // otherwise, use what we calculated last time else { strcpy(text, wt->str); } } // randomize the chars in a string void emp_randomize_chars(char *str) { int idx; int char_index; // shuffle chars around for(idx=0; idx<(int)(strlen(str)-1); idx++){ if(frand_range(0.0f, 1.0f) < Emp_intensity){ char_index = Random::next(NUM_RANDOM_CHARS); str[idx] = Emp_random_char[char_index]; } } } // throw some jitter into HUD x and y coords void emp_hud_jitter(int *x, int *y) { // if the emp effect is not active, don't jitter anything if(!emp_active_local()){ return; } // some movement *x += (int)frand_range(-8.0f * Emp_intensity, 8.0f * Emp_intensity); *y += (int)frand_range(-8.0f * Emp_intensity, 8.0f * Emp_intensity); } // current intensity of the EMP effect (0.0 - 1.0) float emp_current_intensity() { return Emp_intensity; } DCF(zap, "zap a ship with an EMP effect") { int shipnum; SCP_string ship_str; dc_stuff_string_white(ship_str); shipnum = ship_name_lookup(ship_str.c_str(), 1); if(shipnum >= 0){ emp_start_ship(&Objects[Ships[shipnum].objnum], 500.0f, 10.0f); } }