/* * 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 "ai/ai.h" #include "fireball/fireballs.h" #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "globalincs/linklist.h" #include "hud/hudartillery.h" #include "hud/hudmessage.h" #include "io/timer.h" #include "math/vecmat.h" #include "network/multi.h" #include "object/object.h" #include "parse/parselo.h" #include "ship/shipfx.h" #include "sound/sound.h" #include "utils/Random.h" #include "weapon/beam.h" #include "weapon/weapon.h" // ----------------------------------------------------------------------------------------------------------------------- // ARTILLERY DEFINES/VARS // // Goober5000 - moved to hudartillery.h // ----------------------------------------------------------------------------------------------------------------------- // ARTILLERY FUNCTIONS // // test code for subspace missile strike ------------------------------------------- // ssm_info, like ship_info etc. SCP_vector Ssm_info; // list of active strikes SCP_list Ssm_strikes; // Goober5000 int ssm_info_lookup(const char *name) { if(name == NULL) return -1; for (auto it = Ssm_info.cbegin(); it != Ssm_info.cend(); ++it) if (!stricmp(name, it->name)) return (int)std::distance(Ssm_info.cbegin(), it); return -1; } void parse_ssm(const char *filename) { char ssm_name[NAME_LENGTH]; char weapon_name[NAME_LENGTH]; try { read_file_text(filename, CF_TYPE_TABLES); reset_parse(); // parse the table while(required_string_either("#end", "$SSM:")) { bool no_create = false; int string_index; ssm_info *s, new_ssm; s = &new_ssm; // name required_string("$SSM:"); stuff_string(ssm_name, F_NAME, NAME_LENGTH); if (*ssm_name == 0) { sprintf(ssm_name, "SSM " SIZE_T_ARG, Ssm_info.size()); mprintf(("Found an SSM entry without a name. Assigning \"%s\".\n", ssm_name)); } if (optional_string("+nocreate")) { no_create = true; int i = ssm_info_lookup(ssm_name); if (i >= 0) s = &Ssm_info[i]; } else { strcpy_s(s->name, ssm_name); } // stuff data if (optional_string("+Weapon:")) { stuff_string(weapon_name, F_NAME, NAME_LENGTH); // see if we have a valid weapon s->weapon_info_index = weapon_info_lookup(weapon_name); if (s->weapon_info_index < 0) error_display(0, "Unknown weapon [%s] for SSM strike [%s]; this SSM strike will be discarded.", weapon_name, s->name); } string_index = optional_string_either("+Count:", "+Min Count:"); if (string_index == 0) { stuff_int(&s->count); s->max_count = -1; } else if (string_index == 1) { stuff_int(&s->count); required_string("+Max Count:"); stuff_int(&s->max_count); } if (optional_string("+WarpEffect:")) { char unique_id[NAME_LENGTH]; stuff_string(unique_id, F_NAME, NAME_LENGTH); if (can_construe_as_integer(unique_id)) { int temp = atoi(unique_id); if (!SCP_vector_inbounds(Fireball_info, temp)) error_display(0, "Fireball index [%d] out of range (should be 0-%d) for SSM strike [%s]", temp, static_cast(Fireball_info.size()) - 1, s->name); else s->fireball_type = temp; } else { // We have a string to parse instead. int fireball_type = fireball_info_lookup(unique_id); if (fireball_type < 0) error_display(0, "Unknown fireball [%s] to use as warp effect for SSM strike [%s]", unique_id, s->name); else s->fireball_type = fireball_type; } } if (optional_string("+WarpRadius:")) { stuff_float(&s->warp_radius); } if (optional_string("+WarpTime:")) { stuff_float(&s->warp_time); // According to fireballs.cpp, "Warp lifetime must be at least 4 seconds!" if (s->warp_time < 4.0f) { // So let's warn them before they try to use it, shall we? error_display(0, "Expected a '+WarpTime:' value equal or greater than 4.0, found '%f' in SSM strike [%s].\nSetting to 4.0, please check and set to a number 4.0 or greater!", s->warp_time, s->name); // And then make the Assert obsolete -- Zacam s->warp_time = 4.0f; } } string_index = optional_string_either("+Radius:", "+Min Radius:"); if (string_index == 0) { stuff_float(&s->radius); s->max_radius = -1.0f; } else if (string_index == 1) { stuff_float(&s->radius); required_string("+Max Radius:"); stuff_float(&s->max_radius); } string_index = optional_string_either("+Offset:", "+Min Offset:"); if (string_index == 0) { stuff_float(&s->offset); s->max_offset = -1.0f; } else if (string_index == 1) { stuff_float(&s->offset); required_string("+Max Offset:"); stuff_float(&s->max_offset); } if (optional_string("+Shape:")) { switch (required_string_one_of(3, "Point", "Circle", "Sphere")) { case 0: required_string("Point"); s->shape = SSM_SHAPE_POINT; break; case 1: required_string("Circle"); FALLTHROUGH; case -1: // If we're ignoring parse errors and can't identify the shape, go with a circle. s->shape = SSM_SHAPE_CIRCLE; break; case 2: required_string("Sphere"); s->shape = SSM_SHAPE_SPHERE; break; default: UNREACHABLE("Impossible return value from required_string_one_of(); get a coder!"); } } if (optional_string("+HUD Message:")) stuff_boolean(&s->send_message); if (optional_string("+Custom Message:")) { stuff_string(s->custom_message, F_NAME, NAME_LENGTH); s->use_custom_message = true; } parse_game_sound("+Alarm Sound:", &s->sound_index); // don't add new entry if this is just a modified one if (!no_create) Ssm_info.push_back(new_ssm); } } catch (const parse::ParseException& e) { mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what())); return; } } // game init void ssm_init() { if (cf_exists_full("ssm.tbl", CF_TYPE_TABLES)) { mprintf(("TABLES => Starting parse of 'ssm.tbl'...\n")); parse_ssm("ssm.tbl"); } parse_modular_table(NOX("*-ssm.tbm"), parse_ssm); // We already warned the modder that an SSM strike is invalid without a valid weapon, so now remove any that are in the list Ssm_info.erase( std::remove_if(Ssm_info.begin(), Ssm_info.end(), [](const ssm_info &ssm) { return ssm.weapon_info_index < 0; }), Ssm_info.end() ); // Now that we've populated Ssm_info, let's validate weapon $SSM: entries. validate_SSM_entries(); } void ssm_get_random_start_pos(vec3d *out, const vec3d *start, const matrix *orient, size_t ssm_index) { vec3d temp; ssm_info *s = &Ssm_info[ssm_index]; float radius, offset; if (s->max_radius == -1.0f) radius = s->radius; else radius = frand_range(s->radius, s->max_radius); if (s->max_offset == -1.0f) offset = s->offset; else offset = frand_range(s->offset, s->max_offset); switch (s->shape) { case SSM_SHAPE_SPHERE: // get a random vector in a sphere around the target vm_vec_random_in_sphere(&temp, start, radius, true); break; case SSM_SHAPE_CIRCLE: // get a random vector in the circle of the firing plane vm_vec_random_in_circle(&temp, start, orient, radius, true); break; case SSM_SHAPE_POINT: // boooring vm_vec_scale_add(&temp, start, &orient->vec.fvec, radius); break; default: UNREACHABLE("Unknown shape '%d' in SSM type #" SIZE_T_ARG " ('%s'). This should not be possible; get a coder!\n", s->shape, ssm_index, s->name); temp = *start; break; } // offset it a bit vm_vec_scale_add(out, &temp, &orient->vec.fvec, offset); } // level init void ssm_level_init() { } // start a subspace missile effect // (it might be possible to make `target` const, but that would set off another const-cascade) void ssm_create(object *target, const vec3d *start, size_t ssm_index, const ssm_firing_info *override, int team) { ssm_strike ssm; matrix dir; int idx, count; // sanity Assert(target != NULL); if(target == NULL){ return; } Assert(start != NULL); if(start == NULL){ return; } if (ssm_index >= Ssm_info.size()) { return; } // Init the ssm data count = Ssm_info[ssm_index].count; if (Ssm_info[ssm_index].max_count != -1 && (Ssm_info[ssm_index].max_count - count) > 0) { count = Random::next(count, Ssm_info[ssm_index].max_count); } // override in multiplayer if(override != NULL){ ssm.sinfo = *override; } // single player or the server else { // forward orientation vec3d temp; vm_vec_normalized_dir(&temp, &target->pos, start); vm_vector_2_matrix_norm(&dir, &temp, nullptr, nullptr); // stuff info ssm.sinfo.count = count; ssm.sinfo.ssm_index = ssm_index; ssm.sinfo.target = target; ssm.sinfo.ssm_team = team; // Instead of pushing them on one at a time, let's just grab all the memory we'll need at once // (as a side effect, don't need to change the logic from the old array-based code) ssm.sinfo.delay_stamp.resize(count); ssm.sinfo.start_pos.resize(count); for (idx = 0; idx < count; idx++) { ssm.sinfo.delay_stamp[idx] = _timestamp(200 + Random::next(-199, 1000)); ssm_get_random_start_pos(&ssm.sinfo.start_pos[idx], start, &dir, ssm_index); } ssm_info *si = &Ssm_info[ssm_index]; weapon_info *wip = &Weapon_info[si->weapon_info_index]; if (wip->wi_flags[Weapon::Info_Flags::Beam]) { ssm.sinfo.duration = ((si->warp_time - ((wip->b_info.beam_warmup / 1000.0f) + wip->b_info.beam_life + (wip->b_info.beam_warmdown / 1000.0f))) / 2.0f) / si->warp_time; } else { ssm.sinfo.duration = 0.5f; } // if we're the server, send a packet if(MULTIPLAYER_MASTER){ // } } ssm.done_flags.clear(); ssm.done_flags.resize(count); ssm.fireballs.clear(); ssm.fireballs.resize(count, -1); if(Ssm_info[ssm_index].send_message) { if (!Ssm_info[ssm_index].use_custom_message) HUD_printf("%s", XSTR("Firing artillery", 1570)); else HUD_printf("%s", Ssm_info[ssm_index].custom_message); } if (Ssm_info[ssm_index].sound_index.isValid()) { snd_play(gamesnd_get_game_sound(Ssm_info[ssm_index].sound_index)); } Ssm_strikes.push_back(std::move(ssm)); } // delete a finished ssm effect void ssm_delete(SCP_list::iterator ssm) { Ssm_strikes.erase(ssm); } // process subspace missile stuff void ssm_process() { int idx, finished; SCP_list::iterator moveup, eraser; ssm_info *si; int weapon_objnum; // process all strikes moveup = Ssm_strikes.begin(); while ( moveup != Ssm_strikes.end() ) { // get the type Assertion(moveup->sinfo.ssm_index < Ssm_info.size(), "Invalid SSM index detected!"); si = &Ssm_info[moveup->sinfo.ssm_index]; // check all the individual missiles finished = 1; for(idx=0; idxsinfo.count; idx++){ // if this guy is not marked as done if(!moveup->done_flags[idx]){ finished = 0; // if he already has the fireball effect if(moveup->fireballs[idx] >= 0){ if ((1.0f - fireball_lifeleft_percent(&Objects[moveup->fireballs[idx]])) >= moveup->sinfo.duration) { weapon_info *wip = &Weapon_info[si->weapon_info_index]; // get an orientation vec3d temp; matrix orient; vm_vec_normalized_dir(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]); vm_vector_2_matrix_norm(&orient, &temp, nullptr, nullptr); // are we a beam? -MageKing17 if (wip->wi_flags[Weapon::Info_Flags::Beam]) { int num_beams = wip->b_info.beam_type == BeamType::OMNI && (int)wip->b_info.t5info.burst_rot_pattern.size() > 1 ? (int)wip->b_info.t5info.burst_rot_pattern.size() : 1; for (int i = 0; i < num_beams; i++) { beam_fire_info fire_info; memset(&fire_info, 0, sizeof(beam_fire_info)); fire_info.accuracy = 0.000001f; // this will guarantee a hit fire_info.shooter = nullptr; fire_info.turret = nullptr; fire_info.target = moveup->sinfo.target; fire_info.target_subsys = nullptr; fire_info.bfi_flags |= BFIF_FLOATING_BEAM; fire_info.starting_pos = moveup->sinfo.start_pos[idx]; fire_info.beam_info_index = si->weapon_info_index; fire_info.team = static_cast(moveup->sinfo.ssm_team); fire_info.fire_method = BFM_SUBSPACE_STRIKE; fire_info.burst_index = i; // Fill target_pos but DONT turn on BFIF_TARGETING_COORDS // It would mess up normal beams but type 5s can still make use of this fire_info.target_pos1 = orient.vec.fvec + fire_info.starting_pos; // fire the beam beam_fire(&fire_info); moveup->done_flags[idx] = true; } } else { // fire the missile and flash the screen weapon_objnum = weapon_create(&moveup->sinfo.start_pos[idx], &orient, si->weapon_info_index, -1, -1, true); if (weapon_objnum >= 0) { Weapons[Objects[weapon_objnum].instance].team = moveup->sinfo.ssm_team; Weapons[Objects[weapon_objnum].instance].homing_object = moveup->sinfo.target; Weapons[Objects[weapon_objnum].instance].target_sig = moveup->sinfo.target->signature; } // this makes this particular missile done moveup->done_flags[idx] = true; } } } // maybe create his warpin effect else if((moveup->sinfo.delay_stamp[idx].isValid()) && timestamp_elapsed(moveup->sinfo.delay_stamp[idx])){ // get an orientation vec3d temp; matrix orient; vm_vec_normalized_dir(&temp, &moveup->sinfo.target->pos, &moveup->sinfo.start_pos[idx]); vm_vector_2_matrix_norm(&orient, &temp, nullptr, nullptr); moveup->fireballs[idx] = fireball_create(&moveup->sinfo.start_pos[idx], si->fireball_type, FIREBALL_WARP_EFFECT, -1, si->warp_radius, false, &vmd_zero_vector, si->warp_time, 0, &orient); } } } if(finished){ eraser = moveup; ++moveup; ssm_delete(eraser); continue; } ++moveup; } } // test code for subspace missile strike ------------------------------------------- // level init void hud_init_artillery() { } // update all hud artillery related stuff void hud_artillery_update() { } // render all hud artillery related stuff void hud_artillery_render() { // render how long the player has been painting his target if((Player_ai != NULL) && (Player_ai->artillery_objnum >= 0)){ gr_set_color_fast(&Color_bright_blue); gr_printf_no_resize(gr_screen.center_offset_x + 10, gr_screen.center_offset_y + 50, "%f", Player_ai->artillery_lock_time); } }