mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-23 20:17:15 +01:00
Explosives science experiments (#19359)
* yay explosions * i have no idea what i am doing with tgui * experiment stuff done * time title * tabs * . * searh handling * - * make it a bit easier to understand * use correct time * tuned --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,8 @@
|
||||
#define COMSIG_MACHINERY_SET_OCCUPANT "machinery_set_occupant"
|
||||
///from /obj/machinery/rnd/destructive_analyzer/proc/destroy_item(gain_research_points = FALSE): Runs when the destructive scanner scans a group of objects. (list/scanned_atoms)
|
||||
#define COMSIG_MACHINERY_DESTRUCTIVE_SCAN "machinery_destructive_scan"
|
||||
///from /obj/machinery/doppler_array/proc/sense_explosion(): Runs when an explosion is detected. (turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken)
|
||||
#define COMSIG_MACHINERY_EXPLOSION_DETECTED "machinery_explosion_detected"
|
||||
///from /obj/machinery/computer/arcade/victory_tickets(tickets, sound = TRUE)
|
||||
#define COMSIG_ARCADE_VICTORY "arcade_victory"
|
||||
///from /obj/machinery/computer/telescience/proc/doteleport(mob/user): (list/atom/movable/teleported_things, turf/target_turf, sending )
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define EXPERIMENT_TAG_BASE "Base"
|
||||
#define EXPERIMENT_TAG_SCAN "Scan"
|
||||
#define EXPERIMENT_TAG_PHYSICAL "Physical Experiment"
|
||||
#define EXPERIMENT_TAG_ORDINANCE "Explosives Experiment"
|
||||
|
||||
/// Macro for defining a progress stage
|
||||
#define EXPERIMENT_PROGRESS(type, desc, values...) list(list(type, desc, values))
|
||||
|
||||
@@ -8,14 +8,38 @@
|
||||
icon_state = "doppler"
|
||||
circuit = /obj/item/circuitboard/doppler_array
|
||||
|
||||
var/list/detected_explosions = list()
|
||||
|
||||
/obj/machinery/doppler_array/Initialize(mapload)
|
||||
//Explosive analysis
|
||||
var/static/list/explosive_signals = list(
|
||||
COMSIG_MACHINERY_EXPLOSION_DETECTED = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_ordinance_experiment),
|
||||
)
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
config_mode = EXPERIMENT_CONFIG_ALTCLICK, \
|
||||
allowed_experiments = list(/datum/experiment/ordnance),\
|
||||
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE|EXPERIMENT_CONFIG_SILENT_FAIL,\
|
||||
experiment_signals = explosive_signals, \
|
||||
)
|
||||
. = ..()
|
||||
RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, PROC_REF(sense_explosion))
|
||||
ADD_TRAIT(src, TRAIT_ALT_CLICK_BLOCKER, ROUNDSTART_TRAIT)
|
||||
|
||||
/obj/machinery/doppler_array/Destroy()
|
||||
UnregisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION)
|
||||
. = ..()
|
||||
|
||||
/obj/machinery/doppler_array/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "DopplerArray", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/doppler_array/tgui_static_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
||||
var/list/data = list()
|
||||
data["explosions"] = length(detected_explosions) ? detected_explosions : null;
|
||||
return data
|
||||
|
||||
/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
@@ -31,6 +55,21 @@
|
||||
if(our_turf.Distance(epicenter) > 100)
|
||||
return
|
||||
atom_say("Explosive disturbance detected - Epicenter at: grid ([x0],[y0],[z0]). Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range]. Temporal displacement of tachyons: [seconds_taken] seconds.")
|
||||
SEND_SIGNAL(src, COMSIG_MACHINERY_EXPLOSION_DETECTED, epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken)
|
||||
detected_explosions += list(
|
||||
list(
|
||||
"index" = length(detected_explosions),
|
||||
"time" = stationtime2text(),
|
||||
"x" = x0,
|
||||
"y" = y0,
|
||||
"z" = z0,
|
||||
"devastation_range" = devastation_range,
|
||||
"heavy_impact_range" = heavy_impact_range,
|
||||
"light_impact_range" = light_impact_range,
|
||||
"seconds_taken" = seconds_taken,
|
||||
)
|
||||
)
|
||||
update_static_data_for_all_viewers()
|
||||
|
||||
/obj/machinery/doppler_array/power_change()
|
||||
..()
|
||||
@@ -47,3 +86,7 @@
|
||||
return
|
||||
if(default_part_replacement(user, W))
|
||||
return
|
||||
|
||||
/obj/machinery/doppler_array/attack_hand(mob/user)
|
||||
. = ..()
|
||||
tgui_interact(user)
|
||||
|
||||
@@ -134,22 +134,23 @@
|
||||
to_chat(user, span_notice("[target] is not related to your currently selected experiment."))
|
||||
|
||||
/**
|
||||
* Hooks on destructive scans to try and run an experiment (When using a handheld handler)
|
||||
* Hooks on destructive scans to try and run a destructive analyzer experiment.
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_destructive_experiment(datum/source, atom/scan_target)
|
||||
/datum/component/experiment_handler/proc/try_run_destructive_experiment(obj/source, atom/scan_target)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/movable/our_scanner = parent
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE) )
|
||||
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
||||
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(our_scanner, span_notice("No experiment selected!"))
|
||||
return
|
||||
|
||||
if(action_experiment(source, scan_target))
|
||||
playsound(our_scanner, 'sound/machines/ping.ogg', 25)
|
||||
to_chat(our_scanner, span_notice("The scan succeeds."))
|
||||
else if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
our_scanner.atom_say("The scan did not result in anything.")
|
||||
playsound(source, 'sound/machines/ping.ogg', 25)
|
||||
source.atom_say("Destructive analysis complete.")
|
||||
|
||||
/**
|
||||
* Hooks on doppler array scans to try and run a explosive experiment.
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_ordinance_experiment(obj/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(action_experiment(source, epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken))
|
||||
playsound(source, 'sound/machines/ping.ogg', 25)
|
||||
|
||||
/// Hooks on a successful autopsy experiment
|
||||
/datum/component/experiment_handler/proc/try_run_autopsy_experiment(obj/source, mob/living/target)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/datum/experiment/ordnance/lowyieldbomb
|
||||
name = "Explosive Sensor Test"
|
||||
description = "Detonate a simple explosive and record observations using a doppler array."
|
||||
required_devastation_range = 1
|
||||
required_heavy_impact_range = 1
|
||||
|
||||
/datum/experiment/ordnance/highyieldbomb
|
||||
name = "High Yield Explosives Test"
|
||||
description = "Detonate a much bigger explosive and record observations using a doppler array."
|
||||
required_devastation_range = 3
|
||||
required_heavy_impact_range = 5
|
||||
@@ -0,0 +1,30 @@
|
||||
/datum/experiment/ordnance
|
||||
name = "Explosives Research"
|
||||
description = "An experiment conducted in the phoronics subdepartment."
|
||||
exp_tag = EXPERIMENT_TAG_ORDINANCE
|
||||
performance_hint = "Perform research using explosive devices."
|
||||
allowed_experimentors = list(/obj/machinery/doppler_array)
|
||||
var/required_devastation_range = 0
|
||||
var/required_heavy_impact_range = 0
|
||||
var/required_light_impact_range = 0
|
||||
var/explosion_criteria_met = FALSE
|
||||
|
||||
/datum/experiment/ordnance/is_complete()
|
||||
return explosion_criteria_met
|
||||
|
||||
/datum/experiment/ordnance/check_progress()
|
||||
var/status_message = "Detonate an explosive of sufficient size and detect it with a doppler array."
|
||||
. += EXPERIMENT_PROG_BOOL(status_message, is_complete())
|
||||
|
||||
/datum/experiment/ordnance/actionable(datum/component/experiment_handler/experiment_handler)
|
||||
return !is_complete()
|
||||
|
||||
/datum/experiment/ordnance/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, seconds_taken)
|
||||
if(devastation_range < required_devastation_range)
|
||||
return FALSE
|
||||
if(heavy_impact_range < required_heavy_impact_range)
|
||||
return FALSE
|
||||
if(light_impact_range < required_light_impact_range)
|
||||
return FALSE
|
||||
explosion_criteria_met = TRUE
|
||||
return TRUE
|
||||
@@ -38,6 +38,7 @@ It is used to destroy hand-held objects and advance technological research. Used
|
||||
)
|
||||
. = ..()
|
||||
default_apply_parts()
|
||||
ADD_TRAIT(src, TRAIT_ALT_CLICK_BLOCKER, ROUNDSTART_TRAIT)
|
||||
|
||||
/obj/machinery/rnd/destructive_analyzer/Destroy()
|
||||
rmat = null
|
||||
|
||||
@@ -283,7 +283,7 @@
|
||||
"large_Grenade",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS)
|
||||
// required_experiments = list(/datum/experiment/ordnance/explosive/lowyieldbomb)
|
||||
required_experiments = list(/datum/experiment/ordnance/lowyieldbomb)
|
||||
announce_channels = list(CHANNEL_SECURITY, CHANNEL_MEDICAL)
|
||||
|
||||
/datum/techweb_node/exotic_ammo
|
||||
@@ -308,7 +308,7 @@
|
||||
// "techshotshell",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
|
||||
// discount_experiments = list(/datum/experiment/ordnance/explosive/highyieldbomb = TECHWEB_TIER_4_POINTS)
|
||||
discount_experiments = list(/datum/experiment/ordnance/highyieldbomb = TECHWEB_TIER_4_POINTS)
|
||||
announce_channels = list(CHANNEL_SECURITY)
|
||||
|
||||
/datum/techweb_node/electric_weapons
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { LabeledList, Section } from 'tgui-core/components';
|
||||
import type { Explosion } from './types';
|
||||
|
||||
export const DopplerExplosions = (props: { explosions: Explosion[] }) => {
|
||||
const { explosions } = props;
|
||||
|
||||
return explosions.map((exp) => (
|
||||
<Section key={exp.index} title={exp.time}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Coordinates">
|
||||
{exp.x}.{exp.y}.{exp.z}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Inner Radius">
|
||||
{exp.devastation_range}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Outer Radius">
|
||||
{exp.heavy_impact_range}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Shockwave Radius">
|
||||
{exp.light_impact_range}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Tachyon Displacement">
|
||||
{exp.seconds_taken}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
));
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Button, Collapsible, Input, Stack } from 'tgui-core/components';
|
||||
import { capitalize } from 'tgui-core/string';
|
||||
import type { SearchFields } from './types';
|
||||
|
||||
export const DopplerSearch = (props: {
|
||||
searchFields: SearchFields;
|
||||
setSearchFields: React.Dispatch<React.SetStateAction<SearchFields>>;
|
||||
searchText: string;
|
||||
setSearchText: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const { searchFields, setSearchFields, searchText, setSearchText } = props;
|
||||
|
||||
return (
|
||||
<Collapsible color="transparent" title="Search Filter">
|
||||
<Stack wrap="wrap">
|
||||
{Object.keys(searchFields).map((field) => (
|
||||
<Stack.Item key={field}>
|
||||
<Button.Checkbox
|
||||
checked={searchFields[field]}
|
||||
onClick={() =>
|
||||
setSearchFields({
|
||||
...searchFields,
|
||||
[field]: !searchFields[field],
|
||||
})
|
||||
}
|
||||
>
|
||||
{capitalize(field.replace(/_/g, ' '))}
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
<Input
|
||||
placeholder="Search explosions..."
|
||||
value={searchText}
|
||||
onChange={(value) => setSearchText(value)}
|
||||
fluid
|
||||
/>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Tabs } from 'tgui-core/components';
|
||||
import { explosionTypes } from './constants';
|
||||
|
||||
export const DopplerTabs = (props: {
|
||||
activeTab: string;
|
||||
setActiveTab: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const { activeTab, setActiveTab } = props;
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
{['All', ...explosionTypes].map((sev) => (
|
||||
<Tabs.Tab
|
||||
key={sev}
|
||||
selected={activeTab === sev}
|
||||
onClick={() => setActiveTab(sev)}
|
||||
>
|
||||
{sev}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const explosionTypes = ['Small', 'Medium', 'Large'];
|
||||
@@ -0,0 +1,10 @@
|
||||
import { explosionTypes } from './constants';
|
||||
import type { Explosion } from './types';
|
||||
|
||||
export function getSeverity(exp: Explosion) {
|
||||
if (exp.devastation_range >= 3 || exp.heavy_impact_range >= 5)
|
||||
return explosionTypes[2];
|
||||
if (exp.devastation_range >= 2 || exp.heavy_impact_range >= 3)
|
||||
return explosionTypes[1];
|
||||
return explosionTypes[0];
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Box, NoticeBox, Stack } from 'tgui-core/components';
|
||||
import { createSearch } from 'tgui-core/string';
|
||||
import { DopplerExplosions } from './DopplerExplosions';
|
||||
import { DopplerSearch } from './DopplerSearch';
|
||||
import { DopplerTabs } from './DopplerTabs';
|
||||
import { getSeverity } from './functions';
|
||||
import type { Data, Explosion } from './types';
|
||||
|
||||
export const DopplerArray = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const [searchFields, setSearchFields] = useState({
|
||||
time: true,
|
||||
coordinates: true,
|
||||
inner_radius: true,
|
||||
outer_radius: true,
|
||||
shockwave: true,
|
||||
tachyon: true,
|
||||
});
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [activeTab, setActiveTab] = useState('All');
|
||||
|
||||
const { explosions } = data;
|
||||
|
||||
const searcher = createSearch<Explosion>(searchText, (exp) => {
|
||||
const parts: string[] = [];
|
||||
|
||||
if (searchFields.time) parts.push(exp.time);
|
||||
if (searchFields.coordinates) parts.push(`${exp.x}.${exp.y}.${exp.z}`);
|
||||
if (searchFields.inner_radius) parts.push(exp.devastation_range.toString());
|
||||
if (searchFields.outer_radius)
|
||||
parts.push(exp.heavy_impact_range.toString());
|
||||
if (searchFields.shockwave) parts.push(exp.light_impact_range.toString());
|
||||
if (searchFields.tachyon) parts.push(exp.seconds_taken.toString());
|
||||
|
||||
return parts.join(' ');
|
||||
});
|
||||
|
||||
const grouped = explosions?.reduce<Record<string, Explosion[]>>(
|
||||
(acc, exp) => {
|
||||
const severity = getSeverity(exp);
|
||||
|
||||
if (!acc[severity]) {
|
||||
acc[severity] = [];
|
||||
}
|
||||
|
||||
acc[severity].push(exp);
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
const visibleExplosions =
|
||||
activeTab === 'All' ? explosions : grouped?.[activeTab];
|
||||
|
||||
const filteredExplosions = (visibleExplosions ?? []).filter(searcher);
|
||||
|
||||
return (
|
||||
<Window width={350} height={500}>
|
||||
<Window.Content scrollable>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<DopplerTabs activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<DopplerSearch
|
||||
searchFields={searchFields}
|
||||
setSearchFields={setSearchFields}
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
{filteredExplosions.length ? (
|
||||
<DopplerExplosions explosions={filteredExplosions} />
|
||||
) : (
|
||||
<NoticeBox>
|
||||
<Box inline verticalAlign="middle">
|
||||
No recorded explosions.
|
||||
</Box>
|
||||
</NoticeBox>
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
export type Data = {
|
||||
explosions: Explosion[];
|
||||
};
|
||||
|
||||
export type Explosion = {
|
||||
index: number;
|
||||
time: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
devastation_range: number;
|
||||
heavy_impact_range: number;
|
||||
light_impact_range: number;
|
||||
seconds_taken: number;
|
||||
};
|
||||
|
||||
export type SearchFields = {
|
||||
time: boolean;
|
||||
coordinates: boolean;
|
||||
inner_radius: boolean;
|
||||
outer_radius: boolean;
|
||||
shockwave: boolean;
|
||||
tachyon: boolean;
|
||||
};
|
||||
@@ -4557,10 +4557,12 @@
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\destructive.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\machines.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\misc.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\ordinance.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\people.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\physical.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\instances\teleporting.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\experiment.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\ordinance.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\physical_experiment.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\random_scanning.dm"
|
||||
#include "code\modules\research\tg\experisci\experiment\types\scanning.dm"
|
||||
|
||||
Reference in New Issue
Block a user