/
doppler_array.dm
312 lines (265 loc) · 11.5 KB
/
doppler_array.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/obj/machinery/doppler_array
name = "tachyon-doppler array"
desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array.\n"
circuit = /obj/item/circuitboard/machine/doppler_array
icon = 'icons/obj/machines/research.dmi'
icon_state = "tdoppler"
base_icon_state = "tdoppler"
density = TRUE
verb_say = "states coldly"
var/cooldown = 10
var/next_announce = 0
var/max_dist = 150
/// Number which will be part of the name of the next record, increased by one for each already created record
var/record_number = 1
/// List of all explosion records in the form of /datum/data/tachyon_record
var/list/records = list()
/// Reference to a disk we are going to print to.
var/obj/item/computer_disk/inserted_disk
// Lighting system to better communicate the directions.
light_system = OVERLAY_LIGHT_DIRECTIONAL
light_range = 4
light_power = 1.5
light_color = COLOR_RED
/obj/machinery/doppler_array/Initialize(mapload)
. = ..()
RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, PROC_REF(sense_explosion))
RegisterSignal(src, COMSIG_MACHINERY_POWER_LOST, PROC_REF(update_doppler_light))
RegisterSignal(src, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(update_doppler_light))
update_doppler_light()
// Rotation determines the detectable direction.
AddComponent(/datum/component/simple_rotation)
/datum/data/tachyon_record
name = "Log Recording"
var/timestamp
var/coordinates = ""
var/displacement = 0
var/factual_radius = list()
var/theory_radius = list()
/// Indexed to length 3 if filled properly. Should be an empty list otherwise.
var/reaction_results = list()
var/explosion_identifier
/obj/machinery/doppler_array/examine(mob/user)
. = ..()
. += span_notice("It is currently facing [dir2text(dir)]")
/obj/machinery/doppler_array/attackby(obj/item/item, mob/user, params)
if(istype(item, /obj/item/computer_disk))
var/obj/item/computer_disk/disk = item
eject_disk(user)
if(user.transferItemToLoc(disk, src))
inserted_disk = disk
return
else
balloon_alert(user, "it's stuck to your hand!")
return ..()
return ..()
/obj/machinery/doppler_array/wrench_act(mob/living/user, obj/item/tool)
default_unfasten_wrench(user, tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/doppler_array/screwdriver_act(mob/living/user, obj/item/tool)
if(!default_deconstruction_screwdriver(user, "[base_icon_state]", "[base_icon_state]", tool))
return FALSE
power_change()
update_appearance()
return TRUE
/obj/machinery/doppler_array/crowbar_act(mob/living/user, obj/item/tool)
if(!default_deconstruction_crowbar(tool))
return FALSE
return TRUE
/// Printing of a record into a disk.
/obj/machinery/doppler_array/proc/print(mob/user, datum/data/tachyon_record/record)
if(!record || !inserted_disk)
return
var/datum/computer_file/data/ordnance/explosive/record_data = new
record_data.filename = "Doppler Array " + record.name //Doppler Array Log Recording #x
record_data.explosion_record = record
record_data.possible_experiments = apply_experiments(record)
if(inserted_disk.add_file(record_data))
playsound(src, 'sound/machines/ping.ogg', 25)
else
playsound(src, 'sound/machines/terminal_error.ogg', 25)
/**
* Checks a specified tachyon record for fitting reactions, then returns a list with
* the experiment typepath as key and score as value.
* The score is the same for all explosive experiments (light radius).
*/
/obj/machinery/doppler_array/proc/apply_experiments(datum/data/tachyon_record/record)
var/list/passed_experiments = list()
var/list/record_reactions = record.reaction_results
var/list/radius_used = length(record.theory_radius) ? record.theory_radius : record.factual_radius
var/explosion_score = radius_used["shockwave_radius"]
if(!explosion_score) // Dont even bother.
return passed_experiments
/// Early return for explosions without proper reaction_results. We will append experiments that are always fair game.
if(!record_reactions.len)
for (var/datum/experiment/ordnance/explosive/experiment in SSresearch.ordnance_experiments)
if(experiment.allow_any_source)
passed_experiments += list(experiment.type = explosion_score)
return passed_experiments
for (var/datum/experiment/ordnance/explosive/experiment in SSresearch.ordnance_experiments)
var/list/experiment_reactions = experiment.required_reactions
/// The reactions in record.reaction_results that corresponds to the required_reactions list.
var/list/fitting_reactions = list()
/// The reactions in required_reactions list that are missing in record.reaction_results.
var/list/missing_reactions = list()
/// The reactions in record.reaction_results that are not present in required_reactions list.
var/list/extra_reactions = list()
for(var/reaction in experiment_reactions)
if(reaction in record_reactions[TANK_RESULTS_REACTION])
fitting_reactions += reaction
else
missing_reactions += reaction
for(var/reaction in record_reactions[TANK_RESULTS_REACTION])
if(!(reaction in fitting_reactions))
extra_reactions += reaction
// Check for extra reactions.
if(experiment.sanitized_reactions && extra_reactions.len)
continue
// Check for misc properties
if(experiment.sanitized_misc && length(record_reactions[TANK_RESULTS_MISC]))
continue
// Check for missing reactions for those who require all.
if(experiment.require_all && missing_reactions.len)
continue
// For those who only need one, check if there is actually one.
if(!experiment.require_all && experiment_reactions.len)
if(!fitting_reactions.len)
continue
// Suceeded all check, append to the returned list.
passed_experiments += list(experiment.type = explosion_score)
return passed_experiments
/// Sensing, recording, and broadcasting of explosion
/obj/machinery/doppler_array/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range,
took, orig_dev_range, orig_heavy_range, orig_light_range, explosion_cause, explosion_index)
SIGNAL_HANDLER
var/list/fetched_reaction_results = list()
if(istype(explosion_cause, /obj/item/tank))
var/obj/item/tank/exploding_tank = explosion_cause
fetched_reaction_results = exploding_tank.explosion_information()
if(machine_stat & NOPOWER)
return FALSE
var/turf/zone = get_turf(src)
if(!is_valid_z_level(zone, epicenter))
return FALSE
if(next_announce > world.time)
return FALSE
next_announce = world.time + cooldown
if((get_dist(epicenter, zone) > max_dist) || !(get_dir(zone, epicenter) & dir))
return FALSE
var/datum/data/tachyon_record/new_record = new /datum/data/tachyon_record()
new_record.name = "Log Recording #[record_number]"
new_record.timestamp = station_time_timestamp()
new_record.coordinates = "[epicenter.x], [epicenter.y]"
new_record.displacement = took
new_record.factual_radius["epicenter_radius"] = devastation_range
new_record.factual_radius["outer_radius"] = heavy_impact_range
new_record.factual_radius["shockwave_radius"] = light_impact_range
new_record.reaction_results = fetched_reaction_results
new_record.explosion_identifier = explosion_index
var/list/messages = list("Explosive disturbance detected.",
"Epicenter at: grid ([epicenter.x], [epicenter.y]). Temporal displacement of tachyons: [took] seconds.",
"Factual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].",
)
// If the bomb was capped, say its theoretical size.
if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range)
messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]."
new_record.theory_radius["epicenter_radius"] = orig_dev_range
new_record.theory_radius["outer_radius"] = orig_heavy_range
new_record.theory_radius["shockwave_radius"] = orig_light_range
for(var/message in messages)
say(message)
record_number++
records += new_record
return TRUE
/obj/machinery/doppler_array/proc/eject_disk(mob/user)
if(!inserted_disk)
return FALSE
if(user)
user.put_in_hands(inserted_disk)
else
inserted_disk.forceMove(drop_location())
playsound(src, 'sound/machines/card_slide.ogg', 50)
return TRUE
/// We rely on exited to clear references.
/obj/machinery/doppler_array/Exited(atom/movable/gone, direction)
if(gone == inserted_disk)
inserted_disk = null
return ..()
/obj/machinery/doppler_array/powered()
if(panel_open)
return FALSE
return ..()
/obj/machinery/doppler_array/update_overlays()
. = ..()
if(panel_open)
. += mutable_appearance(icon, "[base_icon_state]_open")
else
. += mutable_appearance(icon,"[base_icon_state]_cable")
if(machine_stat & BROKEN) // Probably meant to be used on an indestructible doppler, but this is not implemented.
. += mutable_appearance(icon, "[base_icon_state]_screen-broken")
else if (machine_stat & NOPOWER)
. += mutable_appearance(icon, "[base_icon_state]_screen-off")
/obj/machinery/doppler_array/on_deconstruction(disassembled)
eject_disk()
. = ..()
/obj/machinery/doppler_array/Destroy()
inserted_disk = null
QDEL_NULL(records) //We only want the list nuked, not the contents.
. = ..()
/obj/machinery/doppler_array/proc/update_doppler_light()
SIGNAL_HANDLER
set_light_on(!(machine_stat & NOPOWER))
/obj/machinery/doppler_array/ui_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/ui_data(mob/user)
var/list/data = list()
data["records"] = list()
data["disk"] = inserted_disk?.name
data["storage"] = "[inserted_disk?.used_capacity] / [inserted_disk?.max_capacity] GQ"
for(var/datum/data/tachyon_record/singular_record in records)
var/list/record_data = list(
"name" = singular_record.name,
"timestamp" = singular_record.timestamp,
"coordinates" = singular_record.coordinates,
"displacement" = singular_record.displacement,
"factual_epicenter_radius" = singular_record.factual_radius["epicenter_radius"],
"factual_outer_radius" = singular_record.factual_radius["outer_radius"],
"factual_shockwave_radius" = singular_record.factual_radius["shockwave_radius"],
"theory_epicenter_radius" = singular_record.theory_radius["epicenter_radius"],
"theory_outer_radius" = singular_record.theory_radius["outer_radius"],
"theory_shockwave_radius" = singular_record.theory_radius["shockwave_radius"],
"reaction_results" = list(),
"ref" = REF(singular_record)
)
var/list/reaction_data = singular_record.reaction_results
// Make sure the list is indexed first.
if(reaction_data.len)
for (var/path in reaction_data[TANK_RESULTS_REACTION])
var/datum/gas_reaction/reaction_path = path
record_data["reaction_results"] += initial(reaction_path.name)
if(TANK_MERGE_OVERPRESSURE in reaction_data[TANK_RESULTS_MISC])
record_data["reaction_results"] += "Tank overpressurized before reaction"
data["records"] += list(record_data)
return data
/obj/machinery/doppler_array/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("delete_record")
var/datum/data/tachyon_record/record = locate(params["ref"]) in records
if(!records || !(record in records))
return
records -= record
return TRUE
if("save_record")
var/datum/data/tachyon_record/record = locate(params["ref"]) in records
if(!records || !(record in records))
return
print(usr, record)
return TRUE
if("eject_disk")
eject_disk(usr)