Files
Bubberstation/code/modules/research/ordnance/_scipaper.dm
SkyratBot 73bdd7341a [MIRROR] TGUI Destructive Analyzer [MDB IGNORE] (#25005)
* TGUI Destructive Analyzer (#79572)

## About The Pull Request

I made this to help me move more towards my goals [laid out
here](https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA) which currently doesn't
have much interest.

This makes the Destructive Analyzer use a little neat TGUI menu instead
of its old HTML one. I also touch a lot of science stuff and a little
experimentor stuff, so let me explain a bit:
Old iterations of Science had different items that you can use to boost
nodes through deconstruction. This has been removed, and its only
feature is the auto-unlocking of nodes (that is; making them visible to
the R&D console). I thought that instead of keeping this deprecated code
around, I would rework it a little to make it clear what we actually use
it for (unhiding nodes).
All vars and procs that mentioned this have been renamed or reworked to
make more sense now.

Experimentor stuff shares a lot with the destructive analyzer, so I had
to mess with that a bit to keep its decayed corpse of deprecated code,
functional.

I also added context tips to the destructive analyzer, and added the
ability to AltClick to remove the inserted item. Removing items now also
plays a little sound because it was kinda lame.
Also, balloon alerts.

## Why It's Good For The Game

Moves a shitty machine to TGUI so it is slightly less shitty, now it's
more direct and compact with more player-feedback.
Helps me with a personal project and yea

### Video demonstration

I show off connecting the machine to R&D Servers, but I haven't changed
the behavior of that and the roundstart analyzers are connected to
servers by default.

https://github.com/tgstation/tgstation/assets/53777086/65295600-4fae-42d1-9bae-eccefe337a2b

## Changelog

🆑
refactor: Destructive Analyzers now have a TGUI menu.
/🆑

* TGUI Destructive Analyzer

* Modular

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
2023-11-14 09:19:08 -05:00

307 lines
12 KiB
Plaintext

/// Scientific paper datum for retrieval and re-reading. A lot of the variables are there for fluff & flavor.
/datum/scientific_paper
/// The title of our paper.
var/title
/// The principal author of our paper.
var/author
/// Whether this paper is co-authored or not.
var/et_alia = FALSE
/// Abstract.
var/abstract
/// The coop and funding gains from the paper.
var/list/gains
/// Experiment typepath.
var/datum/experiment/ordnance/experiment_path
/// The main "score" of a particular experiment.
var/tracked_variable
/**
* Derived from tracked_variable. Used for indexing and to reduce duplicates.
* Only one paper can be published in each tier for each experiment.
*/
var/tier
/// The selected sponsor for our paper. Pathtype form.
var/datum/scientific_partner/partner_path
/datum/scientific_paper/New()
. = ..()
set_amount()
/**
* Calculate the gains of an experiment.
* Gain calculation follows a sigmoid curve.
* f(x) = L / (1+e^(-k(x-xo)))
* L is the upper limit. This should be the gain variable * 2.
* k is the steepness.
* x0 is the midpoint.
* x is our tracked variable.
* Returns the expected value of that tier.
*/
/datum/scientific_paper/proc/calculate_gains(calculated_tier)
if(!experiment_path || !tracked_variable)
return FALSE
var/datum/experiment/ordnance/initialized_experiment = locate(experiment_path) in SSresearch.ordnance_experiments
var/gain = initialized_experiment.gain[calculated_tier]
var/target_amount = initialized_experiment.target_amount[calculated_tier]
/// Steepness is calculated so that f(x=0) is always 1.
var/steepness = log(gain*2 - 1) / target_amount
var/calculated_gain = gain*2 / (1+NUM_E**(-steepness*(tracked_variable-target_amount)))
return calculated_gain
/// Determine which tier can we publish at. Lower limit for an allowed tier is 10% of gain. Empty list if none are allowed.
/datum/scientific_paper/proc/calculate_tier()
var/list/allowed_tiers = list()
if(!experiment_path || !tracked_variable)
return allowed_tiers
var/datum/experiment/ordnance/initialized_experiment = locate(experiment_path) in SSresearch.ordnance_experiments
for (var/each_tier in 1 to min(length(initialized_experiment.target_amount), length(initialized_experiment.gain)))
var/calculated_gain = calculate_gains(each_tier)
if(calculated_gain > 0.1 * initialized_experiment.gain[each_tier])
allowed_tiers += each_tier
return allowed_tiers
/**
* Experiment -> Tier -> Gains
* Experiment -> Partners -> Gains
* Changing anything in the chain means those following it should be recounted.
*/
/**
* Used when assigning an experiment to a specific paper.
* Failing to provide a proper path, a tracked variable, or a correct data should null every non-fluff data.
* Implement this in the children procs.
*/
/datum/scientific_paper/proc/set_experiment(ex_path = null, variable = null, data = null)
return
/// Sets a tier for us. Nulls the tier when called without args. Re-counts the amount.
/datum/scientific_paper/proc/set_tier(assigned_tier = null)
tier = null
if(assigned_tier && (assigned_tier in calculate_tier()))
tier = assigned_tier
set_amount()
/** Sets a specific partner for our paper. Partners give money and boost points.
* Partners exist separately from experiments and wont need to be reset every time something changes.
*/
/datum/scientific_paper/proc/set_partner(new_partner = null)
partner_path = null
if(ispath(new_partner, /datum/scientific_partner))
var/datum/scientific_partner/partner = locate(new_partner) in SSresearch.scientific_partners
if(experiment_path in partner.accepted_experiments)
partner_path = new_partner
set_amount()
/** Does the counting for gains.
* Call this whenever experiments/tier/partners are changed.
* Handled automatically, calling this proc externally shouldn't be necessary.
* Also doubles as an initialization for the gains list.
*/
/datum/scientific_paper/proc/set_amount()
gains = list(SCIPAPER_COOPERATION_INDEX = 0, SCIPAPER_FUNDING_INDEX = 0)
if(!tier || !experiment_path || !tracked_variable)
return FALSE
var/gain = calculate_gains(tier)
for (var/gain_type in 1 to gains.len)
gains[gain_type] = gain
if(!partner_path)
continue
var/datum/scientific_partner/partner = locate(partner_path) in SSresearch.scientific_partners
gains[gain_type] *= partner.multipliers[gain_type]
/** Fully check if our paper have all the required variables, and prevent duplicate papers being published in the same tier.
* Things to check: tier, gain, and partner here. ex_path and record datums in subtypes.
*/
/datum/scientific_paper/proc/allowed_to_publish(datum/techweb/techweb_to_check)
if(!tier || !gains || !partner_path || (0 in gains))
return FALSE
return !techweb_to_check.published_papers[experiment_path][tier]
/datum/scientific_paper/proc/publish_paper(datum/techweb/techweb_to_publish)
autofill()
techweb_to_publish.published_papers[experiment_path][tier] = src
techweb_to_publish.scientific_cooperation[partner_path] += gains[SCIPAPER_COOPERATION_INDEX]
if(istype(techweb_to_publish, /datum/techweb/science))
var/datum/bank_account/dept_budget = SSeconomy.get_dep_account(ACCOUNT_SCI)
if(dept_budget)
dept_budget.adjust_money(gains[SCIPAPER_FUNDING_INDEX] * SCIPAPER_GAIN_TO_MONEY)
/**
* Clones into a new paper type.
* Important (non-fluff) variables will be carried over and should be cleaned with set_experiment by whoever is calling this.
*
* clone_into your own typepath will be like a normal clone.
*
* If you want to subtype this, do it in a way that doesn't mess with the type change.
*/
/datum/scientific_paper/proc/clone_into(typepath)
var/datum/scientific_paper/new_paper = new typepath
new_paper.title = title
new_paper.author = author
new_paper.et_alia = et_alia
new_paper.abstract = abstract
new_paper.gains = gains
new_paper.experiment_path = experiment_path
new_paper.tracked_variable = tracked_variable
new_paper.tier = tier
new_paper.partner_path = partner_path
return new_paper
/// Returns the formatted, readable gist of our paper in a list.
/datum/scientific_paper/proc/return_gist()
var/list/gist = list()
var/list/transcripted_gains = list(SCIPAPER_COOPERATION_INDEX, SCIPAPER_FUNDING_INDEX)
for (var/index in 1 to transcripted_gains.len)
if (!gains)
transcripted_gains[index] = "None"
continue
switch (round(gains[index]))
if(-INFINITY to 0)
transcripted_gains[index] = "None"
if(1 to 24)
transcripted_gains[index] = "Little"
if(25 to 49)
transcripted_gains[index] = "Moderate"
if(50 to 99)
transcripted_gains[index] = "Significant"
if(100 to INFINITY)
transcripted_gains[index] = "Huge"
else
transcripted_gains[index] = "Undefined"
gist["gains"] = transcripted_gains
gist["title"] = title
gist["author"] = author
gist["etAlia"] = et_alia
gist["abstract"] = abstract
gist["experimentName"] = initial(experiment_path?.name)
gist["tier"] = tier
gist["partner"] = initial(partner_path?.name)
return gist
/datum/scientific_paper/proc/autofill()
if(!title)
title = "On [initial(experiment_path.name)] - [tier]"
if(!author)
author = "Unknown"
et_alia = FALSE
if(!abstract)
abstract = "Published on [station_time_timestamp()]"
/datum/scientific_paper/explosive
/**
* Used to check explosive experiments that needs to be unique.
* References a tachyon record where applicable.
*/
var/datum/data/tachyon_record/explosion_record
/// Check if our explosion has already been published and whether the experiment path is correct or not.
/datum/scientific_paper/explosive/allowed_to_publish(datum/techweb/techweb_to_check)
if(!ispath(experiment_path, /datum/experiment/ordnance/explosive))
return FALSE
if(!istype(explosion_record))
return FALSE
var/list/published_papers = techweb_to_check.published_papers
for(var/experiment_path in published_papers)
for(var/datum/scientific_paper/explosive/papers in published_papers[experiment_path])
var/datum/data/tachyon_record/record_to_check = papers.explosion_record
if(explosion_record.explosion_identifier == record_to_check.explosion_identifier)
return FALSE
return ..()
/datum/scientific_paper/explosive/set_experiment(ex_path = null, variable = null, data = null)
if(!ispath(ex_path, /datum/experiment/ordnance/explosive) || !variable || !istype(data, /datum/data/tachyon_record))
experiment_path = null
tracked_variable = null
explosion_record = null
else
experiment_path = ex_path
tracked_variable = variable
explosion_record = data
set_tier()
set_partner()
/datum/scientific_paper/explosive/clone_into(typepath)
if(typepath != type)
return ..()
var/datum/scientific_paper/explosive/new_paper = ..(typepath)
new_paper.explosion_record = explosion_record
return new_paper
/datum/scientific_paper/gaseous
var/datum/data/compressor_record/compressor_record
/**
* Check if our record datum is a duplicate or no.
* No index number is necessary because compressor records cant be replicated.
* Also checks the experiment path.
*/
/datum/scientific_paper/gaseous/allowed_to_publish(datum/techweb/techweb_to_check)
if(!ispath(experiment_path, /datum/experiment/ordnance/gaseous))
return FALSE
if(!istype(compressor_record))
return FALSE
var/list/published_papers = techweb_to_check.published_papers
for(var/experiment_path in published_papers)
for(var/datum/scientific_paper/gaseous/papers in published_papers[experiment_path])
if(compressor_record == papers.compressor_record)
return FALSE
. = ..()
/datum/scientific_paper/gaseous/set_experiment(ex_path = null, variable = null, data = null)
var/invalid = FALSE
invalid = invalid || !ispath(ex_path, /datum/experiment/ordnance/gaseous)
invalid = invalid || !variable
invalid = invalid || !istype(data, /datum/data/compressor_record)
if(invalid)
experiment_path = null
tracked_variable = null
compressor_record = null
else
experiment_path = ex_path
tracked_variable = variable
compressor_record = data
set_tier()
set_partner()
/datum/scientific_paper/gaseous/clone_into(typepath)
if(typepath != type)
return ..()
var/datum/scientific_paper/gaseous/new_paper = ..(typepath)
new_paper.compressor_record = compressor_record
return new_paper
/// Various informations on companies/scientific programs/journals etc that the players can sign on to.
/datum/scientific_partner
/// Name of the partner, shown in the Science program's UI.
var/name
/// Brief explanation of the associated program. Can be used for lore.
var/flufftext
/// Cash and renown multiplier for allying with this partner.
var/list/multipliers = list(SCIPAPER_COOPERATION_INDEX = 1, SCIPAPER_FUNDING_INDEX = 1)
/// List of ordnance experiments that our partner is willing to accept. If this list is not filled it means the partner will accept everything.
var/list/accepted_experiments = list()
/// Associative list of which technology the partner might be able to boost and by how much.
var/list/boostable_nodes = list()
/datum/scientific_partner/proc/purchase_boost(datum/techweb/purchasing_techweb, datum/techweb_node/node)
if(!allowed_to_boost(purchasing_techweb, node.id))
return FALSE
purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC = boostable_nodes[node.id]))
purchasing_techweb.scientific_cooperation[type] -= boostable_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER
return TRUE
/datum/scientific_partner/proc/allowed_to_boost(datum/techweb/purchasing_techweb, node_id)
if(purchasing_techweb.scientific_cooperation[type] < (boostable_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive
return FALSE
if(!(node_id in purchasing_techweb.get_available_nodes())) // Not currently available
return FALSE
if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boostable_nodes[node_id])) // Already bought or we have a bigger discount
return FALSE
return TRUE