Artur
2020-04-23 13:28:09 +03:00
parent 9d870893fb
commit 321902c35a
19 changed files with 301 additions and 79 deletions

View File

@@ -1360,7 +1360,7 @@
/obj/structure/closet/crate,
/obj/item/disk/data{
desc = "A data disk used to store cloning and genetic records. The name on the label appears to be scratched off.";
fields = list("label" = "Buffer1:Kr-$$@##", "UI" = "f8f603857000f930127c4", "SE" = "414401462231053131010241514651403453121613263463440351136366", "UE" = "340008485c321e542aed4df7032ac04d", "name" = "Krystal Symers", "blood_type" = "A+");
genetic_makeup_buffer = list("label" = "Buffer1:Kr-$$@##", "UI" = "f8f603857000f930127c4", "SE" = "414401462231053131010241514651403453121613263463440351136366", "UE" = "340008485c321e542aed4df7032ac04d", "name" = "Krystal Symers", "blood_type" = "A+");
name = "dusty genetics data disk";
read_only = 1
},
@@ -1749,7 +1749,7 @@
/obj/structure/closet/crate,
/obj/item/disk/data{
desc = "A data disk used to store cloning and genetic records. The name on the label appears to be scratched off with the words 'DO NOT CLONE' hastily written over it.";
fields = list("label" = "Buffer1:George Melons", "UI" = "3c300f11b5421ca7014d8", "SE" = "430431205660551642142504334461413202111310233445620533134255", "UE" = "6893e6a0b0076a41897776b10cc2b324", "name" = "George Melons", "blood_type" = "B+");
genetic_makeup_buffer = list("label" = "Buffer1:George Melons", "UI" = "3c300f11b5421ca7014d8", "SE" = "430431205660551642142504334461413202111310233445620533134255", "UE" = "6893e6a0b0076a41897776b10cc2b324", "name" = "George Melons", "blood_type" = "B+");
name = "old genetics data disk"
},
/obj/item/disk/data{

View File

@@ -15,6 +15,7 @@
var/mob/living/holder
var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875
var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block
var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting
var/stability = 100
var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers.
var/skin_tone_override //because custom skin tones are not found in the skin_tones global list.
@@ -58,6 +59,7 @@
H.give_genitals(TRUE)//This gives the body the genitals of this DNA. Used for any transformations based on DNA
if(transfer_SE)
destination.dna.mutation_index = mutation_index
destination.dna.default_mutation_genes = default_mutation_genes
destination.dna.update_body_size(old_size)
@@ -66,6 +68,7 @@
/datum/dna/proc/copy_dna(datum/dna/new_dna)
new_dna.unique_enzymes = unique_enzymes
new_dna.mutation_index = mutation_index
new_dna.default_mutation_genes = default_mutation_genes
new_dna.uni_identity = uni_identity
new_dna.blood_type = blood_type
new_dna.skin_tone_override = skin_tone_override
@@ -159,15 +162,18 @@
if(!LAZYLEN(mutations_temp))
return
mutation_index.Cut()
default_mutation_genes.Cut()
shuffle_inplace(mutations_temp)
if(ismonkey(holder))
mutations |= new RACEMUT(MUT_NORMAL)
mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT)
else
mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE)
default_mutation_genes[RACEMUT] = mutation_index[RACEMUT]
for(var/i in 2 to DNA_MUTATION_BLOCKS)
var/datum/mutation/human/M = mutations_temp[i]
mutation_index[M.type] = create_sequence(M.type, FALSE,M.difficulty)
mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty)
default_mutation_genes[M.type] = mutation_index[M.type]
shuffle_inplace(mutation_index)
//Used to generate original gene sequences for every mutation
@@ -372,8 +378,8 @@
return dna
/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures)
/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations)
//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc)
if(newfeatures)
var/old_size = dna.features["body_size"]
dna.features = newfeatures
@@ -397,6 +403,10 @@
if(LAZYLEN(mutation_index))
dna.mutation_index = mutation_index.Copy()
if(LAZYLEN(default_mutation_genes))
dna.default_mutation_genes = default_mutation_genes.Copy()
else
dna.default_mutation_genes = mutation_index.Copy()
domutcheck()
SEND_SIGNAL(src, COMSIG_HUMAN_HARDSET_DNA, ui, mutation_index, newreal_name, newblood_type, mrace, newfeatures)
@@ -488,8 +498,11 @@
. = TRUE
if(on)
mutation_index[HM.type] = GET_SEQUENCE(HM.type)
default_mutation_genes[HM.type] = mutation_index[HM.type]
else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type])
mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty)
default_mutation_genes[HM.type] = mutation_index[HM.type]
/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob
if(!mutation)

View File

@@ -191,3 +191,23 @@
power.panel = "Genetic"
owner.AddSpell(power)
return TRUE
// Runs through all the coefficients and uses this to determine which chromosomes the
// mutation can take. Stores these as text strings in a list.
/datum/mutation/human/proc/update_valid_chromosome_list()
valid_chrom_list.Cut()
if(can_chromosome == CHROMOSOME_NEVER)
valid_chrom_list += "none"
return
valid_chrom_list += "Reinforcement"
if(stabilizer_coeff != -1)
valid_chrom_list += "Stabilizer"
if(synchronizer_coeff != -1)
valid_chrom_list += "Synchronizer"
if(power_coeff != -1)
valid_chrom_list += "Power"
if(energy_coeff != -1)
valid_chrom_list += "Energetic"

View File

@@ -53,32 +53,32 @@
/obj/item/chromosome/stabilizer
name = "stabilizer chromosome"
desc = "A chromosome that adjusts to the body to reduce genetic damage by 20%."
desc = "A chromosome that reduces mutation instability by 20%."
icon_state = "stabilizer"
stabilizer_coeff = 0.8
weight = 1
/obj/item/chromosome/synchronizer
name = "synchronizer chromosome"
desc = "A chromosome that gives the mind more controle over the mutation, reducing knockback and downsides by 50%."
desc = "A chromosome that reduces mutation knockback and downsides by 50%."
icon_state = "synchronizer"
synchronizer_coeff = 0.5
/obj/item/chromosome/power
name = "power chromosome"
desc = "A power chromosome for boosting certain mutation's power by 50%."
desc = "A chromosome that increases mutation power by 50%."
icon_state = "power"
power_coeff = 1.5
/obj/item/chromosome/energy
name = "energetic chromosome"
desc = "A chromosome that reduces cooldown on action based mutations by 50%."
desc = "A chromosome that reduces action based mutation cooldowns by by 50%."
icon_state = "energy"
energy_coeff = 0.5
/obj/item/chromosome/reinforcer
name = "reinforcement chromosome"
desc = "Renders the mutation immune to mutadone."
desc = "A chromosome that renders mutations immune to mutadone."
icon_state = "reinforcer"
weight = 3

View File

@@ -421,7 +421,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
new_character.real_name = record_found.fields["name"]
new_character.gender = record_found.fields["gender"]
new_character.age = record_found.fields["age"]
new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
else
var/datum/preferences/A = new()
A.copy_to(new_character)

View File

@@ -115,7 +115,7 @@
features["mcolor"] = "#59CE00"
for(var/V in quirks)
new V(podman)
podman.hardset_dna(null,null,podman.real_name,blood_type, new /datum/species/pod,features)//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman
podman.hardset_dna(null,null,null,podman.real_name,blood_type, new /datum/species/pod,features)//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman
podman.set_cloned_appearance()
else //else, one packet of seeds. maybe two

View File

@@ -63,3 +63,11 @@
var/damageoverlaytemp = 0
var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects
/// Protection (insulation) from the heat, Value 0-1 corresponding to the percentage of protection
var/heat_protection = 0 // No heat protection
/// Protection (insulation) from the cold, Value 0-1 corresponding to the percentage of protection
var/cold_protection = 0 // No cold protection
/// Timer id of any transformation
var/transformation_timer

View File

@@ -362,7 +362,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
//keep it at the right spot, so we can't have people taking shortcuts
var/location = C.dna.mutation_index.Find(inert_mutation)
C.dna.mutation_index[location] = new_species.inert_mutation
C.dna.default_mutation_genes[location] = C.dna.mutation_index[location]
C.dna.mutation_index[new_species.inert_mutation] = create_sequence(new_species.inert_mutation)
C.dna.default_mutation_genes[new_species.inert_mutation] = C.dna.mutation_index[new_species.inert_mutation]
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)

View File

@@ -1,26 +1,8 @@
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG))
if (notransform)
#define TRANSFORMATION_DURATION 22
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
if (notransform || transformation_timer)
return
//Handle items on mob
//first implants & organs
var/list/stored_implants = list()
var/list/int_organs = list()
if (tr_flags & TR_KEEPIMPLANTS)
for(var/X in implants)
var/obj/item/implant/IMP = X
stored_implants += IMP
IMP.removed(src, 1, 1)
var/list/missing_bodyparts_zones = get_missing_limbs()
var/obj/item/cavity_object
var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST)
if(CH.cavity_item)
cavity_object = CH.cavity_item
CH.cavity_item = null
if(tr_flags & TR_KEEPITEMS)
var/Itemlist = get_equipped_items(TRUE)
@@ -30,13 +12,36 @@
//Make mob invisible and spawn animation
notransform = TRUE
Stun(INFINITY, ignore_canstun = TRUE)
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
icon = null
cut_overlays()
invisibility = INVISIBILITY_MAXIMUM
new /obj/effect/temp_visual/monkeyify(loc)
sleep(22)
transformation_timer = addtimer(CALLBACK(src, .proc/finish_monkeyize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE)
/mob/living/carbon/proc/finish_monkeyize(tr_flags)
transformation_timer = null
var/list/missing_bodyparts_zones = get_missing_limbs()
var/list/stored_implants = list()
if (tr_flags & TR_KEEPIMPLANTS)
for(var/X in implants)
var/obj/item/implant/IMP = X
stored_implants += IMP
IMP.removed(src, 1, 1)
var/list/int_organs = list()
var/obj/item/cavity_object
var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST)
if(CH.cavity_item)
cavity_object = CH.cavity_item
CH.cavity_item = null
var/mob/living/carbon/monkey/O = new /mob/living/carbon/monkey( loc )
// hash the original name?
@@ -50,6 +55,7 @@
if(tr_flags & TR_KEEPSE)
O.dna.mutation_index = dna.mutation_index
O.dna.default_mutation_genes = dna.default_mutation_genes
O.dna.set_se(1, GET_INITIALIZED_MUTATION(RACEMUT))
if(suiciding)
@@ -149,12 +155,33 @@
////////////////////////// Humanize //////////////////////////////
//Could probably be merged with monkeyize but other transformations got their own procs, too
/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG))
if (notransform)
/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
if (notransform || transformation_timer)
return
//Handle items on mob
//first implants & organs
//now the rest
if (tr_flags & TR_KEEPITEMS)
var/Itemlist = get_equipped_items(TRUE)
Itemlist += held_items
for(var/obj/item/W in Itemlist)
dropItemToGround(W, TRUE)
if (client)
client.screen -= W
//Make mob invisible and spawn animation
notransform = TRUE
Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
icon = null
cut_overlays()
invisibility = INVISIBILITY_MAXIMUM
new /obj/effect/temp_visual/monkeyify/humanify(loc)
transformation_timer = addtimer(CALLBACK(src, .proc/finish_humanize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE)
/mob/living/carbon/proc/finish_humanize(tr_flags)
transformation_timer = null
var/list/stored_implants = list()
var/list/int_organs = list()
@@ -173,25 +200,6 @@
cavity_object = CH.cavity_item
CH.cavity_item = null
//now the rest
if (tr_flags & TR_KEEPITEMS)
var/Itemlist = get_equipped_items(TRUE)
Itemlist += held_items
for(var/obj/item/W in Itemlist)
dropItemToGround(W, TRUE)
if (client)
client.screen -= W
//Make mob invisible and spawn animation
notransform = TRUE
Stun(22, ignore_canstun = TRUE)
icon = null
cut_overlays()
invisibility = INVISIBILITY_MAXIMUM
new /obj/effect/temp_visual/monkeyify/humanify(loc)
sleep(22)
var/mob/living/carbon/human/O = new( loc )
for(var/obj/item/C in O.loc)
O.equip_to_appropriate_slot(C)
@@ -208,6 +216,7 @@
if(tr_flags & TR_KEEPSE)
O.dna.mutation_index = dna.mutation_index
O.dna.default_mutation_genes = dna.default_mutation_genes
O.dna.set_se(0, GET_INITIALIZED_MUTATION(RACEMUT))
O.domutcheck()
@@ -580,3 +589,44 @@
. = new_mob
qdel(src)
/* Certain mob types have problems and should not be allowed to be controlled by players.
*
* This proc is here to force coders to manually place their mob in this list, hopefully tested.
* This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them.
*/
/mob/proc/safe_animal(MP)
//Bad mobs! - Remember to add a comment explaining what's wrong with the mob
if(!MP)
return 0 //Sanity, this should never happen.
if(ispath(MP, /mob/living/simple_animal/hostile/construct))
return 0 //Verbs do not appear for players.
//Good mobs!
if(ispath(MP, /mob/living/simple_animal/pet/cat))
return 1
if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi))
return 1
if(ispath(MP, /mob/living/simple_animal/crab))
return 1
if(ispath(MP, /mob/living/simple_animal/hostile/carp))
return 1
if(ispath(MP, /mob/living/simple_animal/hostile/mushroom))
return 1
if(ispath(MP, /mob/living/simple_animal/shade))
return 1
if(ispath(MP, /mob/living/simple_animal/hostile/killertomato))
return 1
if(ispath(MP, /mob/living/simple_animal/mouse))
return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak)
if(ispath(MP, /mob/living/simple_animal/hostile/bear))
return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak)
if(ispath(MP, /mob/living/simple_animal/parrot))
return 1 //Parrots are no longer unfinished! -Nodrak
//Not in here? Must be untested!
return 0
#undef TRANSFORMATION_DURATION

View File

@@ -132,7 +132,7 @@
lich.real_name = mind.name
mind.transfer_to(lich)
mind.grab_ghost(force=TRUE)
lich.hardset_dna(null,null,lich.real_name,null, new /datum/species/skeleton/space)
lich.hardset_dna(null,null,null,lich.real_name,null, new /datum/species/skeleton)
to_chat(lich, "<span class='warning'>Your bones clatter and shudder as you are pulled back into this world!</span>")
var/turf/body_turf = get_turf(old_body)
lich.DefaultCombatKnockdown(200 + 200*resurrections)

View File

@@ -9,6 +9,8 @@ const logger = createLogger('reloader');
const HOME = os.homedir();
const SEARCH_LOCATIONS = [
// Custom location
process.env.BYOND_CACHE,
// Windows
`${HOME}/*/BYOND/cache`,
// Wine
@@ -28,6 +30,9 @@ export const findCacheRoot = async () => {
logger.log('looking for byond cache');
// Find BYOND cache folders
for (let pattern of SEARCH_LOCATIONS) {
if (!pattern) {
continue;
}
const paths = await resolveGlob(pattern);
if (paths.length > 0) {
cacheRoot = paths[0];

View File

@@ -1,19 +1,18 @@
import { classes } from 'common/react';
import { Box } from './Box';
export const Dimmer = props => {
const { style, ...rest } = props;
const { className, children, ...rest } = props;
return (
<Box
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
'background-color': 'rgba(0, 0, 0, 0.75)',
'z-index': 1,
...style,
}}
{...rest} />
className={classes([
'Dimmer',
...className,
])}
{...rest}>
<div className="Dimmer__inner">
{children}
</div>
</Box>
);
};

View File

@@ -1,5 +1,5 @@
import { classes } from 'common/react';
import { Component, createRef } from 'inferno';
import { Component } from 'inferno';
import { Box } from './Box';
import { Icon } from './Icon';
@@ -46,7 +46,7 @@ export class Dropdown extends Component {
<div
key={option}
className="Dropdown__menuentry"
onClick={e => {
onClick={() => {
this.setSelected(option);
}}>
{option}
@@ -60,9 +60,12 @@ export class Dropdown extends Component {
const {
color = 'default',
over,
noscroll,
nochevron,
width,
onClick,
selected,
disabled,
...boxProps
} = props;
const {
@@ -80,7 +83,7 @@ export class Dropdown extends Component {
'width': width,
}}
className={classes([
'Dropdown__menu',
noscroll && 'Dropdown__menu-noscroll' || 'Dropdown__menu',
over && 'Dropdown__over',
])}>
{this.buildMenu()}
@@ -95,18 +98,24 @@ export class Dropdown extends Component {
'Dropdown__control',
'Button',
'Button--color--' + color,
disabled && 'Button--disabled',
className,
])}
{...rest}
onClick={e => {
onClick={() => {
if (disabled && !this.state.open) {
return;
}
this.setOpen(!this.state.open);
}}>
<span className="Dropdown__selected-text">
{this.state.selected}
</span>
<span className="Dropdown__arrow-button">
<Icon name={adjustedOpen ? 'chevron-up' : 'chevron-down'} />
</span>
{!!nochevron || (
<span className="Dropdown__arrow-button">
<Icon name={adjustedOpen ? 'chevron-up' : 'chevron-down'} />
</span>
)}
</Box>
{menu}
</div>

View File

@@ -31,6 +31,17 @@
background-color: rgba(0, 0, 0, 0.75);
}
.Dropdown__menu-noscroll {
position: absolute;
overflow-y: auto;
z-index: 5;
width: 100px;
max-height: 200px;
border-radius: 0 0 2px 2px;
background-color: #000;
background-color: rgba(0, 0, 0, 0.75);
}
.Dropdown__menuentry {
padding: 2px 4px;
font-family: Verdana, sans-serif;

View File

@@ -0,0 +1,18 @@
import { classes } from 'common/react';
export const Divider = props => {
const {
vertical,
hidden,
} = props;
return (
<div
className={classes([
'Divider',
hidden && 'Divider--hidden',
vertical
? 'Divider--vertical'
: 'Divider--horizontal',
])} />
);
};

View File

@@ -0,0 +1,50 @@
@use '../colors.scss';
.outline-dotted {
outline-style: dotted !important;
outline-width: 2px !important;
}
.outline-dashed {
outline-style: dashed !important;
outline-width: 2px !important;
}
.outline-solid {
outline-style: solid !important;
outline-width: 2px !important;
}
.outline-double {
outline-style: double !important;
outline-width: 2px !important;
}
.outline-groove {
outline-style: groove !important;
outline-width: 2px !important;
}
.outline-ridge {
outline-style: ridge !important;
outline-width: 2px !important;
}
.outline-inset {
outline-style: inset !important;
outline-width: 2px !important;
}
.outline-outset {
outline-style: outset !important;
outline-width: 2px !important;
}
$fg-map: colors.$fg-map !default;
$bg-map: colors.$bg-map !default;
@each $color-name, $color-value in $fg-map {
.outline-color-#{$color-name} {
outline-color: $color-value !important;
}
}

View File

@@ -0,0 +1,16 @@
.Dimmer {
// Align everything in the middle.
// A fat middle finger for anything less than IE11.
display: flex;
justify-content: center;
align-items: center;
// Fill positioned parent
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
// Dim everything around it
background-color: rgba(0, 0, 0, 0.75);
z-index: 1;
}

View File

@@ -0,0 +1,20 @@
$color: rgba(255, 255, 255, 0.1) !default;
$thickness: 2px !default;
$spacing: 6px;
.Divider--horizontal {
margin: $spacing 0;
&:not(.Divider--hidden) {
border-top: $thickness solid $color;
}
}
.Divider--vertical {
height: 100%;
margin: 0 $spacing;
&:not(.Divider--hidden) {
border-left: $thickness solid $color;
}
}

View File

@@ -13,6 +13,7 @@
@include meta.load-css('./atomic/outline.scss');
@include meta.load-css('./atomic/position.scss');
@include meta.load-css('./atomic/text.scss');
@include meta.load-css('./atomic/outline.scss');
// Components
@include meta.load-css('./components/BlockQuote.scss');