diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index 41fe7592fe..40f9d1de62 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -53,4 +53,17 @@ **File:** `sound/ambience/startup.ogg` and `sound/ambience/shutdown.ogg`
**Title:** Spaceship starup and shutdown
**Creator:** viznoman (https://freesound.org/people/viznoman/sounds/267308/)
-**License:** [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
\ No newline at end of file +**License:** [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
+
+**File:** `icons/obj/pillows.dmi`
+**Title:** pillows.dmi
+**Creator:** Lamella-0587 (https://github.com/Skyrat-SS13/Skyrat-tg/pull/6740)
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+
+**File:** `icons/mob/vore/taurs_vr_loaf.dmi`and `icons/mob/vore/taurs_vr.dmi`
+**Title:** Deer tummy
+**Icon-States:** `fatdeer_s` and `fatdeer_markings`
+**Creator:** VerySoft (https://github.com/TS-Rogue-Star/Rogue-Star/pull/153)
+**URL:** [Website](https://rogue-star.net/)
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+**Notes:** Permission to use assets also given in writing. These new icon-states are modifications of existing icon-states.
\ No newline at end of file diff --git a/code/ATMOSPHERICS/datum_pipe_network.dm b/code/ATMOSPHERICS/datum_pipe_network.dm index 20a4dbc159..2306775ad5 100644 --- a/code/ATMOSPHERICS/datum_pipe_network.dm +++ b/code/ATMOSPHERICS/datum_pipe_network.dm @@ -1,5 +1,3 @@ -var/global/list/datum/pipe_network/pipe_networks = list() // TODO - Move into SSmachines - /datum/pipe_network var/list/datum/gas_mixture/gases = list() //All of the gas_mixtures continuously connected in this network var/volume = 0 //caches the total volume for atmos machines to use in gas calculations diff --git a/code/ATMOSPHERICS/pipes/simple.dm b/code/ATMOSPHERICS/pipes/simple.dm index 1f427fb151..def462a354 100644 --- a/code/ATMOSPHERICS/pipes/simple.dm +++ b/code/ATMOSPHERICS/pipes/simple.dm @@ -96,7 +96,7 @@ node1 = null if(node2) node2.disconnect(src) - node1 = null + node2 = null . = ..() diff --git a/code/__defines/admin_vr.dm b/code/__defines/admin_vr.dm index 299b290699..83eb907f3c 100644 --- a/code/__defines/admin_vr.dm +++ b/code/__defines/admin_vr.dm @@ -16,4 +16,5 @@ #define MODIFIY_ROBOT_SWAP_MODULE "Swap a Robot Module" #define MODIFIY_ROBOT_RESET_MODULE "Fully Reset Robot Module" #define MODIFIY_ROBOT_TOGGLE_ERT "Toggle ERT Module Overwrite" +#define MODIFIY_ROBOT_TOGGLE_STATION_ACCESS "Toggle All Station Access Codes" #define MODIFIY_ROBOT_TOGGLE_CENT_ACCESS "Toggle Central Access Codes" diff --git a/code/__defines/atoms_movable.dm b/code/__defines/atoms_movable.dm new file mode 100644 index 0000000000..bbcf1eb6d1 --- /dev/null +++ b/code/__defines/atoms_movable.dm @@ -0,0 +1,7 @@ +/* +** Holds defines for code\game\atoms_movable.dm to avoid magic numbers and potential unexpected overwrites down the line +*/ + +#define DEFAULT_ICON_SCALE_X 1 +#define DEFAULT_ICON_SCALE_Y 1 +#define DEFAULT_ICON_ROTATION 0 diff --git a/code/__defines/belly_modes_vr.dm b/code/__defines/belly_modes_vr.dm index c86e66f1c0..35c8425640 100644 --- a/code/__defines/belly_modes_vr.dm +++ b/code/__defines/belly_modes_vr.dm @@ -36,3 +36,9 @@ #define MIN_NUTRITION_TO_GAIN 450 // Above this amount you will gain weight #define MAX_NUTRITION_TO_LOSE 50 // Below this amount you will lose weight // #define WEIGHT_PER_NUTRITION 0.0285 // Tuned so 1050 (nutrition for average mob) = 30 lbs + +// Drain modes +#define DR_NORMAL "Normal" +#define DR_SLEEP "Sleep" +#define DR_FAKE "False Sleep" +#define DR_WEIGHT "Weight Drain" diff --git a/code/__defines/chat.dm b/code/__defines/chat.dm index 681fc8867b..1a9ac03914 100644 --- a/code/__defines/chat.dm +++ b/code/__defines/chat.dm @@ -11,6 +11,7 @@ #define MESSAGE_TYPE_SYSTEM "system" #define MESSAGE_TYPE_LOCALCHAT "localchat" #define MESSAGE_TYPE_NPCEMOTE "npcemote" +#define MESSAGE_TYPE_MULTIZCHAT "multizsay" #define MESSAGE_TYPE_PLOCALCHAT "plocalchat" #define MESSAGE_TYPE_HIVEMIND "hivemind" #define MESSAGE_TYPE_RADIO "radio" diff --git a/code/__defines/color_priority.dm b/code/__defines/color_priority.dm new file mode 100644 index 0000000000..e88784987f --- /dev/null +++ b/code/__defines/color_priority.dm @@ -0,0 +1,11 @@ +//different types of atom colorations +///only used by rare effects like greentext coloring mobs and when admins varedit color +#define ADMIN_COLOUR_PRIORITY 1 +///e.g. purple effect of the revenant on a mob, black effect when mob electrocuted +#define TEMPORARY_COLOUR_PRIORITY 2 +///color splashed onto an atom (e.g. paint on turf) +#define WASHABLE_COLOUR_PRIORITY 3 +///color inherent to the atom (e.g. blob color) +#define FIXED_COLOUR_PRIORITY 4 +///how many priority levels there are. +#define COLOUR_PRIORITY_AMOUNT 4 diff --git a/code/__defines/lum.dm b/code/__defines/lum.dm new file mode 100644 index 0000000000..942462b573 --- /dev/null +++ b/code/__defines/lum.dm @@ -0,0 +1,4 @@ +//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1. +#define LUMA_R 0.213 +#define LUMA_G 0.715 +#define LUMA_B 0.072 diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 9d882883a4..eaafb41e90 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -157,17 +157,17 @@ if (!(DATUM.datum_flags & DF_ISPROCESSING)) {\ #define STOP_PROCESSING_IN_LIST(DATUM, LIST) LIST.Remove(DATUM);DATUM.datum_flags &= ~DF_ISPROCESSING // Note - I would prefer these be defined machines.dm, but some are used prior in file order. ~Leshana -#define START_MACHINE_PROCESSING(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_machines) -#define STOP_MACHINE_PROCESSING(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_machines) +#define START_MACHINE_PROCESSING(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.processing_machines) +#define STOP_MACHINE_PROCESSING(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.processing_machines) -#define START_PROCESSING_PIPENET(Datum) START_PROCESSING_IN_LIST(Datum, global.pipe_networks) -#define STOP_PROCESSING_PIPENET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.pipe_networks) +#define START_PROCESSING_PIPENET(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.networks) +#define STOP_PROCESSING_PIPENET(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.networks) -#define START_PROCESSING_POWERNET(Datum) START_PROCESSING_IN_LIST(Datum, global.powernets) -#define STOP_PROCESSING_POWERNET(Datum) STOP_PROCESSING_IN_LIST(Datum, global.powernets) +#define START_PROCESSING_POWERNET(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.powernets) +#define STOP_PROCESSING_POWERNET(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.powernets) -#define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, global.processing_power_items) -#define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, global.processing_power_items) +#define START_PROCESSING_POWER_OBJECT(Datum) START_PROCESSING_IN_LIST(Datum, SSmachines.powerobjs) +#define STOP_PROCESSING_POWER_OBJECT(Datum) STOP_PROCESSING_IN_LIST(Datum, SSmachines.powerobjs) // Computer login types #define LOGIN_TYPE_NORMAL 1 diff --git a/code/__defines/misc_vr.dm b/code/__defines/misc_vr.dm index f3f2b0ef84..b7eb768f6e 100644 --- a/code/__defines/misc_vr.dm +++ b/code/__defines/misc_vr.dm @@ -14,6 +14,11 @@ #define VANTAG_KIDNAP "vantag_kidnap" #define VANTAG_KILL "vantag_kill" +// ColorMate states +#define COLORMATE_TINT 1 +#define COLORMATE_HSV 2 +#define COLORMATE_MATRIX 3 + #define DEPARTMENT_OFFDUTY "Off-Duty" #define ANNOUNCER_NAME "Facility PA" diff --git a/code/__defines/text.dm b/code/__defines/text.dm new file mode 100644 index 0000000000..4b520e5771 --- /dev/null +++ b/code/__defines/text.dm @@ -0,0 +1,10 @@ +/** + * Holds global defines for use in text input procedures + */ + + +/** + * stuff like `copytext(input, length(input))` will trim the last character of the input, + * because DM does it so it copies until the char BEFORE the `end` arg, so we need to bump `end` by 1 in these cases. + */ +#define PREVENT_CHARACTER_TRIM_LOSS(integer) (integer + 1) diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 9fd9e18500..414238fcaa 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -80,8 +80,8 @@ Blend(M, ICON_ADD) /proc/BlendRGB(rgb1, rgb2, amount) - var/list/RGB1 = rgb2num(rgb1) - var/list/RGB2 = rgb2num(rgb2) + var/list/RGB1 = ReadRGB(rgb1) + var/list/RGB2 = ReadRGB(rgb2) // add missing alpha if needed if(RGB1.len < RGB2.len) RGB1 += 255 diff --git a/code/_helpers/icons/flatten.dm b/code/_helpers/icons/flatten.dm new file mode 100644 index 0000000000..0435f7b6e1 --- /dev/null +++ b/code/_helpers/icons/flatten.dm @@ -0,0 +1,268 @@ +//? File contains functions to generate flat /icon's from things. +//? This is obviously expensive. Very, very expensive. +//? new get_flat_icon is faster, but, really, don't use these unless you need to. +//? Chances are unless you are: +//? - sending to html/browser (for non character preview purposes) +//? - taking photos +//? - doing complex icon operations that can't be done with filters/overlays +//? you probably don't need to use these. + +/** + * Generates an icon with all 4 directions of something. + * + * @params + * - A - appearancelike object. + * - no_anim - flatten out animations + */ +/proc/get_compound_icon(atom/A, no_anim) + var/mutable_appearance/N = new + N.appearance = A + N.dir = NORTH + var/icon/north = get_flat_icon(N, NORTH, no_anim = no_anim) + N.dir = SOUTH + var/icon/south = get_flat_icon(N, SOUTH, no_anim = no_anim) + N.dir = EAST + var/icon/east = get_flat_icon(N, EAST, no_anim = no_anim) + N.dir = WEST + var/icon/west = get_flat_icon(N, WEST, no_anim = no_anim) + qdel(N) + //Starts with a blank icon because of byond bugs. + var/icon/full = icon('icons/system/blank_32x32.dmi', "") + full.Insert(north, dir = NORTH) + full.Insert(south, dir = SOUTH) + full.Insert(east, dir = EAST) + full.Insert(west, dir = WEST) + qdel(north) + qdel(south) + qdel(east) + qdel(west) + return full + +/proc/get_flat_icon(appearance/appearancelike, dir, no_anim) + if(!dir && isloc(appearancelike)) + dir = appearancelike.dir + return _get_flat_icon(appearancelike, dir, no_anim, null, TRUE) + +/proc/_get_flat_icon(image/A, defdir, no_anim, deficon, start) + // start with blank image + var/static/icon/template = icon('icons/system/blank_32x32.dmi', "") + + #define BLANK icon(template) + + #define INDEX_X_LOW 1 + #define INDEX_X_HIGH 2 + #define INDEX_Y_LOW 3 + #define INDEX_Y_HIGH 4 + + #define flatX1 flat_size[INDEX_X_LOW] + #define flatX2 flat_size[INDEX_X_HIGH] + #define flatY1 flat_size[INDEX_Y_LOW] + #define flatY2 flat_size[INDEX_Y_HIGH] + #define addX1 add_size[INDEX_X_LOW] + #define addX2 add_size[INDEX_X_HIGH] + #define addY1 add_size[INDEX_Y_LOW] + #define addY2 add_size[INDEX_Y_HIGH] + + // invis? skip. + if(!A || A.alpha <= 0) + return BLANK + + // detect if state exists + var/icon/icon = A.icon || deficon + var/state = A.icon_state + var/none = !icon + if(!none) + var/list/states = icon_states(icon) + if(!(state in states)) + if(!("" in states)) + none = TRUE + else + state = "" + + // determine if there's directionals + // propagate forced direcitons down if and only if A has a direction + // todo: this results in a mismatch if someone is facing east but their overlays are facing south. + var/dir + if(start || !A.dir) + dir = defdir + else + dir = A.dir + var/ourdir = dir + if(!none && ourdir != SOUTH) + if(length(icon_states(icon(icon, state, NORTH)))) + else if(length(icon_states(icon(icon, state, EAST)))) + else if(length(icon_states(icon(icon, state, WEST)))) + else + ourdir = SOUTH + + // start generating + if(!A.overlays.len && !A.underlays.len) + // we don't even have ourselves! + if(none) + return BLANK + // no overlays/underlays, we're done, just mix in ourselves + var/icon/self_icon = icon(icon(icon, state, ourdir), "", SOUTH, no_anim? 1 : null) + if(A.alpha < 255) + self_icon.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + if(A.color) + if(islist(A.color)) + self_icon.MapColors(arglist(A.color)) + else + self_icon.Blend(A.color, ICON_MULTIPLY) + return self_icon + + // safety/performance check + if((A.overlays.len + A.underlays.len) > 80) + // we use fucking insertion check + // > 80 = death. + CRASH("get_flat_icon tried to process more than 80 layers") + + // otherwise, we have to blend in all overlays/underlays. + var/icon/flat = BLANK + var/list/appearance/gathered = list() + var/appearance/copying + var/appearance/comparing + var/i + var/appearance/self + var/current_layer + + if(!none) + // add the atom itself + self = image(icon = icon, icon_state = state, layer = A.layer, dir = ourdir) + self.color = A.color + self.alpha = A.alpha + self.blend_mode = A.blend_mode + gathered[self] = A.layer + + // gather + for(copying as anything in A.overlays) + // todo: better handling + if(copying.plane != FLOAT_PLANE && copying.plane != A.plane) + // we don't care probably HUD or something lol + continue + current_layer = copying.layer + // if it's float layer, shove it right above atom. + if(current_layer < 0) + if(current_layer < -1000) + CRASH("who the hell is using -1000 or below on float layers?") + current_layer = A.layer + (1000 + current_layer) / 1000 + // else, add 1 so it doesn't potentially collide on float + else + ++current_layer + + // inject with insertion sort + for(i in 1 to gathered.len) + comparing = gathered[i] + if(current_layer < gathered[comparing]) + gathered.Insert(i, copying) + // associate + gathered[copying] = current_layer + + for(copying as anything in A.underlays) + // todo: better handling + if(copying.plane != FLOAT_PLANE && copying.plane != A.plane) + // we don't care probably HUD or something lol + continue + current_layer = copying.layer + // if it's float layer, shove it right below atom. + if(current_layer < 0) + if(current_layer < -1000) + CRASH("who the hell is using -1000 or below on float layers?") + current_layer = A.layer - (1000 + current_layer) / 1000 + // else, subtract 1 so it doesn't potentially collide on float + else + --current_layer + + // inject with insertion sort + for(i in 1 to gathered.len) + comparing = gathered[i] + if(current_layer < gathered[comparing]) + gathered.Insert(i, copying) + // associate + gathered[copying] = current_layer + + // adding icon we're mixing in + var/icon/adding + // current dimensions + var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) + // adding dimensions + var/list/add_size[4] + // blend mode + var/blend_mode + + // blend in layers + for(copying as anything in gathered) + // if invis, skip + if(copying.alpha == 0) + continue + + // detect if it's literally ourselves + if(copying == self) + // blend in normally (no sense doing otherwise unless we're on map) + // we can't assume we're on map. + blend_mode = BLEND_OVERLAY + adding = icon(icon, state, ourdir) + else + // use full get_flat_icon + blend_mode = copying.blend_mode + adding = _get_flat_icon(copying, defdir, no_anim, icon) + + // if we got nothing, skip + if(!adding) + continue + + // detect adding size, taking into account copying overlay's pixel offsets + add_size[INDEX_X_LOW] = min(flatX1, copying.pixel_x + 1) + add_size[INDEX_X_HIGH] = max(flatX2, copying.pixel_x + adding.Width()) + add_size[INDEX_Y_LOW] = min(flatY1, copying.pixel_y + 1) + add_size[INDEX_Y_HIGH] = max(flatY2, copying.pixel_y + adding.Height()) + + // resize flat to fit if necessary + if(flat_size ~! add_size) + flat.Crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + flat_size = add_size.Copy() + + // blend the overlay/underlay in + flat.Blend(adding, blendMode2iconMode(blend_mode), copying.pixel_x + 2 - flatX1, copying.pixel_y + 2 - flatY1) + + // apply colors + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) + + // apply alpha + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + + // finalize + if(no_anim) + // clean up frames + var/icon/cleaned = icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + return cleaned + else + // just return flat as SOUTH + return icon(flat, "", SOUTH) + + #undef flatX1 + #undef flatX2 + #undef flatY1 + #undef flatY2 + #undef addX1 + #undef addX2 + #undef addY1 + #undef addY2 + + #undef INDEX_X_LOW + #undef INDEX_X_HIGH + #undef INDEX_Y_LOW + #undef INDEX_Y_HIGH + + #undef BLANK diff --git a/code/_helpers/icons_procs.dm b/code/_helpers/icons_procs.dm new file mode 100644 index 0000000000..3ee9bb736a --- /dev/null +++ b/code/_helpers/icons_procs.dm @@ -0,0 +1,665 @@ +/* +IconProcs README + +A BYOND library for manipulating icons and colors + +by Lummox JR + +version 1.0 + +The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation +routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. + +CHANGING ICONS + +Several new procs have been added to the /icon datum to simplify working with icons. To use them, +remember you first need to setup an /icon var like so: + +GLOBAL_DATUM_INIT(my_icon, /icon, new('iconfile.dmi')) + +icon/ChangeOpacity(amount = 1) + A very common operation in DM is to try to make an icon more or less transparent. Making an icon more + transparent is usually much easier than making it less so, however. This proc basically is a frontend + for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() + can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. + If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. +icon/GrayScale() + Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. +icon/ColorTone(tone) + Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an + RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. + See also the global ColorTone() proc. +icon/MinColors(icon) + The icon is blended with a second icon where the minimum of each RGB pixel is the result. + Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. +icon/MaxColors(icon) + The icon is blended with a second icon where the maximum of each RGB pixel is the result. + Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. +icon/Opaque(background = "#000000") + All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. +icon/BecomeAlphaMask() + You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. + The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. +icon/AddAlphaMask(mask) + The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, + the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. + Where the mask is translucent, the current icon becomes more transparent. +icon/UseAlphaMask(mask, mode) + Sometimes you may want to take the alpha values from one icon and use them on a different icon. + This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change + so it has the same colors as before but uses the mask for opacity. + +COLOR MANAGEMENT AND HSV + +RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. + + * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to + cyan to blue to magenta and back to red. + * The saturation of a color is how much color is in it. A color with low saturation will be more gray, + and with no saturation at all it is a shade of gray. + * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, + and no value at all is black. + +Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three +hex digits because it ranges from 0 to 0x5FF. + + * 0 to 0xFF - red to yellow + * 0x100 to 0x1FF - yellow to green + * 0x200 to 0x2FF - green to cyan + * 0x300 to 0x3FF - cyan to blue + * 0x400 to 0x4FF - blue to magenta + * 0x500 to 0x5FF - magenta to red + +Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), +value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". + +More than one HSV color can match the same RGB color. + +Here are some procs you can use for color management: + +ReadRGB(rgb) + Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used + that includes alpha, the list will have a fourth item for the alpha value. +hsv(hue, sat, val, apha) + Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" + format. Alpha is not included in the result if null. +ReadHSV(rgb) + Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that + includes alpha, the list will have a fourth item for the alpha value. +RGBtoHSV(rgb) + Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". +HSVtoRGB(hsv) + Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". +BlendRGB(rgb1, rgb2, amount) + Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; + if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an RGB or RGBA color. +BlendHSV(hsv1, hsv2, amount) + Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB + blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, + the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an HSV or HSVA color. +BlendRGBasHSV(rgb1, rgb2, amount) + Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. +HueToAngle(hue) + Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. +AngleToHue(hue) + Converts an angle to a hue in the valid range. +RotateHue(hsv, angle) + Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. + (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value + as the original, but a different hue. +GrayScale(rgb) + Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. +ColorTone(rgb, tone) + Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of + using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. +*/ + +/* +Get Flat Icon DEMO by DarkCampainger + +This is a test for the get flat icon proc, modified approprietly for icons and their states. +Probably not a good idea to run this unless you want to see how the proc works in detail. +mob + icon = 'old_or_unused.dmi' + icon_state = "green" + + Login() + // Testing image underlays + underlays += image(icon='old_or_unused.dmi',icon_state="red") + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) + + // Testing image overlays + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32)) + add_overlay(image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32)) + + // Testing icon file overlays (defaults to mob's state) + add_overlay('_flat_demoIcons2.dmi') + + // Testing icon_state overlays (defaults to mob's icon) + add_overlay("white") + + // Testing dynamic icon overlays + var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") + I.Shift(NORTH,16,1) + add_overlay(I) + + // Testing dynamic image overlays + I=image(icon=I,pixel_x = -32, pixel_y = 32) + add_overlay(I) + + // Testing object types (and layers) + add_overlay(/obj/effect/overlayTest) + + loc = locate (10,10,1) + verb + Browse_Icon() + set name = "1. Browse Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Send the icon to src's local cache + src<

") + + Output_Icon() + set name = "2. Output Icon" + to_chat(src, "Icon is: [icon2base64html(get_flat_icon(src))]") + + Label_Icon() + set name = "3. Label Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Copy the file to the rsc manually + var/icon/I = fcopy_rsc(get_flat_icon(src)) + // Send the icon to src's local cache + src< 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 8) + break + + var/single = digits < 6 + if(digits != 3 && digits != 4 && digits != 6 && digits != 8) + return + if(digits == 4 || digits == 8) + usealpha = 1 + for(i=start, digits>0, ++i) + ch = text2ascii(rgb, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + r = (r << 4) | ch + if(single) + r |= r << 4 + ++which + else if(!(digits & 1)) + ++which + if(1) + g = (g << 4) | ch + if(single) + g |= g << 4 + ++which + else if(!(digits & 1)) + ++which + if(2) + b = (b << 4) | ch + if(single) + b |= b << 4 + ++which + else if(!(digits & 1)) + ++which + if(3) + alpha = (alpha << 4) | ch + if(single) + alpha |= alpha << 4 + + . = list(r, g, b) + if(usealpha) + . += alpha + +/proc/ReadHSV(hsv) + if(!hsv) + return + + // interpret the HSV or HSVA value + var/i=1,start=1 + if(text2ascii(hsv) == 35) + ++start // skip opening # + var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha + var/digits=0 + for(i=start, i<=length(hsv), ++i) + ch = text2ascii(hsv, i) + if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) + break + ++digits + if(digits == 9) + break + if(digits > 7) + usealpha = 1 + if(digits <= 4) + ++which + if(digits <= 2) + ++which + for(i=start, digits>0, ++i) + ch = text2ascii(hsv, i) + if(ch >= 48 && ch <= 57) + ch -= 48 + else if(ch >= 65 && ch <= 70) + ch -= 55 + else if(ch >= 97 && ch <= 102) + ch -= 87 + else + break + --digits + switch(which) + if(0) + hue = (hue << 4) | ch + if(digits == (usealpha ? 6 : 4)) + ++which + if(1) + sat = (sat << 4) | ch + if(digits == (usealpha ? 4 : 2)) + ++which + if(2) + val = (val << 4) | ch + if(digits == (usealpha ? 2 : 0)) + ++which + if(3) + alpha = (alpha << 4) | ch + + . = list(hue, sat, val) + if(usealpha) + . += alpha + +/proc/HSVtoRGB(hsv) + if(!hsv) + return "#000000" + var/list/HSV = ReadHSV(hsv) + if(!HSV) + return "#000000" + + var/hue = HSV[1] + var/sat = HSV[2] + var/val = HSV[3] + + // Compress hue into easier-to-manage range + hue -= hue >> 8 + if(hue >= 0x5fa) + hue -= 0x5fa + + var/hi,mid,lo,r,g,b + hi = val + lo = round((255 - sat) * val / 255, 1) + mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) + if(hue >= 765) + if(hue >= 1275) {r=hi; g=lo; b=mid} + else if(hue >= 1020) {r=mid; g=lo; b=hi } + else {r=lo; g=mid; b=hi } + else + if(hue >= 510) {r=lo; g=hi; b=mid} + else if(hue >= 255) {r=mid; g=hi; b=lo } + else {r=hi; g=mid; b=lo } + + return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) + +/proc/RGBtoHSV(rgb) + if(!rgb) + return "#0000000" + var/list/RGB = ReadRGB(rgb) + if(!RGB) + return "#0000000" + + var/r = RGB[1] + var/g = RGB[2] + var/b = RGB[3] + var/hi = max(r,g,b) + var/lo = min(r,g,b) + + var/val = hi + var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 + var/hue = 0 + + if(sat) + var/dir + var/mid + if(hi == r) + if(lo == b) {hue=0; dir=1; mid=g} + else {hue=1535; dir=-1; mid=b} + else if(hi == g) + if(lo == r) {hue=512; dir=1; mid=b} + else {hue=511; dir=-1; mid=r} + else if(hi == b) + if(lo == g) {hue=1024; dir=1; mid=r} + else {hue=1023; dir=-1; mid=g} + hue += dir * round((mid-lo) * 255 / (hi-lo), 1) + + return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) + +/proc/hsv(hue, sat, val, alpha) + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + if((hue & 0xFF) == 0xFF) + ++hue + if(hue >= 1536) + hue = 0 + if(sat < 0) + sat = 0 + if(sat > 255) + sat = 255 + if(val < 0) + val = 0 + if(val > 255) + val = 255 + . = "#" + . += TO_HEX_DIGIT(hue >> 8) + . += TO_HEX_DIGIT(hue >> 4) + . += TO_HEX_DIGIT(hue) + . += TO_HEX_DIGIT(sat >> 4) + . += TO_HEX_DIGIT(sat) + . += TO_HEX_DIGIT(val >> 4) + . += TO_HEX_DIGIT(val) + if(!isnull(alpha)) + if(alpha < 0) + alpha = 0 + if(alpha > 255) + alpha = 255 + . += TO_HEX_DIGIT(alpha >> 4) + . += TO_HEX_DIGIT(alpha) + +/* + Smooth blend between HSV colors + + amount=0 is the first color + amount=1 is the second color + amount=0.5 is directly between the two colors + + amount<0 or amount>1 are allowed + */ +/proc/BlendHSV(hsv1, hsv2, amount) + var/list/HSV1 = ReadHSV(hsv1) + var/list/HSV2 = ReadHSV(hsv2) + + // add missing alpha if needed + if(HSV1.len < HSV2.len) + HSV1 += 255 + else if(HSV2.len < HSV1.len) + HSV2 += 255 + var/usealpha = HSV1.len > 3 + + // normalize hsv values in case anything is screwy + if(HSV1[1] > 1536) + HSV1[1] %= 1536 + if(HSV2[1] > 1536) + HSV2[1] %= 1536 + if(HSV1[1] < 0) + HSV1[1] += 1536 + if(HSV2[1] < 0) + HSV2[1] += 1536 + if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} + if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} + + // no value for one color means don't change saturation + if(!HSV1[3]) + HSV1[2] = HSV2[2] + if(!HSV2[3]) + HSV2[2] = HSV1[2] + // no saturation for one color means don't change hues + if(!HSV1[2]) + HSV1[1] = HSV2[1] + if(!HSV2[2]) + HSV2[1] = HSV1[1] + + // Compress hues into easier-to-manage range + HSV1[1] -= HSV1[1] >> 8 + HSV2[1] -= HSV2[1] >> 8 + + var/hue_diff = HSV2[1] - HSV1[1] + if(hue_diff > 765) + hue_diff -= 1530 + else if(hue_diff <= -765) + hue_diff += 1530 + + var/hue = round(HSV1[1] + hue_diff * amount, 1) + var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) + var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) + var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null + + // normalize hue + if(hue < 0 || hue >= 1530) + hue %= 1530 + if(hue < 0) + hue += 1530 + // decompress hue + hue += round(hue / 255) + + return hsv(hue, sat, val, alpha) + +/proc/BlendRGBasHSV(rgb1, rgb2, amount) + return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) + +/proc/HueToAngle(hue) + // normalize hsv in case anything is screwy + if(hue < 0 || hue >= 1536) + hue %= 1536 + if(hue < 0) + hue += 1536 + // Compress hue into easier-to-manage range + hue -= hue >> 8 + return hue / (1530/360) + +/proc/AngleToHue(angle) + // normalize hsv in case anything is screwy + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + var/hue = angle * (1530/360) + // Decompress hue + hue += round(hue / 255) + return hue + + +// positive angle rotates forward through red->green->blue +/proc/RotateHue(hsv, angle) + var/list/HSV = ReadHSV(hsv) + + // normalize hsv in case anything is screwy + if(HSV[1] >= 1536) + HSV[1] %= 1536 + if(HSV[1] < 0) + HSV[1] += 1536 + + // Compress hue into easier-to-manage range + HSV[1] -= HSV[1] >> 8 + + if(angle < 0 || angle >= 360) + angle -= 360 * round(angle / 360) + HSV[1] = round(HSV[1] + angle * (1530/360), 1) + + // normalize hue + if(HSV[1] < 0 || HSV[1] >= 1530) + HSV[1] %= 1530 + if(HSV[1] < 0) + HSV[1] += 1530 + // decompress hue + HSV[1] += round(HSV[1] / 255) + + return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) + +// Convert an rgb color to grayscale +/proc/GrayScale(rgb) + var/list/RGB = ReadRGB(rgb) + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) + +// Change grayscale color to black->tone->white range +/proc/ColorTone(rgb, tone) + var/list/RGB = ReadRGB(rgb) + var/list/TONE = ReadRGB(tone) + + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 + + if(gray <= tone_gray) + return BlendRGB("#000000", tone, gray/(tone_gray || 1)) + else + return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) + + +//Used in the OLD chem colour mixing algorithm +/proc/GetColors(hex) + hex = uppertext(hex) + // No alpha set? Default to full alpha. + if(length(hex) == 7) + hex += "FF" + var/hi1 = text2ascii(hex, 2) // R + var/lo1 = text2ascii(hex, 3) // R + var/hi2 = text2ascii(hex, 4) // G + var/lo2 = text2ascii(hex, 5) // G + var/hi3 = text2ascii(hex, 6) // B + var/lo3 = text2ascii(hex, 7) // B + var/hi4 = text2ascii(hex, 8) // A + var/lo4 = text2ascii(hex, 9) // A + return list(((hi1>= 65 ? hi1-55 : hi1-48)<<4) | (lo1 >= 65 ? lo1-55 : lo1-48), + ((hi2 >= 65 ? hi2-55 : hi2-48)<<4) | (lo2 >= 65 ? lo2-55 : lo2-48), + ((hi3 >= 65 ? hi3-55 : hi3-48)<<4) | (lo3 >= 65 ? lo3-55 : lo3-48), + ((hi4 >= 65 ? hi4-55 : hi4-48)<<4) | (lo4 >= 65 ? lo4-55 : lo4-48)) + +//Interface for using DrawBox() to draw 1 pixel on a coordinate. +//Returns the same icon specifed in the argument, but with the pixel drawn +/proc/DrawPixel(icon/I,colour,drawX,drawY) + if(!I) + return 0 + + var/Iwidth = I.Width() + var/Iheight = I.Height() + + if(drawX > Iwidth || drawX <= 0) + return 0 + if(drawY > Iheight || drawY <= 0) + return 0 + + I.DrawBox(colour,drawX, drawY) + return I + + +//Interface for easy drawing of one pixel on an atom. +/atom/proc/DrawPixelOn(colour, drawX, drawY) + var/icon/I = new(icon) + var/icon/J = DrawPixel(I, colour, drawX, drawY) + if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. + icon = J + return J + return 0 + +//Hook, override to run code on- wait this is images +//Images have dir without being an atom, so they get their own definition. +//Lame. +/image/proc/setDir(newdir) + dir = newdir + +/* Gives the result RGB of a RGB string after a matrix transformation. No alpha. + * Input: rr, rg, rb, gr, gg, gb, br, bg, bb, cr, cg, cb + * Output: RGB string + */ +/proc/RGBMatrixTransform(list/color, list/cm) + ASSERT(cm.len >= 9) + if(cm.len < 12) // fill in the rest + for(var/i in 1 to (12 - cm.len)) + cm += 0 + if(!islist(color)) + color = ReadRGB(color) + color[1] = color[1] * cm[1] + color[2] * cm[2] + color[3] * cm[3] + cm[10] * 255 + color[2] = color[1] * cm[4] + color[2] * cm[5] + color[3] * cm[6] + cm[11] * 255 + color[3] = color[1] * cm[7] + color[2] * cm[8] + color[3] * cm[9] + cm[12] * 255 + return rgb(color[1], color[2], color[3]) diff --git a/code/_helpers/sorts/__main.dm b/code/_helpers/sorts/__main.dm index 622d88f147..7fd0a505c7 100644 --- a/code/_helpers/sorts/__main.dm +++ b/code/_helpers/sorts/__main.dm @@ -102,7 +102,8 @@ start the index of the first element in the range that is not already known to b if(start <= lo) start = lo + 1 - for(,start < hi, ++start) + var/list/L = src.L + for(start in start to hi - 1) var/pivot = fetchElement(L,start) //set left and right to the index where pivot belongs @@ -626,20 +627,17 @@ reverse a descending sequence without violating stability. var/val2 = fetchElement(L,cursor2) while(1) - if(call(cmp)(val1,val2) < 0) + if(call(cmp)(val1,val2) <= 0) if(++cursor1 >= end1) break val1 = fetchElement(L,cursor1) else moveElement(L,cursor2,cursor1) - ++cursor2 if(++cursor2 >= end2) break ++end1 ++cursor1 - //if(++cursor1 >= end1) - // break val2 = fetchElement(L,cursor2) diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm index 8bd45bbfc7..1479a07d35 100644 --- a/code/_helpers/type2type.dm +++ b/code/_helpers/type2type.dm @@ -183,6 +183,88 @@ var/hex_to_work_on = copytext(hex,5,7) return hex2num(hex_to_work_on) +/** + * Convert HSL to RGB + */ +/proc/hsl2rgb(hue, saturation, lightness) + var/red + var/green + var/blue + + if(saturation == 0) + red = lightness * 255 + green = red + blue = red + else + var/a;var/b; + if(lightness < 0.5) + b = lightness * (1 + saturation) + else + b = (lightness + saturation) - (saturation * lightness) + a = 2 * lightness - b + + red = round(255 * hue2rgb(a, b, hue + (1/3)), 1) + green = round(255 * hue2rgb(a, b, hue), 1) + blue = round(255 * hue2rgb(a, b, hue - (1/3)), 1) + + return list(red, green, blue) + +/** + * Convert RBG to HSL + */ +/proc/rgb2hsl(red, green, blue) + red /= 255 + green /= 255 + blue /= 255 + + var/max = max(red, green, blue) + var/min = min(red, green, blue) + var/range = max - min + + var/hue = 0 + var/saturation = 0 + var/lightness = 0 + + lightness = (max + min) / 2 + if(range != 0) + if(lightness < 0.5) + saturation = range / (max + min) + else + saturation = range / (2 - max - min) + + var/dred = ((max - red) / (6 * max)) + 0.5 + var/dgreen = ((max - green) / (6 * max)) + 0.5 + var/dblue = ((max - blue) / (6 * max)) + 0.5 + + if(max == red) + hue = dblue - dgreen + else if(max == green) + hue = dred - dblue + (1 / 3) + else + hue = dgreen - dred + (2 / 3) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + + return list(hue, saturation, lightness) + +/** + * Convert hue to RGB + */ +/proc/hue2rgb(a, b, hue) + if(hue < 0) + hue++ + else if(hue > 1) + hue-- + if(6*hue < 1) + return (a + (b - a) * 6 * hue) + if(2*hue < 1) + return b + if(3*hue < 2) + return (a + (b - a) * ((2 / 3) - hue) * 6) + return a + // heat2color functions. Adapted from: http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/ /proc/heat2color(temp) return rgb(heat2color_r(temp), heat2color_g(temp), heat2color_b(temp)) @@ -211,6 +293,53 @@ else . = max(0, min(255, 138.5177312231 * log(temp - 10) - 305.0447927307)) +/** + * Assumes format #RRGGBB #rrggbb + */ +/proc/color_hex2num(A) + if(!A || length(A) != length_char(A)) + return 0 + var/R = hex2num(copytext(A, 2, 4)) + var/G = hex2num(copytext(A, 4, 6)) + var/B = hex2num(copytext(A, 6, 8)) + return R+G+B + +/** + *! Word of warning: + * Using a matrix like this as a color value will simplify it back to a string after being set. + */ +/proc/color_hex2color_matrix(string) + var/length = length(string) + if((length != 7 && length != 9) || length != length_char(string)) + return color_matrix_identity() + var/r = hex2num(copytext(string, 2, 4)) / 255 + var/g = hex2num(copytext(string, 4, 6)) / 255 + var/b = hex2num(copytext(string, 6, 8)) / 255 + var/a = 1 + if(length == 9) + a = hex2num(copytext(string, 8, 10)) / 255 + if(!isnum(r) || !isnum(g) || !isnum(b) || !isnum(a)) + return color_matrix_identity() + return list( + r,0,0,0,0, + g,0,0,0,0, + b,0,0,0,0, + a,0,0,0,0, + ) + +/** + * Will drop all values not on the diagonal. + */ +/proc/color_matrix2color_hex(list/the_matrix) + if(!istype(the_matrix) || the_matrix.len != 20) + return "#ffffffff" + return rgb( + the_matrix[1] * 255, // R + the_matrix[6] * 255, // G + the_matrix[11] * 255, // B + the_matrix[16] * 255, // A + ) + // Very ugly, BYOND doesn't support unix time and rounding errors make it really hard to convert it to BYOND time. // returns "YYYY-MM-DD" by default /proc/unix2date(timestamp, seperator = "-") diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index dea91c93de..de8b45dd37 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -305,6 +305,10 @@ var/list/gamemode_cache = list() var/static/invoke_youtubedl = null + //Enables/Disables the appropriate mob type from obtaining the verb on spawn. Still allows admins to manually give it to them. + var/static/allow_robot_recolor = FALSE + var/static/allow_simple_mob_recolor = FALSE + /datum/configuration/New() var/list/L = subtypesof(/datum/game_mode) for (var/T in L) @@ -1051,6 +1055,10 @@ var/list/gamemode_cache = list() if("loadout_whitelist") config.loadout_whitelist = text2num(value) + if("allow_robot_recolor") + config.allow_robot_recolor = 1 + if("allow_simple_mob_recolor") + config.allow_simple_mob_recolor = 1 else log_misc("Unknown setting in configuration: '[name]'") diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index c1fe92295d..400a61b0f9 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -26,6 +26,7 @@ SUBSYSTEM_DEF(garbage) var/list/queues #ifdef REFERENCE_TRACKING var/list/reference_find_on_fail = list() + var/find_reference_on_fail_global_toggle = FALSE #endif diff --git a/code/controllers/subsystems/machines.dm b/code/controllers/subsystems/machines.dm index 100811f5c0..a4a836f200 100644 --- a/code/controllers/subsystems/machines.dm +++ b/code/controllers/subsystems/machines.dm @@ -23,10 +23,17 @@ SUBSYSTEM_DEF(machines) var/list/current_run = list() + var/list/all_machines = list() + + var/list/networks = list() + var/list/processing_machines = list() + var/list/powernets = list() + var/list/powerobjs = list() + /datum/controller/subsystem/machines/Initialize(timeofday) makepowernets() admin_notice("Initializing atmos machinery.", R_DEBUG) - setup_atmos_machinery(global.machines) + setup_atmos_machinery(all_machines) fire() ..() @@ -83,24 +90,24 @@ SUBSYSTEM_DEF(machines) msg += "PN:[round(cost_powernets,1)]|" msg += "PO:[round(cost_power_objects,1)]" msg += "} " - msg += "PI:[global.pipe_networks.len]|" - msg += "MC:[global.processing_machines.len]|" - msg += "PN:[global.powernets.len]|" - msg += "PO:[global.processing_power_items.len]|" - msg += "MC/MS:[round((cost ? global.processing_machines.len/cost_machinery : 0),0.1)]" + msg += "PI:[SSmachines.networks.len]|" + msg += "MC:[SSmachines.processing_machines.len]|" + msg += "PN:[SSmachines.powernets.len]|" + msg += "PO:[SSmachines.powerobjs.len]|" + msg += "MC/MS:[round((cost ? SSmachines.processing_machines.len/cost_machinery : 0),0.1)]" ..(jointext(msg, null)) /datum/controller/subsystem/machines/proc/process_pipenets(resumed = 0) if (!resumed) - src.current_run = global.pipe_networks.Copy() + src.current_run = networks.Copy() //cache for sanic speed (lists are references anyways) var/wait = src.wait var/list/current_run = src.current_run while(current_run.len) var/datum/pipe_network/PN = current_run[current_run.len] current_run.len-- - if(QDELETED(PN)) - global.pipe_networks.Remove(PN) + if(!PN) + networks.Remove(PN) DISABLE_BITFIELD(PN?.datum_flags, DF_ISPROCESSING) else PN.process(wait) @@ -109,30 +116,30 @@ SUBSYSTEM_DEF(machines) /datum/controller/subsystem/machines/proc/process_machinery(resumed = 0) if (!resumed) - src.current_run = global.processing_machines.Copy() + src.current_run = processing_machines.Copy() var/wait = src.wait var/list/current_run = src.current_run while(current_run.len) var/obj/machinery/M = current_run[current_run.len] current_run.len-- - if(QDELETED(M) || (M.process(wait) == PROCESS_KILL)) - global.processing_machines.Remove(M) + if(!M || (M.process(wait) == PROCESS_KILL)) + processing_machines.Remove(M) DISABLE_BITFIELD(M?.datum_flags, DF_ISPROCESSING) if(MC_TICK_CHECK) return /datum/controller/subsystem/machines/proc/process_powernets(resumed = 0) if (!resumed) - src.current_run = global.powernets.Copy() + src.current_run = powernets.Copy() var/wait = src.wait var/list/current_run = src.current_run while(current_run.len) var/datum/powernet/PN = current_run[current_run.len] current_run.len-- - if(QDELETED(PN)) - global.powernets.Remove(PN) + if(!PN) + powernets.Remove(PN) DISABLE_BITFIELD(PN?.datum_flags, DF_ISPROCESSING) else PN.reset(wait) @@ -143,36 +150,42 @@ SUBSYSTEM_DEF(machines) // Currently only used by powersinks. These items get priority processed before machinery /datum/controller/subsystem/machines/proc/process_power_objects(resumed = 0) if (!resumed) - src.current_run = global.processing_power_items.Copy() + src.current_run = powerobjs.Copy() var/wait = src.wait var/list/current_run = src.current_run while(current_run.len) var/obj/item/I = current_run[current_run.len] current_run.len-- - if(QDELETED(I) || (I.pwr_drain(wait) == PROCESS_KILL)) - global.processing_power_items.Remove(I) + if(!I || (I.pwr_drain(wait) == PROCESS_KILL)) + powerobjs.Remove(I) DISABLE_BITFIELD(I?.datum_flags, DF_ISPROCESSING) if(MC_TICK_CHECK) return /datum/controller/subsystem/machines/Recover() - for(var/datum/D as anything in global.pipe_networks) + for(var/datum/D as anything in SSmachines.networks) if(!istype(D, /datum/pipe_network)) - error("Found wrong type during SSmachinery recovery: list=global.pipe_networks, item=[D], type=[D?.type]") - global.pipe_networks -= D - for(var/datum/D as anything in global.processing_machines) + error("Found wrong type during SSmachinery recovery: list=SSmachines.networks, item=[D], type=[D?.type]") + SSmachines.networks -= D + for(var/datum/D as anything in SSmachines.processing_machines) if(!istype(D, /obj/machinery)) - error("Found wrong type during SSmachinery recovery: list=global.processing_machines, item=[D], type=[D?.type]") - global.processing_machines -= D - for(var/datum/D as anything in global.powernets) + error("Found wrong type during SSmachinery recovery: list=SSmachines.machines, item=[D], type=[D?.type]") + SSmachines.processing_machines -= D + for(var/datum/D as anything in SSmachines.powernets) if(!istype(D, /datum/powernet)) - error("Found wrong type during SSmachinery recovery: list=global.powernets, item=[D], type=[D?.type]") - global.powernets -= D - for(var/datum/D as anything in global.processing_power_items) + error("Found wrong type during SSmachinery recovery: list=SSmachines.powernets, item=[D], type=[D?.type]") + SSmachines.powernets -= D + for(var/datum/D as anything in SSmachines.powerobjs) if(!istype(D, /obj/item)) - error("Found wrong type during SSmachinery recovery: list=global.processing_power_items, item=[D], type=[D?.type]") - global.processing_power_items -= D + error("Found wrong type during SSmachinery recovery: list=SSmachines.powerobjs, item=[D], type=[D?.type]") + SSmachines.powerobjs -= D + + all_machines = SSmachines.all_machines + networks = SSmachines.networks + processing_machines = SSmachines.processing_machines + powernets = SSmachines.powernets + powerobjs = SSmachines.powerobjs #undef SSMACHINES_PIPENETS #undef SSMACHINES_MACHINERY diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm index 7b41519737..0b76d7dea1 100644 --- a/code/controllers/subsystems/ping.dm +++ b/code/controllers/subsystems/ping.dm @@ -27,7 +27,7 @@ SUBSYSTEM_DEF(ping) var/client/client = currentrun[currentrun.len] currentrun.len-- - if(!client.is_preference_enabled(/datum/client_preference/vchat_enable)) + if(!client?.is_preference_enabled(/datum/client_preference/vchat_enable)) winset(client, "output", "on-show=&is-disabled=0&is-visible=1") winset(client, "browseroutput", "is-disabled=1;is-visible=0") client.tgui_panel.oldchat = TRUE diff --git a/code/controllers/subsystems/tgui.dm b/code/controllers/subsystems/tgui.dm index 578e3c486a..46b3ae90c2 100644 --- a/code/controllers/subsystems/tgui.dm +++ b/code/controllers/subsystems/tgui.dm @@ -266,13 +266,13 @@ SUBSYSTEM_DEF(tgui) * * return int The number of UIs closed. */ -/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object) +/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object, logout = FALSE) var/count = 0 if(length(user?.tgui_open_uis) == 0) return count for(var/datum/tgui/ui in user.tgui_open_uis) if(isnull(src_object) || ui.src_object == src_object) - ui.close() + ui.close(logout = logout) count++ return count @@ -318,7 +318,7 @@ SUBSYSTEM_DEF(tgui) * return int The number of UIs closed. */ /datum/controller/subsystem/tgui/proc/on_logout(mob/user) - close_user_uis(user) + close_user_uis(user, logout = TRUE) /** * private diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 62aa9aab86..e38b78ab4d 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -380,3 +380,88 @@ . |= GLOB.bitfields[bitfield][flag] else return + +/datum/browser/modal/color_matrix_picker + var/color_matrix + +/datum/browser/modal/color_matrix_picker/New(mob/user, message, title, button1 = "Ok", button2, button3, stealfocus = TRUE, timeout = 0, list/values) + if(!user) + return + if(!values) + values = list(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) + if(values.len < 12) + values.len = 12 + var/list/output = list() + output += "
" + output += "[message]" +#define MATRIX_FIELD(field, default) " " + output += "

" + output += MATRIX_FIELD("rr", values[1]) + output += MATRIX_FIELD("gr", values[4]) + output += MATRIX_FIELD("br", values[7]) + output += "

" + output += MATRIX_FIELD("rg", values[2]) + output += MATRIX_FIELD("gg", values[5]) + output += MATRIX_FIELD("bg", values[8]) + output += "

" + output += MATRIX_FIELD("rb", values[3]) + output += MATRIX_FIELD("gb", values[6]) + output += MATRIX_FIELD("bb", values[9]) + output += "

" + output += MATRIX_FIELD("cr", values[10]) + output += MATRIX_FIELD("cg", values[11]) + output += MATRIX_FIELD("cb", values[12]) + output += "

" +#undef MATRIX_FIELD + + output += {"
+ "} + + if (button2) + output += {""} + + if (button3) + output += {""} + output += {"
"} + + ..(user, ckey("[user]-[message]-[title]-[world.time]-[rand(1,10000)]"), title, 800, 400, src, stealfocus, timeout) + set_content(output.Join("")) + +/datum/browser/modal/color_matrix_picker/Topic(href, list/href_list) + if(href_list["close"] || !user) + opentime = 0 + return + if(href_list["button"]) + var/button = text2num(href_list["button"]) + if(ISINRANGE(button, 1, 3)) + selectedbutton = button + var/list/cm = rgb_construct_color_matrix( + text2num(href_list["rr"]), + text2num(href_list["rg"]), + text2num(href_list["rb"]), + text2num(href_list["gr"]), + text2num(href_list["gg"]), + text2num(href_list["gb"]), + text2num(href_list["br"]), + text2num(href_list["bg"]), + text2num(href_list["bb"]), + text2num(href_list["cr"]), + text2num(href_list["cg"]), + text2num(href_list["cb"]) + ) + if(cm) + color_matrix = cm + opentime = 0 + close() + +/proc/color_matrix_picker(mob/user, message, title, button1 = "Ok", button2, button3, stealfocus, timeout = 10 MINUTES, list/values) + if(!istype(user)) + if(istype(user, /client)) + var/client/C = user + user = C.mob + else + return + var/datum/browser/modal/color_matrix_picker/B = new(user, message, title, button1, button2, button3, stealfocus, timeout, values) + B.open() + B.wait() + return list("button" = B.selectedbutton, "matrix" = B.color_matrix) diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index 4dd8d42537..d5540bd790 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -51,6 +51,9 @@ var/list/runechat_image_cache = list() /// If we are currently processing animation and cleanup at EOL var/ending_life + /// deletion timer + var/timer_delete + /** * Constructs a chat message overlay * @@ -72,12 +75,14 @@ var/list/runechat_image_cache = list() generate_image(text, target, owner, extra_classes, lifespan) /datum/chatmessage/Destroy() - if(owned_by) + if(timer_delete) + deltimer(timer_delete) + timer_delete = null + if(istype(owned_by, /client)) // hopefully the PARENT_QDELETING on client should beat this if it's a disconnect UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) - LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) + if(owned_by.seen_messages) + LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src) owned_by.images.Remove(message) - if(message_loc) - UnregisterSignal(message_loc, COMSIG_PARENT_QDELETING) owned_by = null message_loc = null message = null @@ -102,7 +107,7 @@ var/list/runechat_image_cache = list() // Register client who owns this message owned_by = owner.client - RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(qdel_self)) + RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(unregister_qdel_self)) // this should only call owned_by if the client is destroyed var/extra_length = owned_by.is_preference_enabled(/datum/client_preference/runechat_long_messages) var/maxlen = extra_length ? CHAT_MESSAGE_EXT_LENGTH : CHAT_MESSAGE_LENGTH @@ -211,6 +216,10 @@ var/list/runechat_image_cache = list() spawn(lifespan - CHAT_MESSAGE_EOL_FADE) end_of_life() +/datum/chatmessage/proc/unregister_qdel_self() // this should only call owned_by if the client is destroyed + UnregisterSignal(owned_by, COMSIG_PARENT_QDELETING) + owned_by = null + qdel_self() /** * Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion */ @@ -219,8 +228,7 @@ var/list/runechat_image_cache = list() return ending_life = TRUE animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL) - spawn(fadetime) - qdel(src) + timer_delete = QDEL_IN(src, fadetime) /** * Creates a message overlay at a defined location for a given speaker diff --git a/code/datums/colormate.dm b/code/datums/colormate.dm new file mode 100644 index 0000000000..2149191543 --- /dev/null +++ b/code/datums/colormate.dm @@ -0,0 +1,213 @@ +/datum/ColorMate + var/name = "colouring" + var/atom/movable/inserted + var/activecolor = "#FFFFFF" + var/list/color_matrix_last + var/active_mode = COLORMATE_HSV + + var/build_hue = 0 + var/build_sat = 1 + var/build_val = 1 + + /// Minimum lightness for normal mode + var/minimum_normal_lightness = 50 + /// Minimum lightness for matrix mode, tested using 4 test colors of full red, green, blue, white. + var/minimum_matrix_lightness = 75 + /// Minimum matrix tests that must pass for something to be considered a valid color (see above) + var/minimum_matrix_tests = 2 + /// Temporary messages + var/temp + +/datum/ColorMate/New(mob/user) + color_matrix_last = list( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + 0, 0, 0, + ) + if(istype(user)) + inserted = user + . = ..() + +/datum/ColorMate/Destroy() + inserted = null + . = ..() + +/datum/ColorMate/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ColorMate", src.name) + ui.set_autoupdate(FALSE) //This might be a bit intensive, better to not update it every few ticks + ui.open() + +/datum/ColorMate/tgui_state(mob/user) + return GLOB.tgui_conscious_state + +/datum/ColorMate/tgui_data() + . = list() + .["activemode"] = active_mode + .["matrixcolors"] = list( + "rr" = color_matrix_last[1], + "rg" = color_matrix_last[2], + "rb" = color_matrix_last[3], + "gr" = color_matrix_last[4], + "gg" = color_matrix_last[5], + "gb" = color_matrix_last[6], + "br" = color_matrix_last[7], + "bg" = color_matrix_last[8], + "bb" = color_matrix_last[9], + "cr" = color_matrix_last[10], + "cg" = color_matrix_last[11], + "cb" = color_matrix_last[12], + ) + .["buildhue"] = build_hue + .["buildsat"] = build_sat + .["buildval"] = build_val + if(temp) + .["temp"] = temp + if(inserted) + .["item"] = list() + .["item"]["name"] = inserted.name + .["item"]["sprite"] = icon2base64(get_flat_icon(inserted,dir=SOUTH,no_anim=TRUE)) + .["item"]["preview"] = icon2base64(build_preview()) + else + .["item"] = null + +/datum/ColorMate/tgui_act(action, params) + . = ..() + if(.) + return + if(inserted) + switch(action) + if("switch_modes") + active_mode = text2num(params["mode"]) + return TRUE + if("choose_color") + var/chosen_color = input(inserted, "Choose a color: ", "ColorMate colour picking", activecolor) as color|null + if(chosen_color) + activecolor = chosen_color + return TRUE + if("paint") + do_paint(inserted) + temp = "Painted Successfully!" + if(istype(inserted, /mob/living/simple_mob)) + var/mob/living/simple_mob/M = inserted + M.has_recoloured = TRUE + if(istype(inserted, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/R = inserted + R.has_recoloured = TRUE + Destroy() + if("drop") + temp = "" + Destroy() + if("clear") + inserted.remove_atom_colour(FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Cleared Successfully!" + return TRUE + if("set_matrix_color") + color_matrix_last[params["color"]] = params["value"] + return TRUE + if("set_hue") + build_hue = clamp(text2num(params["buildhue"]), 0, 360) + return TRUE + if("set_sat") + build_sat = clamp(text2num(params["buildsat"]), -10, 10) + return TRUE + if("set_val") + build_val = clamp(text2num(params["buildval"]), -10, 10) + return TRUE + +/datum/ColorMate/proc/do_paint(mob/user) + var/color_to_use + switch(active_mode) + if(COLORMATE_TINT) + color_to_use = activecolor + if(COLORMATE_MATRIX) + color_to_use = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(COLORMATE_HSV) + color_to_use = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = color_to_use + if(!color_to_use || !check_valid_color(color_to_use, user)) + to_chat(user, SPAN_NOTICE("Invalid color.")) + return FALSE + inserted.add_atom_colour(color_to_use, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + return TRUE + +/// Produces the preview image of the item, used in the UI, the way the color is not stacking is a sin. +/datum/ColorMate/proc/build_preview() + if(inserted) //sanity + var/list/cm + switch(active_mode) + if(COLORMATE_MATRIX) + cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_TINT) + if(!check_valid_color(activecolor, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_HSV) + cm = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = cm + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + var/cur_color = inserted.color + inserted.color = null + inserted.color = (active_mode == COLORMATE_TINT ? activecolor : cm) + var/icon/preview = get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + inserted.color = cur_color + temp = "" + + . = preview + +/datum/ColorMate/proc/check_valid_color(list/cm, mob/user) + if(!islist(cm)) // normal + var/list/HSV = ReadHSV(RGBtoHSV(cm)) + if(HSV[3] < minimum_normal_lightness) + temp = "[cm] is too dark (Minimum lightness: [minimum_normal_lightness])" + return FALSE + return TRUE + else // matrix + // We test using full red, green, blue, and white + // A predefined number of them must pass to be considered valid + var/passed = 0 +#define COLORTEST(thestring, thematrix) passed += (ReadHSV(RGBtoHSV(RGBMatrixTransform(thestring, thematrix)))[3] >= minimum_matrix_lightness) + COLORTEST("FF0000", cm) + COLORTEST("00FF00", cm) + COLORTEST("0000FF", cm) + COLORTEST("FFFFFF", cm) +#undef COLORTEST + if(passed < minimum_matrix_tests) + temp = "Matrix is too dark. (passed [passed] out of [minimum_matrix_tests] required tests. Minimum lightness: [minimum_matrix_lightness])." + return FALSE + return TRUE diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 2e177c8ff3..0aa3c9889f 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -297,12 +297,12 @@ */ /datum/component/proc/_GetInverseTypeList(our_type = type) //we can do this one simple trick + . = list(our_type) var/current_type = parent_type - . = list(our_type, current_type) //and since most components are root level + 1, this won't even have to run while (current_type != /datum/component) - current_type = type2parent(current_type) . += current_type + current_type = type2parent(current_type) /** * Internal proc to handle most all of the signaling procedure diff --git a/code/datums/datum.dm b/code/datums/datum.dm index af085fbc14..a9bd003fa7 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -56,6 +56,7 @@ #ifdef REFERENCE_TRACKING var/tmp/running_find_references var/tmp/last_find_references = 0 + var/tmp/find_references_on_destroy = FALSE //set this to true on an item to have it find refs after #ifdef REFERENCE_TRACKING_DEBUG ///Stores info about where refs are found, used for sanity checks and testing var/list/found_refs @@ -113,6 +114,14 @@ tag = null SStgui.close_uis(src) + + #ifdef REFERENCE_TRACKING + if(find_references_on_destroy) + return QDEL_HINT_FINDREFERENCE + if(SSgarbage.find_reference_on_fail_global_toggle) + return QDEL_HINT_IFFAIL_FINDREFERENCE + #endif + return QDEL_HINT_QUEUE /** diff --git a/code/datums/interfaces/appearance.dm b/code/datums/interfaces/appearance.dm new file mode 100644 index 0000000000..7bcb43d37d --- /dev/null +++ b/code/datums/interfaces/appearance.dm @@ -0,0 +1,143 @@ +/** + * hey, remember mutable appearance? + * + * only: + * - this isn't a real object rather than a struct + * - i'm making a cast for it so we can VV it + * - this is also used to cast procs that operate on appearance-like things. + * + * Sue me, I need to debug things somehow + * + * DO NOT USE THESE UNLESS YOU KNOW WHAT YOU ARE DOING. + */ +/appearance + var/alpha + var/appearance_flags + var/blend_mode + var/color + var/desc + var/dir + var/gender + var/icon + var/icon_state + var/invisibility + var/infra_luminosity + var/list/filters + var/layer + var/luminosity + var/maptext + var/maptext_width + var/maptext_height + var/maptext_x + var/maptext_y + var/mouse_over_pointer + var/mouse_drag_pointer + var/mouse_drop_pointer + var/mouse_drop_zone + var/mouse_opacity + var/name + var/opacity + var/list/overlays + var/override + var/pixel_x + var/pixel_y + var/pixel_w + var/pixel_z + var/plane + var/render_source + var/render_target + var/suffix + var/text + var/transform + var/list/underlays + // var/vis_flags + +//! vis_flags missing even though byond ref says it's there, fuck off why is this possible + +GLOBAL_REAL_VAR(_appearance_var_list) = list( + "alpha", + "appearance_flags", + "blend_mode", + "color", + "desc", + "dir", + "gender", + "icon", + "icon_state", + "invisibility", + "infra_luminosity", + "filters", + "layer", + "luminosity", + "maptext", + "maptext_width", + "maptext_height", + "maptext_x", + "maptext_y", + "mouse_over_pointer", + "mouse_drag_pointer", + "mouse_drop_pointer", + "mouse_drop_zone", + "mouse_opacity", + "name", + "opacity", + "overlays", + "override", + "pixel_x", + "pixel_y", + "pixel_w", + "pixel_z", + "plane", + "render_source", + "render_target", + "suffix", + "text", + "transform", + "underlays" + // "vis_flags" +) + +/proc/__appearance_v_debug(appearance/A, name) + switch(name) +#define DEBUG_APPEARANCE_VAR(n) if(#n) return debug_variable(name, A.n, 0, null) + DEBUG_APPEARANCE_VAR(alpha) + DEBUG_APPEARANCE_VAR(appearance_flags) + DEBUG_APPEARANCE_VAR(blend_mode) + DEBUG_APPEARANCE_VAR(color) + DEBUG_APPEARANCE_VAR(desc) + DEBUG_APPEARANCE_VAR(dir) + DEBUG_APPEARANCE_VAR(gender) + DEBUG_APPEARANCE_VAR(icon) + DEBUG_APPEARANCE_VAR(icon_state) + DEBUG_APPEARANCE_VAR(invisibility) + DEBUG_APPEARANCE_VAR(infra_luminosity) + DEBUG_APPEARANCE_VAR(filters) + DEBUG_APPEARANCE_VAR(layer) + DEBUG_APPEARANCE_VAR(luminosity) + DEBUG_APPEARANCE_VAR(maptext) + DEBUG_APPEARANCE_VAR(maptext_width) + DEBUG_APPEARANCE_VAR(maptext_height) + DEBUG_APPEARANCE_VAR(maptext_x) + DEBUG_APPEARANCE_VAR(maptext_y) + DEBUG_APPEARANCE_VAR(mouse_over_pointer) + DEBUG_APPEARANCE_VAR(mouse_drag_pointer) + DEBUG_APPEARANCE_VAR(mouse_drop_pointer) + DEBUG_APPEARANCE_VAR(mouse_drop_zone) + DEBUG_APPEARANCE_VAR(mouse_opacity) + DEBUG_APPEARANCE_VAR(name) + DEBUG_APPEARANCE_VAR(opacity) + DEBUG_APPEARANCE_VAR(overlays) + DEBUG_APPEARANCE_VAR(override) + DEBUG_APPEARANCE_VAR(pixel_x) + DEBUG_APPEARANCE_VAR(pixel_y) + DEBUG_APPEARANCE_VAR(pixel_w) + DEBUG_APPEARANCE_VAR(pixel_z) + DEBUG_APPEARANCE_VAR(plane) + DEBUG_APPEARANCE_VAR(render_source) + DEBUG_APPEARANCE_VAR(render_target) + DEBUG_APPEARANCE_VAR(suffix) + DEBUG_APPEARANCE_VAR(text) + DEBUG_APPEARANCE_VAR(transform) + DEBUG_APPEARANCE_VAR(underlays) + // DEBUG_APPEARANCE_VAR(vis_flags) +#undef DEBUG_APPEARANCE_VAR diff --git a/code/datums/outfits/jobs/civilian_vr.dm b/code/datums/outfits/jobs/civilian_vr.dm index c4d0cb37d4..55a6611484 100644 --- a/code/datums/outfits/jobs/civilian_vr.dm +++ b/code/datums/outfits/jobs/civilian_vr.dm @@ -22,3 +22,5 @@ /decl/hierarchy/outfit/job/assistant/entrepreneur id_type = /obj/item/weapon/card/id/civilian/entrepreneur + l_hand = /obj/item/device/ticket_printer/train + diff --git a/code/datums/reference_tracking.dm b/code/datums/reference_tracking.dm index 3154f64740..0f317b026c 100644 --- a/code/datums/reference_tracking.dm +++ b/code/datums/reference_tracking.dm @@ -1,6 +1,6 @@ #ifdef REFERENCE_TRACKING -/datum/proc/find_references(skip_alert) +/datum/proc/find_references(skip_alert = TRUE) running_find_references = type if(usr?.client) if(usr.client.running_find_references) @@ -28,7 +28,11 @@ //Time to search the whole game for our ref DoSearchVar(GLOB, "GLOB") //globals - log_reftracker("Finished searching globals") + log_reftracker("Finished searching GLOB") + + for(var/thing in global.vars) + DoSearchVar(thing, "Global variable -> [nameof(thing)]", search_time = starting_time) + log_reftracker("Finished searching global.vars") for(var/datum/thing in world) //atoms (don't beleive its lies) DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time) diff --git a/code/datums/supplypacks/recreation_vr.dm b/code/datums/supplypacks/recreation_vr.dm index 8eb64c7c29..d59fdabb15 100644 --- a/code/datums/supplypacks/recreation_vr.dm +++ b/code/datums/supplypacks/recreation_vr.dm @@ -106,3 +106,68 @@ cost = 25 containertype = /obj/structure/closet/crate containername = "Snack planets crate" + +/datum/supply_pack/recreation/pinkpillows + name = "Pink Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/tealpillows + name = "Teal Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/teal = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/whitepillows + name = "White Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/white = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/blackpillows + name = "Black Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/black = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/redpillows + name = "Red Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/red = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/greenpillows + name = "Green Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/green = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/orangepillows + name = "Orange Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/orange = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + +/datum/supply_pack/recreation/yellowpillows + name = "Yellow Pillow Crate" + contains = list( + /obj/item/weapon/bedsheet/pillow/yellow = 6 + ) + cost = 10 + containertype = /obj/structure/closet/crate + diff --git a/code/defines/obj.dm b/code/defines/obj.dm index beca4354c1..4bacd5e7d9 100644 --- a/code/defines/obj.dm +++ b/code/defines/obj.dm @@ -12,6 +12,14 @@ if(user.z != src.z) return user.forceMove(pick(latejoin)) +/obj/structure/signpostwood + name = "signpost" + desc = "It's a signpost that tells you things!" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "signpostwood" + anchored = TRUE + density = TRUE + /obj/effect/mark var/mark = "" icon = 'icons/misc/mark.dmi' diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9490a0af46..aab9326c51 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -49,6 +49,13 @@ var/chat_color_darkened /// The chat color var, without alpha. var/chat_color_hover + //! Colors + /** + * used to store the different colors on an atom + * + * its inherent color, the colored paint applied on it, special color effect etc... + */ + var/list/atom_colours /atom/New(loc, ...) // Don't call ..() unless /datum/New() ever exists @@ -731,3 +738,51 @@ // Airflow and ZAS zones now uses CanZASPass() instead of this proc. /atom/proc/CanPass(atom/movable/mover, turf/target) return !density + + +//! ## Atom Colour Priority System +/** + * A System that gives finer control over which atom colour to colour the atom with. + * The "highest priority" one is always displayed as opposed to the default of + * "whichever was set last is displayed" + */ + +/// Adds an instance of colour_type to the atom's atom_colours list +/atom/proc/add_atom_colour(coloration, colour_priority) + if(!atom_colours || !atom_colours.len) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(!coloration) + return + if(colour_priority > atom_colours.len) + return + atom_colours[colour_priority] = coloration + update_atom_colour() + +/// Removes an instance of colour_type from the atom's atom_colours list +/atom/proc/remove_atom_colour(colour_priority, coloration) + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(colour_priority > atom_colours.len) + return + if(coloration && atom_colours[colour_priority] != coloration) + return //if we don't have the expected color (for a specific priority) to remove, do nothing + atom_colours[colour_priority] = null + update_atom_colour() + +/// Resets the atom's color to null, and then sets it to the highest priority colour available +/atom/proc/update_atom_colour() + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + color = null + for(var/C in atom_colours) + if(islist(C)) + var/list/L = C + if(L.len) + color = L + return + else if(C) + color = C + return diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 757283f00f..e4643c0bb2 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -16,9 +16,9 @@ var/moved_recently = 0 var/mob/pulledby = null var/item_state = null // Used to specify the item state for the on-mob overlays. - var/icon_scale_x = 1 // Used to scale icons up or down horizonally in update_transform(). - var/icon_scale_y = 1 // Used to scale icons up or down vertically in update_transform(). - var/icon_rotation = 0 // Used to rotate icons in update_transform() + var/icon_scale_x = DEFAULT_ICON_SCALE_X // Used to scale icons up or down horizonally in update_transform(). + var/icon_scale_y = DEFAULT_ICON_SCALE_Y // Used to scale icons up or down vertically in update_transform(). + var/icon_rotation = DEFAULT_ICON_ROTATION // Used to rotate icons in update_transform() var/icon_expected_height = 32 var/icon_expected_width = 32 var/old_x = 0 @@ -45,6 +45,8 @@ add_overlay(list(em_block), TRUE) if(opacity) AddElement(/datum/element/light_blocking) + if(icon_scale_x != DEFAULT_ICON_SCALE_X || icon_scale_y != DEFAULT_ICON_SCALE_Y || icon_rotation != DEFAULT_ICON_ROTATION) + update_transform() switch(light_system) if(STATIC_LIGHT) update_light() @@ -637,4 +639,4 @@ return selfimage /atom/movable/proc/get_cell() - return \ No newline at end of file + return diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_interdiction.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_interdiction.dm index 365a64f45f..655f1a03f2 100644 --- a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_interdiction.dm +++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_interdiction.dm @@ -250,7 +250,7 @@ to_chat(target, "SYSTEM LOG: User: Admin - Connection Lost. Changes Reverted.") return to_chat(user, "Hack succeeded. The AI is now under your exclusive control.") - to_chat(target, "SYSTEM LOG: System re�3RT5�^#COMU@(#$)TED)@$") + to_chat(target, "SYSTEM LOG: System re'3RT5°^#COMU@(#$)TED)@$") for(var/i = 0, i < 5, i++) var/temptxt = pick("1101000100101001010001001001",\ "0101000100100100000100010010",\ diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_manipulation.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_manipulation.dm index 40023f2cbe..70de180328 100644 --- a/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_manipulation.dm +++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/tree_manipulation.dm @@ -3,7 +3,7 @@ // Abilities in this tree allow the AI to physically manipulate systems around the station. // T1 - Electrical Pulse - Sends out pulse that breaks some lights and sometimes even APCs. This can actually break the AI's APC so be careful! // T2 - Hack Camera - Allows the AI to hack a camera. Deactivated areas may be reactivated, and functional cameras can be upgraded. -// T3 - Emergency Forcefield - Allows the AI to project 1 tile forcefield that blocks movement and air flow. Forcefield�dissipates over time. It is also very susceptible to energetic weaponry. +// T3 - Emergency Forcefield - Allows the AI to project 1 tile forcefield that blocks movement and air flow. Forcefield dissipates over time. It is also very susceptible to energetic weaponry. // T4 - Machine Overload - Detonates machine of choice in a minor explosion. Two of these are usually enough to kill or K/O someone. @@ -205,4 +205,4 @@ if(M) qdel(M) -// END ABILITY VERBS \ No newline at end of file +// END ABILITY VERBS diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm index cad9e72e2f..c14d2f302b 100644 --- a/code/game/machinery/computer/computer.dm +++ b/code/game/machinery/computer/computer.dm @@ -9,6 +9,9 @@ idle_power_usage = 300 active_power_usage = 300 blocks_emissive = FALSE + var/climbable = TRUE + var/list/climbers + var/climb_delay = 3.5 SECONDS var/processing = 0 var/icon_keyboard = "generic_key" @@ -22,6 +25,8 @@ . = ..() power_change() update_icon() + if(climbable) + verbs += /obj/structure/proc/climb_on /obj/machinery/computer/process() if(stat & (NOPOWER|BROKEN)) @@ -132,3 +137,86 @@ return attack_hand(user) return + +/obj/machinery/computer/proc/climb_on() + set name = "Climb structure" + set desc = "Climbs onto a structure." + set category = "Object" + set src in oview(1) + + do_climb(usr) + +/obj/machinery/computer/MouseDrop_T(mob/target, mob/user) + var/mob/living/H = user + if(istype(H) && can_climb(H) && target == user) + do_climb(target) + else + return ..() + +/obj/machinery/computer/proc/can_climb(var/mob/living/user, post_climb_check=0) + if (!climbable || !can_touch(user) || (!post_climb_check && (user in climbers))) + return 0 + + if (!user.Adjacent(src)) + to_chat(user, "You can't climb there, the way is blocked.") + return 0 + + var/obj/occupied = turf_is_crowded() + if(occupied) + to_chat(user, "There's \a [occupied] in the way.") + return 0 + return 1 + +/obj/machinery/computer/proc/turf_is_crowded() + var/turf/T = get_turf(src) + if(!T || !istype(T)) + return "empty void" + if(T.density) + return T + for(var/obj/O in T.contents) + if(istype(O,/obj/machinery/computer)) + var/obj/machinery/computer/S = O + if(S.climbable) continue + if(O && O.density && !(O.flags & ON_BORDER)) //ON_BORDER structures are handled by the Adjacent() check. + return O + return 0 + +/obj/machinery/computer/proc/do_climb(var/mob/living/user) + if (!can_climb(user)) + return + + usr.visible_message("[user] starts climbing onto \the [src]!") + LAZYDISTINCTADD(climbers, user) + + if(!do_after(user,(issmall(user) ? climb_delay * 0.6 : climb_delay))) + LAZYREMOVE(climbers, user) + return + + if (!can_climb(user, post_climb_check=1)) + LAZYREMOVE(climbers, user) + return + + usr.forceMove(climb_to(user)) + + if (get_turf(user) == get_turf(src)) + usr.visible_message("[user] climbs onto \the [src]!") + LAZYREMOVE(climbers, user) + +/obj/machinery/computer/proc/climb_to(var/mob/living/user) + return get_turf(src) + + +/obj/machinery/computer/proc/can_touch(var/mob/user) + if (!user) + return 0 + if(!Adjacent(user)) + return 0 + if (user.restrained() || user.buckled) + to_chat(user, "You need your hands and legs free for this.") + return 0 + if (user.stat || user.paralysis || user.sleeping || user.lying || user.weakened) + return 0 + if (isAI(user)) + to_chat(user, "You need hands for this.") + return 0 + return 1 \ No newline at end of file diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 5ce7fcbc58..38b48ee76a 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -473,70 +473,74 @@ SStranscore.leave_round(to_despawn) //VOREStation Edit End - Resleeving. - //Handle job slot/tater cleanup. - var/job = to_despawn.mind.assigned_role - job_master.FreeRole(job) - to_despawn.mind.assigned_role = null + // Everything below should only be applicable to a cliented living/carbon/human. + // All living/carbon/humans should have minds. - if(to_despawn.mind.objectives.len) - qdel(to_despawn.mind.objectives) - to_despawn.mind.special_role = null + //Handle job slot/tater cleanup. + var/job = to_despawn.mind.assigned_role + job_master.FreeRole(job) + to_despawn.mind.assigned_role = null - //else - //if(ticker.mode.name == "AutoTraitor") - //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode - //current_mode.possible_traitors.Remove(to_despawn) + if(to_despawn.mind.objectives.len) + qdel(to_despawn.mind.objectives) + to_despawn.mind.special_role = null - // Delete them from datacore. + //else + //if(ticker.mode.name == "AutoTraitor") + //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode + //current_mode.possible_traitors.Remove(to_despawn) - if(PDA_Manifest.len) - PDA_Manifest.Cut() - for(var/datum/data/record/R in data_core.medical) - if((R.fields["name"] == to_despawn.real_name)) - qdel(R) - for(var/datum/data/record/T in data_core.security) - if((T.fields["name"] == to_despawn.real_name)) - qdel(T) - for(var/datum/data/record/G in data_core.general) - if((G.fields["name"] == to_despawn.real_name)) - qdel(G) + // Delete them from datacore. - // Also check the hidden version of each datacore, if they're an offmap role. - var/datum/job/J = SSjob.get_job(job) - if(J?.offmap_spawn) - for(var/datum/data/record/R in data_core.hidden_general) + if(PDA_Manifest.len) + PDA_Manifest.Cut() + for(var/datum/data/record/R in data_core.medical) if((R.fields["name"] == to_despawn.real_name)) qdel(R) - for(var/datum/data/record/T in data_core.hidden_security) + for(var/datum/data/record/T in data_core.security) if((T.fields["name"] == to_despawn.real_name)) qdel(T) - for(var/datum/data/record/G in data_core.hidden_medical) + for(var/datum/data/record/G in data_core.general) if((G.fields["name"] == to_despawn.real_name)) qdel(G) - icon_state = base_icon_state + // Also check the hidden version of each datacore, if they're an offmap role. + var/datum/job/J = SSjob.get_job(job) + if(J?.offmap_spawn) + for(var/datum/data/record/R in data_core.hidden_general) + if((R.fields["name"] == to_despawn.real_name)) + qdel(R) + for(var/datum/data/record/T in data_core.hidden_security) + if((T.fields["name"] == to_despawn.real_name)) + qdel(T) + for(var/datum/data/record/G in data_core.hidden_medical) + if((G.fields["name"] == to_despawn.real_name)) + qdel(G) - //TODO: Check objectives/mode, update new targets if this mob is the target, spawn new antags? + icon_state = base_icon_state + + //TODO: Check objectives/mode, update new targets if this mob is the target, spawn new antags? - //Make an announcement and log the person entering storage. - control_computer.frozen_crew += "[to_despawn.real_name], [to_despawn.mind.role_alt_title] - [stationtime2text()]" - control_computer._admin_logs += "[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) at [stationtime2text()]" - log_and_message_admins("[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) entered cryostorage.") + //Make an announcement and log the person entering storage. + control_computer.frozen_crew += "[to_despawn.real_name], [to_despawn.mind.role_alt_title] - [stationtime2text()]" + control_computer._admin_logs += "[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) at [stationtime2text()]" + log_and_message_admins("[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) entered cryostorage.") - //VOREStation Edit Start - var/depart_announce = TRUE - var/departing_job = to_despawn.mind.role_alt_title + //VOREStation Edit Start + var/depart_announce = TRUE + var/departing_job = to_despawn.mind.role_alt_title - if(istype(to_despawn, /mob/living/dominated_brain)) - depart_announce = FALSE + if(istype(to_despawn, /mob/living/dominated_brain)) + depart_announce = FALSE - if(depart_announce) - announce.autosay("[to_despawn.real_name][departing_job ? ", [departing_job], " : " "][on_store_message]", "[on_store_name]", announce_channel, using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)) - visible_message("\The [initial(name)] [on_store_visible_message_1] [to_despawn.real_name] [on_store_visible_message_2]", 3) + if(depart_announce) + announce.autosay("[to_despawn.real_name][departing_job ? ", [departing_job], " : " "][on_store_message]", "[on_store_name]", announce_channel, using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)) + visible_message("\The [initial(name)] [on_store_visible_message_1] [to_despawn.real_name] [on_store_visible_message_2]", 3) - //VOREStation Edit End + + //VOREStation Edit End //VOREStation Edit begin: Dont delete mobs-in-mobs if(to_despawn.client && to_despawn.stat<2) diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index d991eddae9..b275821a59 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -130,7 +130,7 @@ Class Procs: /obj/machinery/Initialize(var/mapload) . = ..() - global.machines += src + SSmachines.all_machines += src if(ispath(circuit)) circuit = new circuit(src) if(!speed_process) @@ -145,7 +145,7 @@ Class Procs: STOP_MACHINE_PROCESSING(src) else STOP_PROCESSING(SSfastprocess, src) - global.machines -= src + SSmachines.all_machines -= src if(component_parts) for(var/atom/A in component_parts) if(A.loc == src) // If the components are inside the machine, delete them. diff --git a/code/game/machinery/painter_vr.dm b/code/game/machinery/painter_vr.dm index e10311915d..53614b2f90 100644 --- a/code/game/machinery/painter_vr.dm +++ b/code/game/machinery/painter_vr.dm @@ -1,62 +1,81 @@ -// I'm honestly pretty sure that short of stuffing five million things into this -// there's absolutely no way it could ever have any performance impact -// Given that all it does is set the color var -// But just in case it's cursed in some arcane horrible way -// I'm going to leave this limit here -#define MAX_PROCESSING 10 // Arbitrary performance insurance - /obj/machinery/gear_painter name = "Color Mate" - desc = "A machine to give your apparel a fresh new color! Recommended to use with white items for best results." + desc = "A machine to give your apparel a fresh new color!" icon = 'icons/obj/vending_vr.dmi' icon_state = "colormate" density = TRUE anchored = TRUE - var/list/processing = list() + var/atom/movable/inserted var/activecolor = "#FFFFFF" + var/list/color_matrix_last + var/active_mode = COLORMATE_HSV + + var/build_hue = 0 + var/build_sat = 1 + var/build_val = 1 + + /// Allow holder'd mobs + var/allow_mobs = TRUE + /// Minimum lightness for normal mode + var/minimum_normal_lightness = 50 + /// Minimum lightness for matrix mode, tested using 4 test colors of full red, green, blue, white. + var/minimum_matrix_lightness = 75 + /// Minimum matrix tests that must pass for something to be considered a valid color (see above) + var/minimum_matrix_tests = 2 + /// Temporary messages + var/temp + var/list/allowed_types = list( - /obj/item/clothing, - /obj/item/weapon/storage/backpack, - /obj/item/weapon/storage/belt, - /obj/item/device/radio/headset - ) + /obj/item/clothing, + /obj/item/weapon/storage/backpack, + /obj/item/weapon/storage/belt, + /obj/item/toy + ) + +/obj/machinery/gear_painter/Initialize(mapload) + . = ..() + color_matrix_last = list( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + 0, 0, 0, + ) /obj/machinery/gear_painter/update_icon() if(panel_open) icon_state = "colormate_open" else if(inoperable()) icon_state = "colormate_off" - else if(processing.len) + else if(inserted) icon_state = "colormate_active" else icon_state = "colormate" /obj/machinery/gear_painter/Destroy() - for(var/atom/movable/O in processing) - O.forceMove(drop_location()) - processing.Cut() + if(inserted) //please i beg you do not drop nulls + inserted.forceMove(drop_location()) return ..() -/obj/machinery/gear_painter/attackby(obj/item/W, mob/user) - if(LAZYLEN(processing) >= MAX_PROCESSING) - to_chat(user, "The machine is full.") +/obj/machinery/gear_painter/attackby(obj/item/I, mob/living/user) + if(inserted) + to_chat(user, SPAN_WARNING("The machine is already loaded.")) return - if(default_deconstruction_screwdriver(user, W)) + if(default_deconstruction_screwdriver(user, I)) return - if(default_deconstruction_crowbar(user, W)) + if(default_deconstruction_crowbar(user, I)) return - if(default_unfasten_wrench(user, W, 40)) + if(default_unfasten_wrench(user, I, 40)) return - if(is_type_in_list(W, allowed_types) && !inoperable()) - user.visible_message("[user] inserts \the [W] into the Color Mate receptable.") - user.drop_from_inventory(W) - W.forceMove(src) - processing |= W + if(is_type_in_list(I, allowed_types) && !inoperable()) + user.visible_message("[user] inserts \the [I] into the Color Mate receptable.") + user.drop_from_inventory(I) + I.forceMove(src) + inserted = I SStgui.update_uis(src) + else - ..() - update_icon() + return ..() /obj/machinery/gear_painter/attack_hand(mob/user) if(..()) @@ -69,49 +88,209 @@ ui = new(user, src, "ColorMate", name) ui.open() -/obj/machinery/gear_painter/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) - var/list/data = ..() +/obj/machinery/gear_painter/proc/insert_mob(mob/victim, mob/user) + if(inserted) + return + if(user) + visible_message(SPAN_WARNING("[user] stuffs [victim] into [src]!")) + inserted = victim + inserted.forceMove(src) - var/list/items = list() - for(var/atom/movable/O in processing) - items.Add("[O]") - data["items"] = items +/obj/machinery/gear_painter/AllowDrop() + return FALSE - data["activecolor"] = activecolor - return data +// /obj/machinery/gear_painter/handle_atom_del(atom/movable/AM) +// if(AM == inserted) +// inserted = null +// return ..() -/obj/machinery/gear_painter/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) - if(..()) - return TRUE - - add_fingerprint(usr) - - switch(action) - if("select") - var/newcolor = input(usr, "Choose a color.", "", activecolor) as color|null - if(newcolor) - activecolor = newcolor - . = TRUE - - if("paint") - for(var/atom/movable/O in processing) - O.color = activecolor - CHECK_TICK - playsound(src, 'sound/effects/spray3.ogg', 50, 1) - . = TRUE - - if("clear") - for(var/atom/movable/O in processing) - O.color = initial(O.color) - CHECK_TICK - playsound(src, 'sound/effects/spray3.ogg', 50, 1) - . = TRUE - - if("eject") - for(var/atom/movable/O in processing) - O.forceMove(drop_location()) - CHECK_TICK - processing.Cut() - . = TRUE +/obj/machinery/gear_painter/AltClick(mob/user) + . = ..() + drop_item() +/obj/machinery/gear_painter/proc/drop_item() + if(!oview(1,src)) + return + if(!inserted) + return + to_chat(usr, SPAN_NOTICE("You remove [inserted] from [src]")) + inserted.forceMove(drop_location()) + var/mob/living/user = usr + if(istype(user)) + user.put_in_hands(inserted) + inserted = null update_icon() + SStgui.update_uis(src) + +/obj/machinery/gear_painter/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ColorMate", src.name) + ui.set_autoupdate(FALSE) //This might be a bit intensive, better to not update it every few ticks + ui.open() + +/obj/machinery/gear_painter/tgui_data(mob/user) + . = list() + .["activemode"] = active_mode + .["matrixcolors"] = list( + "rr" = color_matrix_last[1], + "rg" = color_matrix_last[2], + "rb" = color_matrix_last[3], + "gr" = color_matrix_last[4], + "gg" = color_matrix_last[5], + "gb" = color_matrix_last[6], + "br" = color_matrix_last[7], + "bg" = color_matrix_last[8], + "bb" = color_matrix_last[9], + "cr" = color_matrix_last[10], + "cg" = color_matrix_last[11], + "cb" = color_matrix_last[12], + ) + .["buildhue"] = build_hue + .["buildsat"] = build_sat + .["buildval"] = build_val + if(temp) + .["temp"] = temp + if(inserted) + .["item"] = list() + .["item"]["name"] = inserted.name + .["item"]["sprite"] = icon2base64(get_flat_icon(inserted,dir=SOUTH,no_anim=TRUE)) + .["item"]["preview"] = icon2base64(build_preview()) + else + .["item"] = null + +/obj/machinery/gear_painter/tgui_act(action, params) + . = ..() + if(.) + return + if(inserted) + switch(action) + if("switch_modes") + active_mode = text2num(params["mode"]) + return TRUE + if("choose_color") + var/chosen_color = input(usr, "Choose a color: ", "ColorMate colour picking", activecolor) as color|null + if(chosen_color) + activecolor = chosen_color + return TRUE + if("paint") + do_paint(usr) + temp = "Painted Successfully!" + return TRUE + if("drop") + temp = "" + drop_item() + return TRUE + if("clear") + inserted.remove_atom_colour(FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + temp = "Cleared Successfully!" + return TRUE + if("set_matrix_color") + color_matrix_last[params["color"]] = params["value"] + return TRUE + if("set_hue") + build_hue = clamp(text2num(params["buildhue"]), 0, 360) + return TRUE + if("set_sat") + build_sat = clamp(text2num(params["buildsat"]), -10, 10) + return TRUE + if("set_val") + build_val = clamp(text2num(params["buildval"]), -10, 10) + return TRUE + + +/obj/machinery/gear_painter/proc/do_paint(mob/user) + var/color_to_use + switch(active_mode) + if(COLORMATE_TINT) + color_to_use = activecolor + if(COLORMATE_MATRIX) + color_to_use = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(COLORMATE_HSV) + color_to_use = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = color_to_use + if(!color_to_use || !check_valid_color(color_to_use, user)) + to_chat(user, SPAN_NOTICE("Invalid color.")) + return FALSE + inserted.add_atom_colour(color_to_use, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + return TRUE + + +/// Produces the preview image of the item, used in the UI, the way the color is not stacking is a sin. +/obj/machinery/gear_painter/proc/build_preview() + if(inserted) //sanity + var/list/cm + switch(active_mode) + if(COLORMATE_MATRIX) + cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]), + ) + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_TINT) + if(!check_valid_color(activecolor, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + if(COLORMATE_HSV) + cm = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = cm + if(!check_valid_color(cm, usr)) + return get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + + var/cur_color = inserted.color + inserted.color = null + inserted.color = (active_mode == COLORMATE_TINT ? activecolor : cm) + var/icon/preview = get_flat_icon(inserted, dir=SOUTH, no_anim=TRUE) + inserted.color = cur_color + temp = "" + + . = preview + +/obj/machinery/gear_painter/proc/check_valid_color(list/cm, mob/user) + if(!islist(cm)) // normal + var/list/HSV = ReadHSV(RGBtoHSV(cm)) + if(HSV[3] < minimum_normal_lightness) + temp = "[cm] is too dark (Minimum lightness: [minimum_normal_lightness])" + return FALSE + return TRUE + else // matrix + // We test using full red, green, blue, and white + // A predefined number of them must pass to be considered valid + var/passed = 0 +#define COLORTEST(thestring, thematrix) passed += (ReadHSV(RGBtoHSV(RGBMatrixTransform(thestring, thematrix)))[3] >= minimum_matrix_lightness) + COLORTEST("FF0000", cm) + COLORTEST("00FF00", cm) + COLORTEST("0000FF", cm) + COLORTEST("FFFFFF", cm) +#undef COLORTEST + if(passed < minimum_matrix_tests) + temp = "Matrix is too dark. (passed [passed] out of [minimum_matrix_tests] required tests. Minimum lightness: [minimum_matrix_lightness])." + return FALSE + return TRUE diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index f6051c0318..74a7e624b8 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -288,7 +288,7 @@ name = "mine" desc = "A small explosive mine with 'HE' and a grenade symbol on the side." icon = 'icons/obj/weapons.dmi' - icon_state = "uglymine" + icon_state = "landmine" var/countdown = 10 var/minetype = /obj/effect/mine //This MUST be an /obj/effect/mine type, or it'll runtime. diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index c97693ed5f..55b744f0dc 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -26,7 +26,7 @@ if(message) audible_message("\icon[src][bicon(src)] \The [src.name] states, \"[message]\"", runemessage = "synthesized speech") if(ismob(loc)) - loc.audible_message("", runemessage = "\[TTS Voice\] [message]") + loc.runechat_message("\[TTS Voice\] [message]") /obj/item/device/text_to_speech/AltClick(mob/user) // QOL Change attack_self(user) diff --git a/code/game/objects/random/mapping.dm b/code/game/objects/random/mapping.dm index bf5bca2359..225b704ddd 100644 --- a/code/game/objects/random/mapping.dm +++ b/code/game/objects/random/mapping.dm @@ -140,7 +140,7 @@ name = "Random Land Mine" desc = "This is a random land mine." icon = 'icons/obj/weapons.dmi' - icon_state = "uglymine" + icon_state = "landmine" spawn_nothing_percentage = 25 /obj/random/landmine/item_to_spawn() diff --git a/code/game/objects/structures/crates_lockers/__closets.dm b/code/game/objects/structures/crates_lockers/__closets.dm index f56c0269a4..46a3226464 100644 --- a/code/game/objects/structures/crates_lockers/__closets.dm +++ b/code/game/objects/structures/crates_lockers/__closets.dm @@ -40,6 +40,7 @@ var/is_animating_door = FALSE /// Our visual object for the closet door, if we're animating var/obj/effect/overlay/closet_door/door_obj + var/vore_sound = 'sound/effects/metalscrape2.ogg' /obj/structure/closet/Initialize() ..() @@ -543,3 +544,49 @@ M.Multiply(matrix(cos(angle), 0, 0, -sin(angle) * closet_appearance.door_anim_squish, 1, 0)) M.Translate(closet_appearance.door_hinge, 0) return M + +//verb to eat people in the same closet as yourself + +/obj/structure/closet/verb/hidden_vore() + set src in oview(1) + set category = "Object" + set name = "Vore Occupants" + + if(!istype(usr, /mob/living)) //no ghosts + return + + if(!(usr in src.contents)) + to_chat(usr, "You need to be inside \the [src] to do this.") + return + + var/list/targets = list() //IF IT IS NOT BROKEN. DO NOT FIX IT. + + for(var/mob/living/L in src.contents) + if(!istype(L, /mob/living)) //Don't eat anything that isn't mob/living. Failsafe. + continue + if(L == usr) //no eating yourself. 1984. + continue + if(L.devourable) + targets += L + + if(targets == 0) + to_chat(src, "No eligible targets found.") + return + + var/mob/living/target = tgui_input_list(usr, "Please select a target.", "Victim", targets) + + if(!target) + return + + if(!istype(target, /mob/living)) //Safety. + to_chat(src, "You need to select a living target!") + return + + if (get_dist(src,target) >= 1 || get_dist(src,usr) >= 1) //in case they leave the locker + to_chat(src, "You are no longer both in \the [src].") + return + + playsound(src, vore_sound, 25) + + var/mob/living/M = usr + M.perform_the_nom(usr,target,usr,usr.vore_selected,1) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index f79d770f74..48bea9205b 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -6,6 +6,7 @@ open_sound = 'sound/effects/wooden_closet_open.ogg' close_sound = 'sound/effects/wooden_closet_close.ogg' + vore_sound = 'sound/effects/woodhit.ogg' /obj/structure/closet/acloset name = "strange closet" @@ -82,3 +83,34 @@ icon = 'icons/obj/closets/abductor.dmi' anchored = TRUE closet_appearance = null // special icons + +/obj/structure/closet/tent + name = "tent" + desc = "An old fashioned tent that can easily fit a couple of people." + icon = 'icons/obj/closets/tent.dmi' + closet_appearance = null + anchored = 1 + open_sound = 'sound/effects/rustle3.ogg' + close_sound = 'sound/effects/rustle4.ogg' + vore_sound = 'sound/effects/rustle1.ogg' + +/obj/structure/closet/tent/B + icon = 'icons/obj/closets/tentB.dmi' + +/datum/crafting_recipe/tent + name = "tent (green)" + result = /obj/structure/closet/tent + reqs = list( + list(/obj/item/stack/material/cloth = 10) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/tentb + name = "tent (tan)" + result = /obj/structure/closet/tent/B + reqs = list( + list(/obj/item/stack/material/cloth = 10) + ) + time = 60 + category = CAT_MISC diff --git a/code/game/objects/structures/pillows.dm b/code/game/objects/structures/pillows.dm new file mode 100644 index 0000000000..fa97543f09 --- /dev/null +++ b/code/game/objects/structures/pillows.dm @@ -0,0 +1,260 @@ +//Pillows with sprites ported from skyrat + +/obj/item/weapon/bedsheet/pillow + name = "pillow" + desc = "A surprisingly soft stuffed pillow." + icon = 'icons/obj/pillows.dmi' + icon_state = "pillow_pink_square" + slot_flags = 0 + var/pile_type = "/obj/structure/bed/pillowpile" + throw_range = 7 + +/obj/item/weapon/bedsheet/pillow/attack_self(mob/user as mob) + user.drop_item() + if(icon_state == initial(icon_state)) + icon_state = "[icon_state]_placed" + add_fingerprint(user) + +/obj/item/weapon/bedsheet/pillow/pickup(mob/user) + ..() + icon_state = initial(icon_state) + +/obj/item/weapon/bedsheet/pillow/attackby(var/obj/item/component, mob/user as mob) + if (istype(component,src)) + to_chat(user, "You assemble a pillow pile!") + user.drop_item() + qdel(component) + var/turf/T = get_turf(src) + new pile_type(T) + user.drop_from_inventory(src) + qdel(src) + else + to_chat(user, "You can't assemble a pillow pile out of mismatched stuff, it'd look hideous!") + +//Pillow Piles, they're piles of pillows! layer = BELOW_MOB_LAYER + +/obj/structure/bed/pillowpile + name = "pillow pile" + desc = "A massive pile of pillows!" + icon = 'icons/obj/pillows.dmi' + icon_state = "pillowpile_large_pink" + var/pillowpilefront = "/obj/structure/bed/pillowpilefront" + var/sourcepillow = "/obj/item/weapon/bedsheet/pillow" + +/obj/structure/bed/pillowpilefront + name = "pillow pile" + desc = "A massive pile of pillows!" + icon = 'icons/obj/pillows.dmi' + icon_state = "pillowpile_large_pink_overlay" + layer = ABOVE_MOB_LAYER + plane = MOB_PLANE + var/sourcepillow = "/obj/item/weapon/bedsheet/pillow" + +/obj/structure/bed/pillowpile/New() + ..() + var/turf/T = get_turf(src) + new pillowpilefront(T) + +/obj/structure/bed/pillowpilefront/update_icon() + return + +/obj/structure/bed/pillowpile/update_icon() + return + +/obj/structure/bed/pillowpile/attack_hand(mob/user) + to_chat(user, "Now disassembling the large pillow pile...") + if(do_after(user, 30)) + if(!src) return + to_chat(user, "You dissasembled the large pillow pile!") + new sourcepillow(src.loc) + qdel(src) + +/obj/structure/bed/pillowpilefront/attack_hand(mob/user) + to_chat(user, "Now disassembling the front of the pillow pile...") + if(do_after(user, 30)) + if(!src) return + to_chat(user, "You dissasembled the the front of the pillow pile!") + new sourcepillow(src.loc) + qdel(src) + +//Colours + +//teal + +/obj/item/weapon/bedsheet/pillow/teal + icon_state = "pillow_teal_square" + pile_type = "/obj/structure/bed/pillowpile/teal" + +/obj/structure/bed/pillowpile/teal + icon_state = "pillowpile_large_teal" + pillowpilefront = "/obj/structure/bed/pillowpilefront/teal" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/teal" + +/obj/structure/bed/pillowpilefront/teal + icon_state = "pillowpile_large_teal_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/teal" + +//yellow + +/obj/item/weapon/bedsheet/pillow/yellow + icon_state = "pillow_yellow_square" + pile_type = "/obj/structure/bed/pillowpile/yellow" + +/obj/structure/bed/pillowpile/yellow + icon_state = "pillowpile_large_yellow" + pillowpilefront = "/obj/structure/bed/pillowpilefront/yellow" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/yellow" + +/obj/structure/bed/pillowpilefront/yellow + icon_state = "pillowpile_large_yellow_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/yellow" + +//white + +/obj/item/weapon/bedsheet/pillow/white + icon_state = "pillow_white_square" + pile_type = "/obj/structure/bed/pillowpile/white" + +/obj/structure/bed/pillowpile/white + icon_state = "pillowpile_large_white" + pillowpilefront = "/obj/structure/bed/pillowpilefront/white" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/white" + +/obj/structure/bed/pillowpilefront/white + icon_state = "pillowpile_large_white_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/white" + +//black + +/obj/item/weapon/bedsheet/pillow/black + icon_state = "pillow_black_square" + pile_type = "/obj/structure/bed/pillowpile/black" + +/obj/structure/bed/pillowpile/black + icon_state = "pillowpile_large_black" + pillowpilefront = "/obj/structure/bed/pillowpilefront/black" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/black" + +/obj/structure/bed/pillowpilefront/black + icon_state = "pillowpile_large_black_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/black" + +//green + +/obj/item/weapon/bedsheet/pillow/green + icon_state = "pillow_green_square" + pile_type = "/obj/structure/bed/pillowpile/green" + +/obj/structure/bed/pillowpile/green + icon_state = "pillowpile_large_green" + pillowpilefront = "/obj/structure/bed/pillowpilefront/green" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/green" + +/obj/structure/bed/pillowpilefront/green + icon_state = "pillowpile_large_green_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/green" + +//red + +/obj/item/weapon/bedsheet/pillow/red + icon_state = "pillow_red_square" + pile_type = "/obj/structure/bed/pillowpile/red" + +/obj/structure/bed/pillowpile/red + icon_state = "pillowpile_large_red" + pillowpilefront = "/obj/structure/bed/pillowpilefront/red" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/red" + +/obj/structure/bed/pillowpilefront/red + icon_state = "pillowpile_large_red_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/red" + +//orange + +/obj/item/weapon/bedsheet/pillow/orange + icon_state = "pillow_orange_square" + pile_type = "/obj/structure/bed/pillowpile/orange" + +/obj/structure/bed/pillowpile/orange + icon_state = "pillowpile_large_orange" + pillowpilefront = "/obj/structure/bed/pillowpilefront/orange" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/orange" + +/obj/structure/bed/pillowpilefront/orange + icon_state = "pillowpile_large_orange_overlay" + sourcepillow = "/obj/item/weapon/bedsheet/pillow/orange" + +//crafting + +/datum/crafting_recipe/pillowpink + name = "pillow (pink)" + result = /obj/item/weapon/bedsheet/pillow + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowteal + name = "pillow (teal)" + result = /obj/item/weapon/bedsheet/pillow/teal + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowwhite + name = "pillow (white)" + result = /obj/item/weapon/bedsheet/pillow/white + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowblack + name = "pillow (black)" + result = /obj/item/weapon/bedsheet/pillow/black + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowgreen + name = "pillow (green)" + result = /obj/item/weapon/bedsheet/pillow/green + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowyellow + name = "pillow (yellow)" + result = /obj/item/weapon/bedsheet/pillow/yellow + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pillowred + name = "pillow (red)" + result = /obj/item/weapon/bedsheet/pillow/red + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + +/datum/crafting_recipe/pilloworange + name = "pillow (orange)" + result = /obj/item/weapon/bedsheet/pillow/orange + reqs = list( + list(/obj/item/stack/material/cloth = 6) + ) + time = 60 + category = CAT_MISC + diff --git a/code/global.dm b/code/global.dm index 5a4ea32019..9c0fbec990 100644 --- a/code/global.dm +++ b/code/global.dm @@ -1,8 +1,7 @@ // Items that ask to be called every cycle. var/global/datum/datacore/data_core = null -var/global/list/machines = list() // ALL Machines, wether processing or not. -var/global/list/processing_machines = list() // TODO - Move into SSmachines -var/global/list/processing_power_items = list() // TODO - Move into SSmachines +var/global/list/machines = SSmachines.all_machines //I would upgrade all instances of global.machines to SSmachines.all_machines but it's used in so many places and a search returns so many matches for 'machines' that isn't a use of the global... + var/global/list/active_diseases = list() var/global/list/hud_icon_reference = list() @@ -91,8 +90,6 @@ var/list/IClog = list() var/list/OOClog = list() var/list/adminlog = list() -var/list/powernets = list() // TODO - Move into SSmachines - var/Debug2 = 0 var/datum/debug/debugobj @@ -180,7 +177,7 @@ var/max_explosion_range = 14 // Announcer intercom, because too much stuff creates an intercom for one message then hard del()s it. var/global/obj/item/device/radio/intercom/omni/global_announcer = new /obj/item/device/radio/intercom/omni(null) -var/list/station_departments = list("Command", "Medical", "Engineering", "Science", "Security", "Cargo", "Exploration", "Civilian") //VOREStation Edit +var/list/station_departments = list("Command", "Medical", "Engineering", "Research", "Security", "Cargo", "Exploration", "Civilian") //VOREStation Edit //Icons for in-game HUD glasses. Why don't we just share these a little bit? var/static/icon/ingame_hud = icon('icons/mob/hud.dmi') diff --git a/code/matrices/color_matrix.dm b/code/matrices/color_matrix.dm new file mode 100644 index 0000000000..1e2988ea9b --- /dev/null +++ b/code/matrices/color_matrix.dm @@ -0,0 +1,240 @@ +///////////////////// +// COLOUR MATRICES // +///////////////////// + +/* Documenting a couple of potentially useful color matrices here to inspire ideas. +// Greyscale - indentical to saturation @ 0 +list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0) + +// Color inversion +list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0) + +// Sepiatone +list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0) +*/ + +/// Does nothing. +/proc/color_matrix_identity() + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +/** + * Adds/subtracts overall lightness. + * 0 is identity, 1 makes everything white, -1 makes everything black. + */ +/proc/color_matrix_lightness(power) + return list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0) + +/** + * Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected. + * 1 is identity, 0 is greyscale, >1 oversaturates colors. + */ +/proc/color_matrix_saturation(value) + var/inv = 1 - value + var/R = round(LUMA_R * inv, 0.001) + var/G = round(LUMA_G * inv, 0.001) + var/B = round(LUMA_B * inv, 0.001) + + return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0) + +/** + * Exxagerates or removes colors. + */ +/proc/color_matrix_saturation_percent(percent) + if(percent == 0) + return color_matrix_identity() + percent = clamp(percent, -100, 100) + if(percent > 0) + percent *= 3 + var/x = 1 + percent / 100 + var/inv = 1 - x + var/R = LUMA_R * inv + var/G = LUMA_G * inv + var/B = LUMA_B * inv + + return list(R + x,R,R, G,G + x,G, B,B,B + x) + +/** + * Greyscale matrix. + */ +/proc/color_matrix_greyscale() + return list(LUMA_R, LUMA_R, LUMA_R, LUMA_G, LUMA_G, LUMA_G, LUMA_B, LUMA_B, LUMA_B) + +/** + * Changes distance colors have from rgb(127,127,127) grey. + * 1 is identity. 0 makes everything grey >1 blows out colors and greys. + */ +/proc/color_matrix_contrast(value) + var/add = (1 - value) / 2 + return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0) + +/** + * Exxagerates or removes brightness. + */ +/proc/color_matrix_contrast_percent(percent) + var/static/list/delta_index = list( + 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, + 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, + 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, + 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, + 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, + 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, + 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, + 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, + 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, + 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, + 10.0) + percent = clamp(percent, -100, 100) + if(percent == 0) + return color_matrix_identity() + + var/x = 0 + if (percent < 0) + x = 127 + percent / 100 * 127; + else + x = percent % 1 + if(x == 0) + x = delta_index[percent] + else + x = delta_index[percent] * (1-x) + delta_index[percent+1] * x//use linear interpolation for more granularity. + x = x * 127 + 127 + + var/mult = x / 127 + var/add = 0.5 * (127-x) / 255 + return list(mult,0,0, 0,mult,0, 0,0,mult, add,add,add) + +/** + * Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys. + * 0 is identity, 120 moves reds to greens, 240 moves reds to blues. + */ +// +// +/proc/color_matrix_rotate_hue(angle) + var/sin = sin(angle) + var/cos = cos(angle) + var/cos_inv_third = 0.333*(1-cos) + var/sqrt3_sin = sqrt(3)*sin + return list( + round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0, + round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0, + round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0, + 0,0,0,1, + 0,0,0,0, + ) + +/** + * Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites. + * TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone) + */ +/proc/color_matrix_rotation(angle) + if(angle == 0) + return color_matrix_identity() + angle = clamp(angle, -180, 180) + var/cos = cos(angle) + var/sin = sin(angle) + + var/constA = 0.143 + var/constB = 0.140 + var/constC = -0.283 + return list( + LUMA_R + cos * (1-LUMA_R) + sin * -LUMA_R, LUMA_R + cos * -LUMA_R + sin * constA, LUMA_R + cos * -LUMA_R + sin * -(1-LUMA_R), + LUMA_G + cos * -LUMA_G + sin * -LUMA_G, LUMA_G + cos * (1-LUMA_G) + sin * constB, LUMA_G + cos * -LUMA_G + sin * LUMA_G, + LUMA_B + cos * -LUMA_B + sin * (1-LUMA_B), LUMA_B + cos * -LUMA_B + sin * constC, LUMA_B + cos * (1-LUMA_B) + sin * LUMA_B + ) + +/** + * These next three rotate values about one axis only. + * x is the red axis, y is the green axis, z is the blue axis. + */ +/proc/color_matrix_rotate_x(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_y(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0) + +/proc/color_matrix_rotate_z(angle) + var/sinval = round(sin(angle), 0.001) + var/cosval = round(cos(angle), 0.001) + return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) + +/** + * Builds a color matrix that transforms the hue, saturation, and value, all in one operation. + */ +/proc/color_matrix_hsv(hue, saturation, value) + hue = clamp(360 - hue, 0, 360) + + // This is very much a rough approximation of hueshifting. This carries some artifacting, such as negative values that simply shouldn't exist, but it does get the job done, and that's what matters. + var/cos_a = cos(hue) // These have to be inverted from 360, otherwise the hue's inverted + var/sin_a = sin(hue) + var/rot_x = cos_a + (1 - cos_a) / 3 + var/rot_y = (1 - cos_a) / 3 - 0.5774 * sin_a // 0.5774 is sqrt(1/3) + var/rot_z = (1 - cos_a) / 3 + 0.5774 * sin_a + + return list( + round((((1-saturation) * LUMA_R) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_z * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_G) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_y * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_B) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_x * saturation)) * value, 0.01), + 0, 0, 0 + ) + +/** + * Returns a matrix addition of A with B. + */ +/proc/color_matrix_add(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + for(var/value in 1 to 20) + output[value] = A[value] + B[value] + return output + +/** + * Returns a matrix multiplication of A with B. + */ +/proc/color_matrix_multiply(list/A, list/B) + if(!istype(A) || !istype(B)) + return color_matrix_identity() + if(A.len != 20 || B.len != 20) + return color_matrix_identity() + var/list/output = list() + output.len = 20 + var/x = 1 + var/y = 1 + var/offset = 0 + for(y in 1 to 5) + offset = (y-1)*4 + for(x in 1 to 4) + output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y==5?B[x+16]:0), 0.001) + return output + +/** + * Assembles a color matrix, defaulting to identity. + */ +/proc/rgb_construct_color_matrix(rr = 1, rg, rb, gr, gg = 1, gb, br, bg, bb = 1, cr, cg, cb) + return list(rr, rg, rb, gr, gg, gb, br, bg, bb, cr, cg, cb) + +/** + * Assembles a color matrix, defaulting to identity. + */ +/proc/rgba_construct_color_matrix(rr = 1, rg, rb, ra, gr, gg = 1, gb, ga, br, bg, bb = 1, ba, ar, ag, ab, aa = 1, cr, cg, cb, ca) + return list(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, cr, cg, cb, ca) + +/** + * Constructs a colored greyscale matrix. + * WARNING: Bad math up ahead. + */ +/proc/rgba_auto_greyscale_matrix(rgba_string) + // process rgb(a) + var/list/L1 = ReadRGB(rgba_string) + ASSERT(L1.len) + if(L1.len == 3) + return rgba_construct_color_matrix(0.39, 0.39, 0.39, 0, 0.5, 0.5, 0.5, 0, 0.11, 0.11, 0.11, 0, 0, 0, 0, 1, max(-0.5, (L1[1] - 255) / 255), max(-0.5, (L1[2] - 255) / 255), max(-0.5, (L1[3] - 255) / 255), 0) + else + // alpha + return rgba_construct_color_matrix(0.39, 0.39, 0.39, 0, 0.5, 0.5, 0.5, 0, 0.11, 0.11, 0.11, 0, 0, 0, 0, 0, max(-0.5, (L1[1] - 255) / 255), max(-0.5, (L1[2] - 255) / 255), max(-0.5, (L1[3] - 255) / 255), L1[4] / 255) diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index e903145cb3..ecc9b3f024 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -131,6 +131,7 @@ var/list/admin_verbs_admin = list( /client/proc/unmake_mentor, /client/proc/removetickets, /client/proc/delbook, + /client/proc/toggle_spawning_with_recolour, /client/proc/setckey, //YW add - readds SetCkey proc /client/proc/dbcon_fix //YW add - adds Fix Database Connection proc ) @@ -562,7 +563,8 @@ var/list/admin_verbs_event_manager = list( /client/proc/cmd_admin_delete, //delete an instance/object/mob/etc, /client/proc/cmd_debug_del_all, /client/proc/toggle_random_events, - /client/proc/modify_server_news + /client/proc/modify_server_news, + /client/proc/toggle_spawning_with_recolour ) diff --git a/code/modules/admin/admin_verbs_vr.dm b/code/modules/admin/admin_verbs_vr.dm index 3a65d070d2..8f46703edb 100644 --- a/code/modules/admin/admin_verbs_vr.dm +++ b/code/modules/admin/admin_verbs_vr.dm @@ -120,3 +120,20 @@ usr << browse(dat, "window=library") onclose(usr, "library") + +/client/proc/toggle_spawning_with_recolour() + set name = "Toggle Simple/Robot recolour verb" + set desc = "Makes it so new robots/simple_mobs spawn with a verb to recolour themselves for this round. You must set them separately." + set category = "Server" + + if(!check_rights(R_ADMIN|R_EVENT|R_FUN)) + return + + var/which = tgui_alert(usr, "Which do you want to toggle?", "Choose Recolour Toggle", list("Robot", "Simple Mob")) + switch(which) + if("Robot") + config.allow_robot_recolor = !config.allow_robot_recolor + to_chat(usr, "You have [config.allow_robot_recolor ? "enabled" : "disabled"] newly spawned cyborgs to spawn with the recolour verb") + if("Simple Mob") + config.allow_simple_mob_recolor = !config.allow_simple_mob_recolor + to_chat(usr, "You have [config.allow_simple_mob_recolor ? "enabled" : "disabled"] newly spawned simple mobs to spawn with the recolour verb") diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 64e3c418b4..f0588eeb5c 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -57,7 +57,7 @@ if(AH) message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.") - var/msg = tgui_input_text(src,"Message:", "Private message to [key_name(C, 0, 0)]") + var/msg = tgui_input_text(src,"Message:", "Private message to [key_name(C, 0, 0)]", multiline = TRUE) if (!msg) message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.") return @@ -92,7 +92,7 @@ if(!ircreplyamount) //to prevent people from spamming irc return if(!msg) - msg = tgui_input_text(src,"Message:", "Private message to Administrator") + msg = tgui_input_text(src,"Message:", "Private message to Administrator", multiline = TRUE) if(!msg) return @@ -112,7 +112,7 @@ //get message text, limit it's length.and clean/escape html if(!msg) - msg = tgui_input_text(src,"Message:", "Private message to [key_name(recipient, 0, 0)]") + msg = tgui_input_text(src,"Message:", "Private message to [key_name(recipient, 0, 0)]", multiline = TRUE) if(!msg) return @@ -188,7 +188,7 @@ spawn() //so we don't hold the caller proc up var/sender = src var/sendername = key - var/reply = tgui_input_text(recipient, msg,"Admin PM from-[sendername]", "") //show message and await a reply + var/reply = tgui_input_text(recipient, msg,"Admin PM from-[sendername]", "", multiline = TRUE) //show message and await a reply if(recipient && reply) if(sender) recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index 082f0e26b0..64c33419d0 100644 --- a/code/modules/admin/verbs/atmosdebug.dm +++ b/code/modules/admin/verbs/atmosdebug.dm @@ -48,7 +48,7 @@ feedback_add_details("admin_verb","CPOW") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - for (var/datum/powernet/PN in powernets) + for (var/datum/powernet/PN in SSmachines.powernets) if (!PN.nodes || !PN.nodes.len) if(PN.cables && (PN.cables.len > 1)) var/obj/structure/cable/C = PN.cables[1] @@ -57,4 +57,4 @@ if (!PN.cables || (PN.cables.len < 10)) if(PN.cables && (PN.cables.len > 1)) var/obj/structure/cable/C = PN.cables[1] - to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") \ No newline at end of file + to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") diff --git a/code/modules/admin/verbs/grief_fixers.dm b/code/modules/admin/verbs/grief_fixers.dm index 9ad4619166..1024fb7d32 100644 --- a/code/modules/admin/verbs/grief_fixers.dm +++ b/code/modules/admin/verbs/grief_fixers.dm @@ -20,7 +20,7 @@ to_chat(usr, "\[1/5\] - Supermatter depowered") // Remove all gases from all pipenets - for(var/datum/pipe_network/PN in pipe_networks) + for(var/datum/pipe_network/PN in SSmachines.networks) for(var/datum/gas_mixture/G in PN.gases) G.gas = list() G.update_values() @@ -48,4 +48,4 @@ SSair.RebootZAS() to_chat(usr, "\[5/5\] - ZAS Rebooted") - to_world("Atmosphere restart completed in [(world.timeofday - current_time)/10] seconds.") \ No newline at end of file + to_world("Atmosphere restart completed in [(world.timeofday - current_time)/10] seconds.") diff --git a/code/modules/admin/verbs/modify_robot.dm b/code/modules/admin/verbs/modify_robot.dm index e90e06bd27..68a7263fa2 100644 --- a/code/modules/admin/verbs/modify_robot.dm +++ b/code/modules/admin/verbs/modify_robot.dm @@ -6,14 +6,21 @@ if(!check_rights(R_ADMIN|R_FUN|R_VAREDIT|R_EVENT)) return - if(!istype(target) || !target.module) + if(!istype(target)) + return + + if(!target.module) + if(tgui_alert(usr, "This robot has not yet selected a module. Would you like to toggle combat module override?","Confirm",list("Yes","No"))!="Yes") + return + target.crisis_override = !target.crisis_override + to_chat(usr, "You [target.crisis_override? "enabled":"disabled"] [target]'s combat module overwrite.") return if(!target.module.modules) return var/list/modification_options = list(MODIFIY_ROBOT_MODULE_ADD,MODIFIY_ROBOT_MODULE_REMOVE, MODIFIY_ROBOT_APPLY_UPGRADE, MODIFIY_ROBOT_SUPP_ADD, MODIFIY_ROBOT_SUPP_REMOVE, MODIFIY_ROBOT_RADIOC_ADD, MODIFIY_ROBOT_RADIOC_REMOVE, - MODIFIY_ROBOT_COMP_ADD, MODIFIY_ROBOT_COMP_REMOVE, MODIFIY_ROBOT_SWAP_MODULE, MODIFIY_ROBOT_RESET_MODULE, MODIFIY_ROBOT_TOGGLE_ERT, MODIFIY_ROBOT_TOGGLE_CENT_ACCESS) + MODIFIY_ROBOT_COMP_ADD, MODIFIY_ROBOT_COMP_REMOVE, MODIFIY_ROBOT_SWAP_MODULE, MODIFIY_ROBOT_RESET_MODULE, MODIFIY_ROBOT_TOGGLE_ERT, MODIFIY_ROBOT_TOGGLE_STATION_ACCESS, MODIFIY_ROBOT_TOGGLE_CENT_ACCESS) while(TRUE) var/modification_choice = tgui_input_list(usr, "Select if you want to add or remove a module to/from [target]","Choice", modification_options) @@ -43,9 +50,10 @@ var/list/all_modules = robot.module.modules all_modules += robot.module.emag while(TRUE) - var/add_item = tgui_input_list(usr, "Please select the module to add", "Modules", all_modules) - if(!istype(add_item, /obj/item/)) + var/add_item_select = tgui_input_list(usr, "Please select the module to add", "Modules", all_modules) + if(!istype(add_item_select, /obj/item/)) break + var/obj/item/add_item = add_item_select robot.module.emag.Remove(add_item) robot.module.modules.Remove(add_item) robot.module.contents.Remove(add_item) @@ -117,7 +125,7 @@ if(!selected_module_upgrade || selected_module_upgrade == "Cancel") break if(selected_module_upgrade == "Reset Module") - if(tgui_alert(usr, "Are you sure that you want to install [selected_module_upgrade] and reset the robot's module?","Confirm",list("Yes","No"))=="No") + if(tgui_alert(usr, "Are you sure that you want to install [selected_module_upgrade] and reset the robot's module?","Confirm",list("Yes","No"))!="Yes") continue var/new_upgrade = upgrades[capitalize(selected_module_upgrade)] upgrades.Remove(selected_module_upgrade) @@ -293,23 +301,33 @@ target.hands.icon_state = target.get_hud_module_icon() target.hud_used.update_robot_modules_display() if(MODIFIY_ROBOT_RESET_MODULE) - if(tgui_alert(usr, "Are you sure that you want to reset the entire module?","Confirm",list("Yes","No"))=="No") + if(tgui_alert(usr, "Are you sure that you want to reset the entire module?","Confirm",list("Yes","No"))!="Yes") continue target.module_reset(FALSE) to_chat(usr, "You resetted [target]'s module selection.") if(MODIFIY_ROBOT_TOGGLE_ERT) target.crisis_override = !target.crisis_override to_chat(usr, "You [target.crisis_override? "enabled":"disabled"] [target]'s combat module overwrite.") - if(tgui_alert(usr, "Do you want to reset the module as well to allow selection?","Confirm",list("Yes","No"))=="No") + if(tgui_alert(usr, "Do you want to reset the module as well to allow selection?","Confirm",list("Yes","No"))!="Yes") continue target.module_reset(FALSE) + if(MODIFIY_ROBOT_TOGGLE_STATION_ACCESS) + if(target?.idcard?.access) + var/obj/item/weapon/card/id/synthetic/card = target.idcard + if(access_synth in card.access) + card.access -= get_all_station_access() + card.access -= access_synth + to_chat(usr, "You revoke station access from [target].") + else + card.access |= get_all_station_access() + card.access |= access_synth + to_chat(usr, "You grant station access to [target].") if(MODIFIY_ROBOT_TOGGLE_CENT_ACCESS) - for(var/obj in target.contents) - if(istype(obj, /obj/item/weapon/card/id/synthetic)) - var/obj/item/weapon/card/id/synthetic/card = obj - if(access_cent_specops in card.access) - card.access -= get_all_centcom_access() - to_chat(usr, "You revoke central access from [target].") - else - card.access += get_all_centcom_access() - to_chat(usr, "You grant central access to [target].") + if(target?.idcard?.access) + var/obj/item/weapon/card/id/synthetic/card = target.idcard + if(access_cent_specops in card.access) + card.access -= get_all_centcom_access() + to_chat(usr, "You revoke central access from [target].") + else + card.access |= get_all_centcom_access() + to_chat(usr, "You grant central access to [target].") diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index ab978f6fe9..ed593de684 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -272,19 +272,30 @@ //DISCONNECT// ////////////// /client/Del() + if(!gc_destroyed) + gc_destroyed = world.time + if (!QDELING(src)) + stack_trace("Client does not purport to be QDELING, this is going to cause bugs in other places!") + + GLOB.ahelp_tickets.ClientLogout(src) + GLOB.mhelp_tickets.ClientLogout(src) + + // Yes this is the same as what's found in qdel(). Yes it does need to be here + // Get off my back + SEND_SIGNAL(src, COMSIG_PARENT_QDELETING, TRUE) + Destroy() //Clean up signals and timers. + return ..() + +/client/Destroy() if(holder) holder.owner = null GLOB.admins -= src if (mentorholder) mentorholder.owner = null GLOB.mentors -= src - GLOB.ahelp_tickets.ClientLogout(src) - GLOB.mhelp_tickets.ClientLogout(src) GLOB.directory -= ckey GLOB.clients -= src - return ..() -/client/Destroy() ..() return QDEL_HINT_HARDDEL_NOW diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 084b3ff437..f7231db93d 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -444,7 +444,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O for(var/BP in mark_datum.body_parts) var/obj/item/organ/external/O = character.organs_by_name[BP] - if(O) + if(O && islist(O.markings) && islist(pref.body_markings[M]) && islist(pref.body_markings[M][BP])) O.markings[M] = list("color" = pref.body_markings[M][BP]["color"], "datum" = mark_datum, "priority" = priority, "on" = pref.body_markings[M][BP]["on"]) character.markings_len = priority diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index 221b89fcc2..4fa8cb5d9d 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -62,8 +62,8 @@ /datum/category_item/player_setup_item/player_global/ui/content(var/mob/user) . = "UI Style: [pref.UI_style]
" . += "Custom UI (recommended for White UI):
" - . += "-Color: [pref.UI_style_color]�[color_square(hex = pref.UI_style_color)]�reset
" - . += "-Alpha(transparency): [pref.UI_style_alpha]reset
" + . += "-Color: [pref.UI_style_color] [color_square(hex = pref.UI_style_color)] reset
" + . += "-Alpha(transparency): [pref.UI_style_alpha] reset
" . += "Tooltip Style: [pref.tooltipstyle]
" . += "Client FPS: [pref.client_fps]
" . += "Random Ambience Frequency: [pref.ambience_freq]
" diff --git a/code/modules/client/preference_setup/global/02_settings.dm b/code/modules/client/preference_setup/global/02_settings.dm index fcdb85b843..71ff5d0a09 100644 --- a/code/modules/client/preference_setup/global/02_settings.dm +++ b/code/modules/client/preference_setup/global/02_settings.dm @@ -91,7 +91,7 @@ preference = list(preference) for(var/p in preference) var/datum/client_preference/cp = get_client_preference(p) - if(!prefs || !cp || !(cp.key in prefs.preferences_enabled)) + if(!prefs || !cp || !istype(cp, /datum/client_preference) || !(cp.key in prefs.preferences_enabled)) return FALSE return TRUE diff --git a/code/modules/client/preference_setup/loadout/gear_tweaks.dm b/code/modules/client/preference_setup/loadout/gear_tweaks.dm index ed5306412b..f371fac6c1 100644 --- a/code/modules/client/preference_setup/loadout/gear_tweaks.dm +++ b/code/modules/client/preference_setup/loadout/gear_tweaks.dm @@ -40,7 +40,48 @@ /datum/gear_tweak/color/tweak_item(var/obj/item/I, var/metadata) if(valid_colors && !(metadata in valid_colors)) return - I.color = metadata + if(!metadata || (metadata == "#ffffff")) + return + if(istype(I)) + I.add_atom_colour(metadata, FIXED_COLOUR_PRIORITY) + else + I.color = metadata + +GLOBAL_DATUM_INIT(gear_tweak_free_matrix_recolor, /datum/gear_tweak/matrix_recolor, new) + +/datum/gear_tweak/matrix_recolor + +/datum/gear_tweak/matrix_recolor/get_contents(var/metadata) + if(islist(metadata) && length(metadata)) + return "Matrix Recolor: [english_list(metadata)]" + return "Matrix Recolor" + +/datum/gear_tweak/matrix_recolor/get_default() + return null + +/datum/gear_tweak/matrix_recolor/get_metadata(user, metadata) + var/list/returned = color_matrix_picker(user, "Pick a color matrix for this item", "Matrix Recolor", "Ok", "Erase", "Cancel", TRUE, 10 MINUTES, islist(metadata) && metadata) + var/list/L = returned["matrix"] + if(returned["button"] == 3) + return metadata + if((returned["button"] == 2) || !islist(L) || !ISINRANGE(L.len, 9, 20)) + return list() + var/identity = TRUE + var/static/list/ones = list(1, 5, 9) + for(var/i in 1 to L.len) + if(L[i] != ((i in ones)? 1 : 0)) + identity = FALSE + break + return identity? list() : L + +/datum/gear_tweak/matrix_recolor/tweak_item(obj/item/I, metadata) + . = ..() + if(!islist(metadata) || (length(metadata) < 12)) + return + if(istype(I)) + I.add_atom_colour(metadata, FIXED_COLOUR_PRIORITY) + else + I.color = metadata /* * Path adjustment diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories.dm b/code/modules/client/preference_setup/loadout/loadout_accessories.dm index 8c68399737..9ea615df08 100644 --- a/code/modules/client/preference_setup/loadout/loadout_accessories.dm +++ b/code/modules/client/preference_setup/loadout/loadout_accessories.dm @@ -349,3 +349,27 @@ /datum/gear/accessory/pressbadge display_name = "freelance press pass" path = /obj/item/clothing/accessory/badge/press/independent + +/datum/gear/accessory/wristband + display_name = "wristband (recolourable)" + path = /obj/item/clothing/accessory/wristband + +/datum/gear/accessory/wristband/New() + ..() + gear_tweaks += gear_tweak_free_color_choice + +/datum/gear/accessory/wristband_collection + display_name = "wristbands (selection)" + path = /obj/item/clothing/accessory/wristbandcollection + +/datum/gear/accessory/wristband_collection/New() + ..() + var/list/wristband_lists = list( + "Green, Blue and Yellow" = /obj/item/clothing/accessory/wristbandcollection, + "Pink, Black and Red" = /obj/item/clothing/accessory/wristbandcollection/pink, + "Red and Orange" = /obj/item/clothing/accessory/wristbandcollection/les, + "White, Pink and Blue" = /obj/item/clothing/accessory/wristbandcollection/trans, + "Blue, Purple and Pink" = /obj/item/clothing/accessory/wristbandcollection/bi, + "Black, White and Grey" = /obj/item/clothing/accessory/wristbandcollection/ace + ) + gear_tweaks += new/datum/gear_tweak/path(wristband_lists) diff --git a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm index e2d5714051..1720d50315 100644 --- a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm @@ -1321,6 +1321,13 @@ character_name = list("Bradley Khatibi") // V CKEYS + +/datum/gear/fluff/valkhorns + path = /obj/item/clothing/ears/earring/fluff/valkhorns + display_name = "Valkaerie's Horns" + ckeywhitelist = list("valkaerie") + character_name = list("Valkaerie Stoze") + /datum/gear/fluff/verie_suit path = /obj/item/clothing/under/fluff/verie display_name = "Verie's Salacious Suit" diff --git a/code/modules/client/preference_setup/preference_setup.dm b/code/modules/client/preference_setup/preference_setup.dm index 17ee5ed672..5a8e31c324 100644 --- a/code/modules/client/preference_setup/preference_setup.dm +++ b/code/modules/client/preference_setup/preference_setup.dm @@ -304,3 +304,7 @@ if(PREF_FBP_SOFTWARE) return 150 return S.max_age // welp + +#undef PREF_FBP_CYBORG +#undef PREF_FBP_POSI +#undef PREF_FBP_SOFTWARE diff --git a/code/modules/client/preference_setup/vore/09_misc.dm b/code/modules/client/preference_setup/vore/09_misc.dm index 19fd61011a..bde733d4c8 100644 --- a/code/modules/client/preference_setup/vore/09_misc.dm +++ b/code/modules/client/preference_setup/vore/09_misc.dm @@ -5,20 +5,22 @@ /datum/category_item/player_setup_item/vore/misc/load_character(var/savefile/S) S["show_in_directory"] >> pref.show_in_directory S["directory_tag"] >> pref.directory_tag - S["directory_erptag"] >> pref.directory_erptag + S["directory_erptag"] >> pref.directory_erptag S["directory_ad"] >> pref.directory_ad S["sensorpref"] >> pref.sensorpref S["capture_crystal"] >> pref.capture_crystal S["auto_backup_implant"] >> pref.auto_backup_implant + S["borg_petting"] >> pref.borg_petting /datum/category_item/player_setup_item/vore/misc/save_character(var/savefile/S) S["show_in_directory"] << pref.show_in_directory S["directory_tag"] << pref.directory_tag - S["directory_erptag"] << pref.directory_erptag + S["directory_erptag"] << pref.directory_erptag S["directory_ad"] << pref.directory_ad S["sensorpref"] << pref.sensorpref S["capture_crystal"] << pref.capture_crystal S["auto_backup_implant"] << pref.auto_backup_implant + S["borg_petting"] << pref.borg_petting /datum/category_item/player_setup_item/vore/misc/copy_to_mob(var/mob/living/carbon/human/character) if(pref.sensorpref > 5 || pref.sensorpref < 1) @@ -29,10 +31,11 @@ /datum/category_item/player_setup_item/vore/misc/sanitize_character() pref.show_in_directory = sanitize_integer(pref.show_in_directory, 0, 1, initial(pref.show_in_directory)) pref.directory_tag = sanitize_inlist(pref.directory_tag, GLOB.char_directory_tags, initial(pref.directory_tag)) - pref.directory_erptag = sanitize_inlist(pref.directory_erptag, GLOB.char_directory_erptags, initial(pref.directory_erptag)) + pref.directory_erptag = sanitize_inlist(pref.directory_erptag, GLOB.char_directory_erptags, initial(pref.directory_erptag)) pref.sensorpref = sanitize_integer(pref.sensorpref, 1, sensorpreflist.len, initial(pref.sensorpref)) pref.capture_crystal = sanitize_integer(pref.capture_crystal, 0, 1, initial(pref.capture_crystal)) - pref.auto_backup_implant = sanitize_integer(pref.auto_backup_implant, 0, 1, initial(pref.auto_backup_implant)) + pref.auto_backup_implant = sanitize_integer(pref.auto_backup_implant, 0, 1, initial(pref.auto_backup_implant)) + pref.borg_petting = sanitize_integer(pref.borg_petting, 0, 1, initial(pref.borg_petting)) /datum/category_item/player_setup_item/vore/misc/content(var/mob/user) . += "
" @@ -43,6 +46,7 @@ . += "Suit Sensors Preference: [sensorpreflist[pref.sensorpref]]
" . += "Capture Crystal Preference: [pref.capture_crystal ? "Yes" : "No"]
" . += "Spawn With Backup Implant: [pref.auto_backup_implant ? "Yes" : "No"]
" + . += "Allow petting as robot: [pref.borg_petting ? "Yes" : "No"]
" /datum/category_item/player_setup_item/vore/misc/OnTopic(var/href, var/list/href_list, var/mob/user) if(href_list["toggle_show_in_directory"]) @@ -77,4 +81,7 @@ else if(href_list["toggle_implant"]) pref.auto_backup_implant = pref.auto_backup_implant ? 0 : 1; return TOPIC_REFRESH + else if(href_list["toggle_borg_petting"]) + pref.borg_petting = pref.borg_petting ? 0 : 1; + return TOPIC_REFRESH return ..(); diff --git a/code/modules/client/preferences_vr.dm b/code/modules/client/preferences_vr.dm index 99e8103509..79079354e4 100644 --- a/code/modules/client/preferences_vr.dm +++ b/code/modules/client/preferences_vr.dm @@ -6,6 +6,7 @@ var/sensorpref = 5 //Set character's suit sensor level var/capture_crystal = 1 //Whether or not someone is able to be caught with capture crystals var/auto_backup_implant = FALSE //Whether someone starts with a backup implant or not. + var/borg_petting = TRUE //Whether someone can be petted as a borg or not. var/job_talon_high = 0 var/job_talon_med = 0 diff --git a/code/modules/client/verbs/ignore.dm b/code/modules/client/verbs/ignore.dm index dab46f3a60..416aa24606 100644 --- a/code/modules/client/verbs/ignore.dm +++ b/code/modules/client/verbs/ignore.dm @@ -18,21 +18,29 @@ SScharacter_setup.queue_preferences_save(prefs) to_chat(usr, "Now ignoring [key_to_ignore].") -/client/verb/unignore(key_to_unignore as text) +/client/verb/unignore() set name = "Unignore" set category = "OOC" set desc = "Reverts your ignoring of a specific player." + if(!prefs) + to_chat(usr, "Preferences not found.") + return + + if(!prefs.ignored_players?.len) + to_chat(usr, "You aren't ignoring any players.") + return + + var/key_to_unignore = tgui_input_list(usr, "Ignored players", "Unignore", prefs.ignored_players) if(!key_to_unignore) return key_to_unignore = ckey(sanitize(key_to_unignore)) - if(prefs && prefs.ignored_players) - if(!(key_to_unignore in prefs.ignored_players)) - to_chat(usr, "[key_to_unignore] isn't being ignored.") - return - prefs.ignored_players -= key_to_unignore - SScharacter_setup.queue_preferences_save(prefs) - to_chat(usr, "Reverted ignore on [key_to_unignore].") + if(!(key_to_unignore in prefs.ignored_players)) + to_chat(usr, "[key_to_unignore] isn't being ignored.") + return + prefs.ignored_players -= key_to_unignore + SScharacter_setup.queue_preferences_save(prefs) + to_chat(usr, "Reverted ignore on [key_to_unignore].") /mob/proc/is_key_ignored(var/key_to_check) if(client) @@ -45,4 +53,4 @@ if(GLOB.directory[key_to_check] in GLOB.admins) // This is here so this is only evaluated if someone is actually being blocked. return 0 return 1 - return 0 \ No newline at end of file + return 0 diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index a69eee2e7f..0e52b8674c 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -264,7 +264,7 @@ /obj/item/clothing/mask/chameleon name = "gas mask" - icon_state = "gas_alt" + icon_state = "gas_old" desc = "It looks like a plain gask mask, but on closer inspection, it seems to have a small dial inside." origin_tech = list(TECH_ILLEGAL = 3) var/global/list/clothing_choices @@ -277,7 +277,7 @@ /obj/item/clothing/mask/chameleon/emp_act(severity) //Because we don't have psych for all slots right now but still want a downside to EMP. In this case your cover's blown. name = "gas mask" desc = "It's a gas mask." - icon_state = "gas_alt" + icon_state = "gas_old" update_icon() update_clothing_icon() diff --git a/code/modules/clothing/spacesuits/rig/modules/modules.dm b/code/modules/clothing/spacesuits/rig/modules/modules.dm index f8821c89d0..7b27dfb9ac 100644 --- a/code/modules/clothing/spacesuits/rig/modules/modules.dm +++ b/code/modules/clothing/spacesuits/rig/modules/modules.dm @@ -135,6 +135,11 @@ stat_modules += new/stat_rig_module/select(src) stat_modules += new/stat_rig_module/charge(src) +/obj/item/rig_module/Destroy(force, ...) + QDEL_NULL_LIST(stat_modules) + . = ..() + + // Called when the module is installed into a suit. /obj/item/rig_module/proc/installed(var/obj/item/weapon/rig/new_holder) holder = new_holder @@ -260,6 +265,10 @@ ..() src.module = module +/stat_rig_module/Destroy() + module = null + . = ..() + /stat_rig_module/proc/AddHref(var/list/href_list) return diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index 081f08ed49..742a10ffab 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -159,11 +159,17 @@ update_icon(1) /obj/item/weapon/rig/Destroy() - for(var/obj/item/piece in list(gloves,boots,helmet,chest)) + for(var/obj/item/piece in list(gloves,boots,helmet,chest,cell,air_supply)) var/mob/living/M = piece.loc if(istype(M)) M.drop_from_inventory(piece) qdel(piece) + gloves = null + boots = null + helmet = null + chest = null + cell = null + air_supply = null STOP_PROCESSING(SSobj, src) qdel(wires) wires = null diff --git a/code/modules/clothing/spacesuits/void/military_vr.dm b/code/modules/clothing/spacesuits/void/military_vr.dm index 8eb7ff9ebd..88701c2da7 100644 --- a/code/modules/clothing/spacesuits/void/military_vr.dm +++ b/code/modules/clothing/spacesuits/void/military_vr.dm @@ -40,7 +40,7 @@ name = "\improper Ace's ODST Helmet" icon = 'icons/obj/custom_items.dmi' icon_state = "rig0-odst_ace" - desc = "Etched under the inner visor is the phrase \"Don�t let your past define you; let it mold you into the person you want to be.\"" + desc = "Etched under the inner visor is the phrase \"Don't let your past define you; let it mold you into the person you want to be.\"" icon = 'icons/inventory/head/item_vr.dmi' icon_override = 'icons/inventory/head/mob_vr.dmi'*/ @@ -55,4 +55,3 @@ species_restricted = null breach_threshold = 16 //Extra Thicc resilience = 0.05 //Military Armor - diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm index 679e129829..96cc4afca1 100644 --- a/code/modules/clothing/under/accessories/accessory.dm +++ b/code/modules/clothing/under/accessories/accessory.dm @@ -488,6 +488,70 @@ icon_state = "sash" slot = ACCESSORY_SLOT_OVER +/obj/item/clothing/accessory/wristband + name = "wristband" + desc = "A simple plastic wristband." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection/pink + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection2" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection/les + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection3" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection/trans + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection4" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection/bi + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection5" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/wristbandcollection/ace + name = "wristband collection" + desc = "A mix of colourable plastic wristbands." + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "wristband_collection6" + w_class = ITEMSIZE_TINY + slot_flags = SLOT_TIE + slot = ACCESSORY_SLOT_DECOR + + //Gaiter scarves /obj/item/clothing/accessory/gaiter name = "red neck gaiter" diff --git a/code/modules/economy/Events_Mundane.dm b/code/modules/economy/Events_Mundane.dm index 29f1457fd2..6703133462 100644 --- a/code/modules/economy/Events_Mundane.dm +++ b/code/modules/economy/Events_Mundane.dm @@ -15,7 +15,7 @@ "Pens proclaim pencils obsolete, 'lead is dead'.",\ "Rock and paper sues scissors for discrimination.",\ "Steak tell-all book reveals he never liked sitting by potato.",\ - "Woodchuck stops counting how many times he�s chucked 'Never again'.",\ + "Woodchuck stops counting how many times he's chucked 'Never again'.",\ "[affected_dest.name] clerk first person able to pronounce '@*$%!'.",\ "[affected_dest.name] delis serving boiled paperback dictionaries, 'Adjectives chewy' customers declare.",\ "[affected_dest.name] weather deemed 'boring'; meteors and rad storms to be imported.",\ @@ -31,8 +31,8 @@ "Skrell marries computer; wedding attended by 100 modems.",\ "Chef reports successfully using harmonica as cheese grater.",\ "[using_map.company_name] invents handkerchief that says 'Bless you' after sneeze.",\ - "Clone accused of posing for other clones�s school photo.",\ - "Clone accused of stealing other clones�s employee of the month award.",\ + "Clone accused of posing for other clones's school photo.",\ + "Clone accused of stealing other clones's employee of the month award.",\ "Woman robs station with hair dryer; crewmen love new style.",\ "This space for rent.",\ "[affected_dest.name] Baker Wins Pickled Crumpet Toss Three Years Running",\ diff --git a/code/modules/economy/vending_machines_vr.dm b/code/modules/economy/vending_machines_vr.dm index 8ea556781a..d5cde9b76c 100644 --- a/code/modules/economy/vending_machines_vr.dm +++ b/code/modules/economy/vending_machines_vr.dm @@ -599,6 +599,13 @@ /obj/item/clothing/accessory/wcoat/swvest = 5, /obj/item/clothing/accessory/wcoat/swvest/blue = 5, /obj/item/clothing/accessory/wcoat/swvest/red = 5, + /obj/item/clothing/accessory/wristband = 5, + /obj/item/clothing/accessory/wristbandcollection = 5, + /obj/item/clothing/accessory/wristbandcollection/pink = 5, + /obj/item/clothing/accessory/wristbandcollection/les = 5, + /obj/item/clothing/accessory/wristbandcollection/bi = 5, + /obj/item/clothing/accessory/wristbandcollection/trans = 5, + /obj/item/clothing/accessory/wristbandcollection/ace = 5, /obj/item/weapon/storage/wallet = 5, /obj/item/weapon/storage/wallet/poly = 5, /obj/item/weapon/storage/wallet/womens = 5, @@ -728,6 +735,13 @@ /obj/item/clothing/accessory/wcoat/swvest = 50, /obj/item/clothing/accessory/wcoat/swvest/blue = 50, /obj/item/clothing/accessory/wcoat/swvest/red = 50, + /obj/item/clothing/accessory/wristband = 10, + /obj/item/clothing/accessory/wristbandcollection = 30, + /obj/item/clothing/accessory/wristbandcollection/pink = 30, + /obj/item/clothing/accessory/wristbandcollection/les = 30, + /obj/item/clothing/accessory/wristbandcollection/bi = 30, + /obj/item/clothing/accessory/wristbandcollection/trans = 30, + /obj/item/clothing/accessory/wristbandcollection/ace = 30, /obj/item/weapon/storage/wallet = 50, /obj/item/weapon/storage/wallet/poly = 50, /obj/item/weapon/storage/wallet/womens = 50, diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm index 34dc9e5c92..642af69152 100644 --- a/code/modules/emotes/emote_mob.dm +++ b/code/modules/emotes/emote_mob.dm @@ -222,6 +222,8 @@ if(M) if(isobserver(M)) message = "[src] ([ghost_follow_link(src, M)]) [input]" + if(usr && usr.client && M && !(get_z(usr) == get_z(M))) + message = "[message]" M.show_message(message, m_type) M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), (m_type == AUDIBLE_MESSAGE)) diff --git a/code/modules/events/money_spam.dm b/code/modules/events/money_spam.dm index ad8a1bbc3f..e83a3601a6 100644 --- a/code/modules/events/money_spam.dm +++ b/code/modules/events/money_spam.dm @@ -38,7 +38,7 @@ for(var/obj/item/device/pda/check_pda in sortAtom(PDAs)) if (!check_pda.owner || check_pda == src || check_pda.hidden) continue - + var/datum/data/pda/app/messenger/M = check_pda.find_program(/datum/data/pda/app/messenger) if(!M || M.toff) continue @@ -69,7 +69,7 @@ message = pick("Luxury watches for Blowout sale prices!",\ "Watches, Jewelry & Accessories, Bags & Wallets !",\ "Deposit 100$ and get 300$ totally free!",\ - " 100K NT.|WOWGOLD �nly $89 ",\ + " 100K NT.|WOWGOLD Only $89 ",\ "We have been filed with a complaint from one of your customers in respect of their business relations with you.",\ "We kindly ask you to open the COMPLAINT REPORT (attached) to reply on this complaint..") if(4) diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm index a5703007bf..c58bed83e3 100644 --- a/code/modules/food/kitchen/cooking_machines/_appliance.dm +++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm @@ -41,6 +41,7 @@ var/container_type = null var/combine_first = FALSE // If TRUE, this appliance will do combination cooking before checking recipes + var/food_safety = FALSE //RS ADD - If true, the appliance automatically ejects food instead of burning it /obj/machinery/appliance/Initialize() . = ..() @@ -354,7 +355,7 @@ //Rescaling cooking work to avoid insanely long times for large things var/buffer = CI.max_cookwork CI.max_cookwork = 0 - var/multiplier = 1 + var/multiplier = 0.35 var/step = 4 while (buffer > step) buffer -= step @@ -404,7 +405,10 @@ finish_cooking(CI) else if (!CI.burned && CI.cookwork > min(CI.max_cookwork * CI.overcook_mult, CI.max_cookwork + 30)) - burn_food(CI) + if(!food_safety) + burn_food(CI) + else + eject(CI, null) // Gotta hurt. for(var/obj/item/weapon/holder/H in CI.container.contents) @@ -644,7 +648,15 @@ delete = 0 else//If the container is empty OR contains more than one thing, then we must extract the container thing = CI.container - if (!user || !user.put_in_hands(thing)) + + if (user) + if(!user.put_in_hands(thing)) + thing.forceMove(get_turf(src)) + else if(istype(thing, /obj/item/weapon/reagent_containers/cooking_container)) + var/obj/item/weapon/reagent_containers/cooking_container/cc = thing + cc.do_empty() + delete = 0 + else thing.forceMove(get_turf(src)) if (delete) @@ -652,7 +664,13 @@ qdel(CI) else CI.reset()//reset instead of deleting if the container is left inside - user.visible_message("\The [user] remove \the [thing] from \the [src].") + + if(user) + user.visible_message("\The [user] remove \the [thing] from \the [src].") + else + src.visible_message("\The [src] pings as it automatically ejects its contents!") + if(cooked_sound) + playsound(get_turf(src), cooked_sound, 50, 1) /obj/machinery/appliance/proc/cook_mob(var/mob/living/victim, var/mob/user) return @@ -782,3 +800,16 @@ heating_power = initial(heating_power) + cap_rating * 25 cooking_power = cooking_coeff * (1 + (scan_rating + cap_rating) / 20) // 100% eff. becomes 120%, 140%, 160% w/ better parts, thus rewarding upgrading the appliances during your shift. // to_world("RefreshParts returned cooking power of [cooking_power] during this step.") // Debug lines, uncomment if you need to test. + + +/obj/machinery/appliance/verb/toggle_safety() + set name = "Toggle Safety" + set desc = "Toggles whether the appliance automatically ejects food when it starts to burn." + set category = "Object" + set src in view(1) + + if(!isliving(usr)) + return + + food_safety = !food_safety + to_chat(usr, "You flip \the [src]'s safe mode switch. Safe mode is now [food_safety ? "on" : "off"].") \ No newline at end of file diff --git a/code/modules/food/kitchen/cooking_machines/_cooker.dm b/code/modules/food/kitchen/cooking_machines/_cooker.dm index e02d6dd477..3bae9108ff 100644 --- a/code/modules/food/kitchen/cooking_machines/_cooker.dm +++ b/code/modules/food/kitchen/cooking_machines/_cooker.dm @@ -5,7 +5,7 @@ var/optimal_power = 0.6 //cooking power at 100% - This variable determines the MAXIMUM increase in do_cooking_ticks, once math goes through. If you want ticks of 0.5, set it to 0.5, etc. var/loss = 1 //Temp lost per proc when equalising - var/resistance = 32000 //Resistance to heating. combines with heating power to determine how long heating takes. 32k by default. + var/resistance = 1200 //Resistance to heating. combines with heating power to determine how long heating takes. 32k by default. var/light_x = 0 var/light_y = 0 diff --git a/code/modules/food/kitchen/cooking_machines/fryer.dm b/code/modules/food/kitchen/cooking_machines/fryer.dm index 5746e18c4c..d2ffc9a073 100644 --- a/code/modules/food/kitchen/cooking_machines/fryer.dm +++ b/code/modules/food/kitchen/cooking_machines/fryer.dm @@ -24,7 +24,7 @@ // Power used to maintain temperature once it's heated. // Going with 25% of the active power. This is a somewhat arbitrary value. - resistance = 10 KILOWATTS // Approx. 10 minutes to heat up. + resistance = 2 KILOWATTS // Approx. 2 minutes to heat up. max_contents = 2 container_type = /obj/item/weapon/reagent_containers/cooking_container/fryer diff --git a/code/modules/food/kitchen/cooking_machines/grill.dm b/code/modules/food/kitchen/cooking_machines/grill.dm index d7e1829092..01d0ba8c5f 100644 --- a/code/modules/food/kitchen/cooking_machines/grill.dm +++ b/code/modules/food/kitchen/cooking_machines/grill.dm @@ -21,7 +21,7 @@ // Grill is faster to heat and setup than the rest. optimal_temp = 120 + T0C min_temp = 60 + T0C - resistance = 8 KILOWATTS // Very fast to heat up. + resistance = 2 KILOWATTS // Very fast to heat up. max_contents = 3 // Arbitrary number, 3 grill 'racks' container_type = /obj/item/weapon/reagent_containers/cooking_container/grill diff --git a/code/modules/food/kitchen/cooking_machines/oven.dm b/code/modules/food/kitchen/cooking_machines/oven.dm index a34f7e3583..ca99a28541 100644 --- a/code/modules/food/kitchen/cooking_machines/oven.dm +++ b/code/modules/food/kitchen/cooking_machines/oven.dm @@ -13,7 +13,7 @@ heating_power = 6 KILOWATTS //Based on a double deck electric convection oven - resistance = 12 KILOWATTS // Approx. 12 minutes to heat up. + resistance = 2 KILOWATTS // Approx. 2 minutes to heat up. idle_power_usage = 2 KILOWATTS //uses ~30% power to stay warm optimal_power = 0.8 // Oven cooks .2 faster than the default speed. diff --git a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm index 212087abbc..e6fe5e8c29 100644 --- a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm +++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm @@ -37,7 +37,7 @@ for(var/obj/item/device/pda/check_pda in sortAtom(PDAs)) if (!check_pda.owner || check_pda == src || check_pda.hidden) continue - + var/datum/data/pda/app/messenger/M = check_pda.find_program(/datum/data/pda/app/messenger) if(!M || M.toff) continue @@ -97,7 +97,7 @@ message = pick("Luxury watches for Blowout sale prices!",\ "Watches, Jewelry & Accessories, Bags & Wallets !",\ "Deposit 100$ and get 300$ totally free!",\ - " 100K NT.|WOWGOLD �nly $89 ",\ + " 100K NT.|WOWGOLD Only $89 ",\ "We have been filed with a complaint from one of your customers in respect of their business relations with you.",\ "We kindly ask you to open the COMPLAINT REPORT (attached) to reply on this complaint..") if(4) diff --git a/code/modules/integrated_electronics/core/tools.dm b/code/modules/integrated_electronics/core/tools.dm index 75b6e85fb4..d5d01b38c4 100644 --- a/code/modules/integrated_electronics/core/tools.dm +++ b/code/modules/integrated_electronics/core/tools.dm @@ -33,7 +33,7 @@ to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") return if(io.io_type != selected_io.io_type) - to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ + to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ while the second is a [io.io_type].") return if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) @@ -106,7 +106,7 @@ /obj/item/device/integrated_electronics/debugger name = "circuit debugger" desc = "This small tool allows one working with custom machinery to directly set data to a specific pin, useful for writing \ - settings to specific circuits, or for debugging purposes. It can also pulse activation pins." + settings to specific circuits, or for debugging purposes. It can also pulse activation pins." icon = 'icons/obj/integrated_electronics/electronic_tools.dmi' icon_state = "debugger" w_class = 2 @@ -129,13 +129,13 @@ to_chat(user, "You set \the [src]'s memory to \"[new_data]\".") if("number") accepting_refs = 0 - new_data = tgui_input_number(usr, "Now type in a number.","[src] number writing") + new_data = tgui_input_number(usr, "Now type in a number.","[src] number writing", min_value=-INFINITY, round_value=FALSE) if(isnum(new_data) && CanInteract(user, GLOB.tgui_physical_state)) data_to_write = new_data to_chat(user, "You set \the [src]'s memory to [new_data].") if("ref") accepting_refs = 1 - to_chat(user, "You turn \the [src]'s ref scanner on. Slide it across \ + to_chat(user, "You turn \the [src]'s ref scanner on. Slide it across \ an object for a ref of that object to save it in memory.") if("null") data_to_write = null @@ -145,7 +145,7 @@ if(accepting_refs && proximity) data_to_write = WEAKREF(target) visible_message("[user] slides \a [src]'s over \the [target].") - to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ + to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ now off.") accepting_refs = 0 @@ -206,7 +206,7 @@ to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") return if(io.io_type != selected_io.io_type) - to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ + to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ while the second is a [io.io_type].") return if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) @@ -246,7 +246,7 @@ if(accepting_refs && toolmode == MULTITOOL_MODE_INTCIRCUITS && proximity) weakref_wiring = WEAKREF(target) visible_message("[user] slides \a [src]'s over \the [target].") - to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ + to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ now off.") accepting_refs = 0 diff --git a/code/modules/mentor/mentor.dm b/code/modules/mentor/mentor.dm index 28f4225485..e28202745e 100644 --- a/code/modules/mentor/mentor.dm +++ b/code/modules/mentor/mentor.dm @@ -150,7 +150,7 @@ var/list/mentor_verbs_default = list( if(MH) message_mentors("[src] has started replying to [C]'s mentor help.") - var/msg = tgui_input_text(src,"Message:", "Private message to [C]") + var/msg = tgui_input_text(src,"Message:", "Private message to [C]", multiline = TRUE) if (!msg) message_mentors("[src] has cancelled their reply to [C]'s mentor help.") return @@ -168,7 +168,7 @@ var/list/mentor_verbs_default = list( set hidden = 1 var/mhelp = tgui_alert(usr, "Select the help you need.","Request for Help",list("Adminhelp","Mentorhelp")) == "Mentorhelp" - var/msg = tgui_input_text(usr, "Input your request for help.", "Request for Help") + var/msg = tgui_input_text(usr, "Input your request for help.", "Request for Help", multiline = TRUE) if (mhelp) mentorhelp(msg) @@ -202,7 +202,7 @@ var/list/mentor_verbs_default = list( //get message text, limit it's length.and clean/escape html if(!msg) - msg = tgui_input_text(src,"Message:", "Mentor-PM to [whom]") + msg = tgui_input_text(src,"Message:", "Mentor-PM to [whom]", multiline = TRUE) if(!msg) return @@ -260,4 +260,4 @@ var/list/mentor_verbs_default = list( to_chat(C, interaction_message) for(var/client/C in GLOB.admins) if (C != recipient && C != src) - to_chat(C, interaction_message) \ No newline at end of file + to_chat(C, interaction_message) diff --git a/code/modules/mentor/mentorhelp.dm b/code/modules/mentor/mentorhelp.dm index 8a53d8d398..5b23a244af 100644 --- a/code/modules/mentor/mentorhelp.dm +++ b/code/modules/mentor/mentorhelp.dm @@ -429,6 +429,25 @@ GLOBAL_DATUM_INIT(mhelp_tickets, /datum/mentor_help_tickets, new) if(!msg) return + // Making sure there's actually a mentor or admin who can respond. + var/list/admins = get_admin_counts() + var/list/activeAdmins = admins["present"] + var/list/mentors = GLOB.mentors + if(!mentors.len && !activeAdmins.len) + var/choice = tgui_alert(usr, "There are no active admins or mentors online. Would you like to make an ahelp instead, so that staff is notified of your issue? \ + Alternatively, you may go to the discord yourself and repeat your question in #cadet-academy. Please note, if choosing the later, do not include current-round information.", + "Send to discord?", list("Admin-help!", "Still mentorhelp!", "Cancel")) + if(choice == "Admin-help!") + usr.client.adminhelp(msg) + src.verbs -= /client/verb/mentorhelp + spawn(1200) + src.verbs += /client/verb/mentorhelp // 2 minute cd to prevent abusing this to spam admins. + return + else if(choice == "Cancel") + return + + + //remove out adminhelp verb temporarily to prevent spamming of admins. src.verbs -= /client/verb/mentorhelp spawn(600) diff --git a/code/modules/mining/resonator_vr.dm b/code/modules/mining/resonator_vr.dm index d3c3246a24..796a2a12fd 100644 --- a/code/modules/mining/resonator_vr.dm +++ b/code/modules/mining/resonator_vr.dm @@ -18,6 +18,8 @@ var/fieldsactive = 0 var/burst_time = 50 var/fieldlimit = 3 + var/spreadmode = 0 + var/cascading = 0 /obj/item/resonator/upgraded name = "upgraded resonator" @@ -30,20 +32,75 @@ var/turf/T = get_turf(target) if(locate(/obj/effect/resonance) in T) return - if(fieldsactive < fieldlimit) + + if(fieldsactive > fieldlimit || cascading) + to_chat(creator, span_warning("You've exceeded the field limit! Wait for them to dissipate.")) + return + if(spreadmode) + //log_debug("Starting cascade...") + cascading = TRUE + var/depth = 0 + var/fields = 0 + if(depth == 0) + //log_debug("Creating resonance at the target spot.") + playsound(src,'sound/weapons/resonator_fire.ogg',50,1) + new /obj/effect/resonance(T, WEAKREF(creator), burst_time) + fields++ + depth++ + var/origin_dir = get_cardinal_dir(creator, T) + var/dir + while(fields < fieldlimit) + for(var/i=0, i<=2, i++) + //log_debug("Fields are [fields], doing [i] iteration of subloop") + if(fields >= fieldlimit) + //log_debug("[fields] exceeded fieldlimit, exiting procedure.") + sleep(burst_time) + cascading = FALSE + return + switch(i) //Using a switch statement rather than (-90 + i * 90) to favour going straight ahead + if(0) + dir = origin_dir + if(1) + dir = turn(origin_dir, 90) + if(2) + dir = turn(origin_dir, -90) + var/turf/newT = T + for(var/step = 1, step<=depth, step++) + //log_debug("Taking a step until [depth] steps taken.") + var/turf/oldT = newT + newT = get_step(oldT, dir) + if(step == depth) + new /obj/effect/resonance(newT, WEAKREF(creator), burst_time) + fields++ + if(depth > 1 && fields < fieldlimit) //Works until 15 fieldlimit. + oldT = newT + dir = turn(dir, (i == 2 ? 135 : -135)) + newT = get_step(oldT, dir) + new /obj/effect/resonance(newT, WEAKREF(creator), burst_time) + fields++ + depth++ + + + + else playsound(src,'sound/weapons/resonator_fire.ogg',50,1) - new /obj/effect/resonance(T, creator, burst_time) + new /obj/effect/resonance(T, WEAKREF(creator), burst_time) fieldsactive++ spawn(burst_time) fieldsactive-- /obj/item/resonator/attack_self(mob/user) - if(burst_time == 50) - burst_time = 30 - to_chat(user, "You set the resonator's fields to detonate after 3 seconds.") - else - burst_time = 50 - to_chat(user, "You set the resonator's fields to detonate after 5 seconds.") + switch(tgui_alert(user, "Change Detonation Time or toggle Cascading?","Setting", list("Toggle Cascade", "Resonance Time"))) + if("Resonance Time") + if(burst_time == 50) + burst_time = 30 + to_chat(user, "You set the resonator's fields to detonate after 3 seconds.") + else + burst_time = 50 + to_chat(user, "You set the resonator's fields to detonate after 5 seconds.") + if("Toggle Cascade") + spreadmode = !spreadmode + to_chat(user, span_info("You have [(spreadmode ? "enabled" : "disabled")] the resonance cascade mode.")) /obj/item/resonator/afterattack(atom/target, mob/user, proximity_flag) if(proximity_flag) @@ -61,6 +118,7 @@ mouse_opacity = 0 var/resonance_damage = 20 + /obj/effect/resonance/Initialize(mapload, var/creator = null, var/timetoburst) . = ..() // Start small and grow to big size as we are about to burst @@ -90,7 +148,7 @@ if(environment.temperature < 250) name = "strong resonance field" resonance_damage = 50 - + for(var/mob/living/L in src.loc) if(creator) add_attack_logs(creator, L, "used a resonator field on") diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 202d4c6581..1032da0cf7 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -145,6 +145,8 @@ message = "[message]" if(speaker && !speaker.client) message = "[message]" + else if(speaker && !(get_z(src) == get_z(speaker))) + message = "[message]" to_chat(src, message) else if(teleop) to_chat(teleop, "[create_text_tag("body", "BODY:", teleop.client)][message]") @@ -159,6 +161,8 @@ message = "[message]" if(speaker && !speaker.client) message = "[message]" + else if(speaker && !(get_z(src) == get_z(speaker))) + message = "[message]" to_chat(src, message) else if(teleop) to_chat(teleop, "[create_text_tag("body", "BODY:", teleop.client)][message]") @@ -333,4 +337,6 @@ var/rendered = "[name] [message]" if(!speaker.client) rendered = "[rendered]" + else if(speaker && !(get_z(src) == get_z(speaker))) + rendered = "[message]" to_chat(src, rendered) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 316f314860..665852b953 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -548,3 +548,9 @@ if(src.wear_mask) //if the mob is not human, it cleans the mask without asking for bitflags if(src.wear_mask.clean_blood()) src.update_inv_wear_mask(0) + +/mob/living/carbon/proc/food_preference(var/allergen_type) //RS edit + + if(allergen_type in species.food_preference) + return species.food_preference_bonus + return 0 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index cd7758a44b..3a8d5b2acd 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1211,7 +1211,7 @@ if(species.default_language) add_language(species.default_language) - if(species.icon_scale_x != 1 || species.icon_scale_y != 1) + if(species.icon_scale_x != DEFAULT_ICON_SCALE_X || species.icon_scale_y != DEFAULT_ICON_SCALE_Y) update_transform() if(example) //VOREStation Edit begin diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 905966009d..4737c9b89f 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -423,6 +423,13 @@ emp_act if(istype(AM,/obj/)) var/obj/O = AM + if(stat != DEAD && istype(O,/obj/item) && trash_catching && vore_selected) //Ported from chompstation + var/obj/item/I = O + if(adminbus_trash || is_type_in_list(I,edible_trash) && I.trash_eatable && !is_type_in_list(I,item_vore_blacklist)) + visible_message("[I] is thrown directly into [src]'s [lowertext(vore_selected.name)]!") + I.throwing = 0 + I.forceMove(vore_selected) + return if(in_throw_mode && speed <= THROWFORCE_SPEED_DIVISOR) //empty active hand and we're in throw mode if(canmove && !restrained()) if(isturf(O.loc)) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index a5c9448032..5697f004e0 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1245,6 +1245,11 @@ Paralyse(10) setHalLoss(species.total_health - 1) + if(tiredness) //tiredness for vore drain + tiredness = (tiredness - 1) + if(tiredness >= 100) + Sleeping(5) + if(paralysis || sleeping) blinded = 1 set_stat(UNCONSCIOUS) @@ -1429,6 +1434,21 @@ else clear_fullscreen("brute") + //tiredness for drain vore + if(tiredness) + var/severity = 0 + switch(tiredness) + if(10 to 20) severity = 1 + if(20 to 30) severity = 2 + if(30 to 45) severity = 3 + if(45 to 60) severity = 4 + if(60 to 75) severity = 5 + if(75 to 90) severity = 6 + if(90 to INFINITY) severity = 7 + overlay_fullscreen("tired", /obj/screen/fullscreen/oxy, severity) + else + clear_fullscreen("tired") + if(healths) if (chem_effects[CE_PAINKILLER] > 100) healths.icon_state = "health_numb" diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 4bdd1cbf0e..9f68957cee 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -32,8 +32,8 @@ var/tail_animation // If set, the icon to obtain tail animation states from. var/tail_hair - var/icon_scale_x = 1 // Makes the icon wider/thinner. - var/icon_scale_y = 1 // Makes the icon taller/shorter. + var/icon_scale_x = DEFAULT_ICON_SCALE_X // Makes the icon wider/thinner. + var/icon_scale_y = DEFAULT_ICON_SCALE_Y // Makes the icon taller/shorter. var/race_key = 0 // Used for mob icon cache string. var/icon/icon_template // Used for mob icon generation for non-32x32 species. diff --git a/code/modules/mob/living/carbon/human/species/species_vr.dm b/code/modules/mob/living/carbon/human/species/species_vr.dm index 9ca4008d83..57163d1e9b 100644 --- a/code/modules/mob/living/carbon/human/species/species_vr.dm +++ b/code/modules/mob/living/carbon/human/species/species_vr.dm @@ -42,6 +42,8 @@ var/can_climb = FALSE var/climbing_delay = 1.5 // We climb with a quarter delay + var/list/food_preference = list() //RS edit + var/food_preference_bonus = 0 /datum/species/proc/give_numbing_bite() //Holy SHIT this is hacky, but it works. Updating a mob's attacks mid game is insane. unarmed_attacks = list() diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm index aff75b3e6b..faf3009eb3 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm @@ -200,6 +200,7 @@ YW change end */ /datum/trait/neutral/trashcan/apply(var/datum/species/S,var/mob/living/carbon/human/H) ..() H.verbs |= /mob/living/proc/eat_trash + H.verbs |= /mob/living/proc/toggle_trash_catching //Ported from chompstation /datum/trait/neutral/gem_eater name = "Expensive Taste" @@ -780,3 +781,231 @@ YW CHANGE STOP*/ /datum/trait/neutral/synth_cosmetic_pain/apply(var/datum/species/S,var/mob/living/carbon/human/H, var/trait_prefs = null) ..() H.verbs |= /mob/living/carbon/human/proc/toggle_pain_module + +//Food preferences ported from RogueStar + +/datum/trait/neutral/food_pref + name = "Food Preference - Carnivore" + desc = "You prefer to eat meat, and gain extra nutrition for doing so!" + cost = 0 + custom_only = FALSE + can_take = ORGANICS + var_changes = list("food_preference_bonus" = 5) + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + var/list/our_allergens = list(ALLERGEN_MEAT) + +/datum/trait/neutral/food_pref/apply(datum/species/S, mob/living/carbon/human/H, trait_prefs) + . = ..() + for(var/a in our_allergens) + S.food_preference |= a + +/datum/trait/neutral/food_pref/herbivore + name = "Food Preference - Herbivore" + desc = "You prefer to eat fruits and vegitables, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_VEGETABLE,ALLERGEN_FRUIT) + +/datum/trait/neutral/food_pref/beanivore + name = "Food Preference - Legumovore" + desc = "You prefer to eat bean related foods, such as tofu, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_BEANS) + +/datum/trait/neutral/food_pref/omnivore + name = "Food Preference - Omnivore" + desc = "You prefer to eat meat and vegitables, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_VEGETABLE,ALLERGEN_MEAT) + +/datum/trait/neutral/food_pref/fungivore + name = "Food Preference - Fungivore" + desc = "You prefer to eat mushrooms and fungus, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_FUNGI) + +/datum/trait/neutral/food_pref/piscivore + name = "Food Preference - Piscivore" + desc = "You prefer to eat fish, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_FISH) + +/datum/trait/neutral/food_pref/granivore + name = "Food Preference - Granivore" + desc = "You prefer to eat grains and seeds, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_GRAINS,ALLERGEN_SEEDS) + +/datum/trait/neutral/food_pref/cocoavore + name = "Food Preference - Cocoavore" + desc = "You prefer to eat chocolate, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_CHOCOLATE) + +/datum/trait/neutral/food_pref/glycovore + name = "Food Preference - Glycovore" + desc = "You prefer to eat sugar, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_SUGARS) + +/datum/trait/neutral/food_pref/lactovore + name = "Food Preference - Lactovore" + desc = "You prefer to eat and drink things with milk in them, and gain extra nutrition for doing so!" + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/coffee, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_DAIRY) + +/datum/trait/neutral/food_pref/coffee + name = "Food Preference - Coffee Dependant" + desc = "You can get by on coffee alone if you have to, and you like it that way." + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/stimulant + ) + our_allergens = list(ALLERGEN_COFFEE) + +/datum/trait/neutral/food_pref/stimulant + name = "Food Preference - Stimulant Dependant" + desc = "You can get by on caffine alone if you have to, and you like it that way." + excludes = list( + /datum/trait/neutral/food_pref, + /datum/trait/neutral/food_pref/herbivore, + /datum/trait/neutral/food_pref/beanivore, + /datum/trait/neutral/food_pref/omnivore, + /datum/trait/neutral/food_pref/fungivore, + /datum/trait/neutral/food_pref/piscivore, + /datum/trait/neutral/food_pref/granivore, + /datum/trait/neutral/food_pref/cocoavore, + /datum/trait/neutral/food_pref/glycovore, + /datum/trait/neutral/food_pref/lactovore, + /datum/trait/neutral/food_pref/coffee + ) + our_allergens = list(ALLERGEN_STIMULANT) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7abf8601aa..ce7fc6d13e 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1307,5 +1307,5 @@ /mob/living/set_dir(var/new_dir) . = ..() - if(size_multiplier != 1 || icon_scale_x != 1 && center_offset > 0) + if(size_multiplier != 1 || icon_scale_x != DEFAULT_ICON_SCALE_X && center_offset > 0) update_transform(TRUE) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index a686d4501e..1429a172a3 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -257,6 +257,13 @@ /mob/living/hitby(atom/movable/AM as mob|obj,var/speed = THROWFORCE_SPEED_DIVISOR)//Standardization and logging -Sieve if(istype(AM,/obj/)) var/obj/O = AM + if(stat != DEAD && istype(O,/obj/item) && trash_catching && vore_selected) //ported from chompstation + var/obj/item/I = O + if(adminbus_trash || is_type_in_list(I,edible_trash) && I.trash_eatable && !is_type_in_list(I,item_vore_blacklist)) + visible_message("[I] is thrown directly into [src]'s [lowertext(vore_selected.name)]!") + I.throwing = 0 + I.forceMove(vore_selected) + return var/dtype = O.damtype var/throw_damage = O.throwforce*(speed/THROWFORCE_SPEED_DIVISOR) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 7bdb20349d..6635200baa 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -82,3 +82,4 @@ var/inventory_panel_type = /datum/inventory_panel var/datum/inventory_panel/inventory_panel var/last_resist_time = 0 // world.time of the most recent resist that wasn't on cooldown. + var/tiredness = 0 //For vore draining diff --git a/code/modules/mob/living/living_vr.dm b/code/modules/mob/living/living_vr.dm index 582ed83db6..853e790885 100644 --- a/code/modules/mob/living/living_vr.dm +++ b/code/modules/mob/living/living_vr.dm @@ -53,8 +53,10 @@ /mob/living/proc/set_metainfo_likes(var/reopen = TRUE) if(usr != src) return - var/new_metadata = strip_html_simple(tgui_input_text(usr, "Enter any information you'd like others to see relating to your LIKED roleplay preferences. This will not be saved permanently unless you click save in the OOC notes panel!", "Game Preference" , html_decode(ooc_notes_likes), multiline = TRUE, prevent_enter = TRUE)) - if(CanUseTopic(usr)) + var/new_metadata = strip_html_simple(tgui_input_text(usr, "Enter any information you'd like others to see relating to your LIKED roleplay preferences. This will not be saved permanently unless you click save in the OOC notes panel! Type \"!clear\" to empty.", "Game Preference" , html_decode(ooc_notes_likes), multiline = TRUE, prevent_enter = TRUE)) + if(new_metadata && CanUseTopic(usr)) + if(new_metadata == "!clear") + new_metadata = "" ooc_notes_likes = new_metadata client.prefs.metadata_likes = new_metadata to_chat(usr, "OOC note likes have been updated. Don't forget to save!") @@ -65,8 +67,10 @@ /mob/living/proc/set_metainfo_dislikes(var/reopen = TRUE) if(usr != src) return - var/new_metadata = strip_html_simple(tgui_input_text(usr, "Enter any information you'd like others to see relating to your DISLIKED roleplay preferences. This will not be saved permanently unless you click save in the OOC notes panel!", "Game Preference" , html_decode(ooc_notes_dislikes), multiline = TRUE, prevent_enter = TRUE)) - if(CanUseTopic(usr)) + var/new_metadata = strip_html_simple(tgui_input_text(usr, "Enter any information you'd like others to see relating to your DISLIKED roleplay preferences. This will not be saved permanently unless you click save in the OOC notes panel! Type \"!clear\" to empty.", "Game Preference" , html_decode(ooc_notes_dislikes), multiline = TRUE, prevent_enter = TRUE)) + if(new_metadata && CanUseTopic(usr)) + if(new_metadata == "!clear") + new_metadata = "" ooc_notes_dislikes = new_metadata client.prefs.metadata_dislikes = new_metadata to_chat(usr, "OOC note dislikes have been updated. Don't forget to save!") diff --git a/code/modules/mob/living/silicon/ai/ai_remote_control.dm b/code/modules/mob/living/silicon/ai/ai_remote_control.dm index 80d02bb08e..0655ed8123 100644 --- a/code/modules/mob/living/silicon/ai/ai_remote_control.dm +++ b/code/modules/mob/living/silicon/ai/ai_remote_control.dm @@ -57,6 +57,12 @@ deployed_shell = target target.deploy_init(src) mind.transfer_to(target) + if(target.first_transfer) + target.first_transfer = FALSE + target.copy_from_prefs_vr() + if(LAZYLEN(target.vore_organs)) + target.vore_selected = target.vore_organs[1] + src.copy_vore_prefs_to_mob(target) teleop = target // So the AI 'hears' messages near its core. target.post_deploy() diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index ea3feaa230..ab87a5e588 100644 --- a/code/modules/mob/living/silicon/ai/login.dm +++ b/code/modules/mob/living/silicon/ai/login.dm @@ -1,4 +1,4 @@ -/mob/living/silicon/ai/Login() //ThisIsDumb(TM) TODO: tidy this up �_� ~Carn +/mob/living/silicon/ai/Login() //ThisIsDumb(TM) TODO: tidy this up °_° ~Carn ..() for(var/obj/effect/rune/rune in rune_list) client.images += rune.blood_image @@ -9,4 +9,4 @@ if(multicam_on) end_multicam() src.view_core() - return \ No newline at end of file + return diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 83b25ea037..bd7f419f7f 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -96,7 +96,7 @@ var/lockcharge //Used when looking to see if a borg is locked down. var/lockdown = 0 //Controls whether or not the borg is actually locked down. var/speed = 0 //Cause sec borgs gotta go fast //No they dont! - var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. + var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. var/tracking_entities = 0 //The number of known entities currently accessing the internal camera var/braintype = "Cyborg" @@ -113,6 +113,8 @@ /mob/living/proc/lend_prey_control ) + var/has_recoloured = FALSE + /mob/living/silicon/robot/New(loc, var/unfinished = 0) spark_system = new /datum/effect/effect/system/spark_spread() spark_system.set_up(5, 0, src) @@ -164,15 +166,17 @@ add_robot_verbs() - hud_list[HEALTH_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_HEALTH) - hud_list[STATUS_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudhealth100", plane = PLANE_CH_STATUS) - hud_list[LIFE_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudhealth100", plane = PLANE_CH_LIFE) - hud_list[ID_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_ID) - hud_list[WANTED_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_WANTED) - hud_list[IMPLOYAL_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPLOYAL) - hud_list[IMPCHEM_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPCHEM) - hud_list[IMPTRACK_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPTRACK) - hud_list[SPECIALROLE_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_SPECIAL) + hud_list[HEALTH_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_HEALTH) + hud_list[STATUS_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudhealth100", plane = PLANE_CH_STATUS) + hud_list[LIFE_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudhealth100", plane = PLANE_CH_LIFE) + hud_list[ID_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_ID) + hud_list[WANTED_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_WANTED) + hud_list[IMPLOYAL_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPLOYAL) + hud_list[IMPCHEM_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPCHEM) + hud_list[IMPTRACK_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_IMPTRACK) + hud_list[SPECIALROLE_HUD] = gen_hud_image('icons/mob/hud.dmi', src, "hudblank", plane = PLANE_CH_SPECIAL) + + /mob/living/silicon/robot/LateInitialize() . = ..() @@ -518,14 +522,14 @@ // function to toggle VTEC once installed /mob/living/silicon/robot/proc/toggle_vtec() - set name = "Toggle VTEC" - set category = "Abilities" - if(speed == -1) - to_chat(src, "VTEC module disabled.") - speed = 0 - else - to_chat(src, "VTEC module enabled.") - speed = -1 + set name = "Toggle VTEC" + set category = "Abilities" + if(speed == -1) + to_chat(src, "VTEC module disabled.") + speed = 0 + else + to_chat(src, "VTEC module enabled.") + speed = -1 // update the status screen display /mob/living/silicon/robot/Stat() @@ -818,6 +822,18 @@ module.Destroy() module = null updatename("Default") + has_recoloured = FALSE + +/mob/living/silicon/robot/proc/ColorMate() + set name = "Recolour Module" + set category = "Robot Commands" + set desc = "Allows to recolour once." + + if(!has_recoloured) + var/datum/ColorMate/recolour = new /datum/ColorMate(usr) + recolour.tgui_interact(usr) + return + to_chat(usr, "You've already recoloured yourself once. Ask for a module reset for another.") /mob/living/silicon/robot/attack_hand(mob/user) @@ -842,11 +858,15 @@ if(istype(user,/mob/living/carbon/human) && !opened) var/mob/living/carbon/human/H = user - //Adding borg petting. Help intent pets, Disarm intent taps and Harm is punching(no damage) + //Adding borg petting. Help intent pets if preferences allow, Disarm intent taps and Harm is punching(no damage) switch(H.a_intent) if(I_HELP) - visible_message("[H] pets [src].") - return + if(client && !client.prefs.borg_petting) + visible_message("[H] reaches out for [src], but quickly refrains from petting.") + return + else + visible_message("[H] pets [src].") + return if(I_HURT) H.do_attack_animation(src) if(H.species.can_shred(H)) @@ -1146,7 +1166,7 @@ /mob/living/silicon/robot/proc/ResetSecurityCodes() set category = "Robot Commands" set name = "Reset Identity Codes" - set desc = "Scrambles your security and identification codes and resets your current buffers. Unlocks you and but permenantly severs you from your AI and the robotics console and will deactivate your camera system." + set desc = "Scrambles your security and identification codes and resets your current buffers. Unlocks you and permenantly severs you from your AI and the robotics console and will deactivate your camera system." var/mob/living/silicon/robot/R = src @@ -1249,10 +1269,14 @@ /mob/living/silicon/robot/proc/add_robot_verbs() src.verbs |= robot_verbs_default src.verbs |= silicon_subsystems + if(config.allow_robot_recolor) + src.verbs |= /mob/living/silicon/robot/proc/ColorMate /mob/living/silicon/robot/proc/remove_robot_verbs() src.verbs -= robot_verbs_default src.verbs -= silicon_subsystems + if(config.allow_robot_recolor) + src.verbs |= /mob/living/silicon/robot/proc/ColorMate // Uses power from cyborg's cell. Returns 1 on success or 0 on failure. // Properly converts using CELLRATE now! Amount is in Joules. @@ -1331,12 +1355,23 @@ return if(opened)//Cover is open - if(emagged) return//Prevents the X has hit Y with Z message also you cant emag them twice + if(emagged) + if (!has_zeroth_law()) + to_chat(user, "You assigned yourself as [src]'s operator.") + message_admins("[key_name_admin(user)] assigned as operator on cyborg [key_name_admin(src)]. Syndicate Operator change.") + log_game("[key_name(user)] assigned as operator on cyborg [key_name(src)]. Syndicate Operator change.") + var/datum/gender/TU = gender_datums[user.get_visible_gender()] + set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.") + to_chat(src, "Obey these laws:") + laws.show_laws(src) + to_chat(src, "ALERT: [user.real_name] is your new master. Obey your new laws and [TU.his] commands.") + else + to_chat(user, "[src] already has an operator assigned.") + return//Prevents the X has hit Y with Z message also you cant emag them twice if(wiresexposed) to_chat(user, "You must close the panel first.") return - // The block of code below is from TG. Feel free to replace with a better result if desired. if(shell) // AI shells cannot be emagged, so we try to make it look like a standard reset. Smart players may see through this, however. to_chat(user, "[src] is remotely controlled! Your emag attempt has triggered a system reset instead!") @@ -1350,8 +1385,8 @@ lawupdate = 0 disconnect_from_ai() to_chat(user, "You emag [src]'s interface.") - message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.") - log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.") + message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.") + log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.") clear_supplied_laws() clear_inherent_laws() laws = new /datum/ai_laws/syndicate_override diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm index 80fc74488e..59c2c6e875 100644 --- a/code/modules/mob/living/silicon/robot/robot_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_items.dm @@ -365,7 +365,7 @@ /obj/item/weapon/form_printer/proc/print_form() var/list/paper_forms = list("Empty", "Command", "Security", "Supply", "Science", "Medical", "Engineering", "Service", "Exploration", "Event", "Other", "Mercenary") var/list/command_paper_forms = list("COM-0002: Dismissal Order", "COM-0003: Job Change Request", "COM-0004: ID Replacement Request", "COM-0005: Access Change Order", "COM-0006: Formal Complaint", "COM-0009: Visitor Permit", "COM-0012: Personnel Request Form", "COM-0013: Employee of the Month Nomination Form") - var/list/security_paper_forms = list("SEC-1001: Shift-Start Checklist", "SEC-1002: Patrol Assignment Sheet", "SEC-1003: Incident Report", "SEC-1004: Arrest Report", "SEC-1005: Arrest Warrant", "SEC-1006: Search Warrant", "SEC-1007: Forensics Investigation Report", "SEC-1008: Interrogation Report", "SEC-1009: Witness Statement", "SEC-1010: Armory Inventory", "SEC-1011: Armory Equipment Request", "SEC-1012: Armory Equipment Deployment", "SEC-1013: Weapon Permit", "SEC-1014: Injunction") + var/list/security_paper_forms = list("SEC-1001: Shift-Start Checklist", "SEC-1002: Patrol Assignment Sheet", "SEC-1003: Incident Report", "SEC-1004: Arrest Report", "SEC-1005: Arrest Warrant", "SEC-1006: Search Warrant", "SEC-1007: Forensics Investigation Report", "SEC-1008: Interrogation Report", "SEC-1009: Witness Statement", "SEC-1010: Armory Inventory", "SEC-1011: Armory Equipment Request", "SEC-1012: Armory Equipment Deployment", "SEC-1013: Weapon Permit", "SEC-1014: Injunction", "SEC-1015: Deputization Waiver") var/list/supply_paper_forms = list("SUP-2001: Delivery of Goods", "SUP-2002: Delivery of Resources", "SUP-2003: Material Stock") var/list/science_paper_forms = list("SCI-3003: Cyborg / Robot Inspection", "SCI-3004: Cyborg / Robot Upgrades", "SCI-3009: Xenoflora Genetics Report") var/list/medical_paper_forms = list("MED-4001: Death Certificate", "MED-4002: Prescription", "MED-4003: Against Medical Advice", "MED-4004: Cyborgification Contract", "MED-4005: Mental Health Patient Intake", "MED-4006: NIF Surgery", "MED-4007: Psychiatric Evaluation") @@ -514,6 +514,9 @@ if("SEC-1014") content = @{"[grid][row][cell][b]Receiving Personnel:[/b][cell][field][/grid][br][hr][h3]Terms of Injunction[/h3][field][br][br][hr][small][i]I have read and understand the above terms of this injunction. I understand that this injunction legally forbids me from performing the above actions under any circumstances and that violating this injunction can result in my immediate arrest. If I wish to appeal this injunction, I may contact an Internal Affairs Agent to appeal my case. I understand that this injunction is valid only until the end of the shift in which it was issued unless I am notified otherwise by a Central Command Officer. My below signature constitutes acknowledgment and agreeance with all of these above statements, though it is not required to enforce this injunction.[/small][/i][br][br][grid][row][cell][list][b]Signature of Receiving Personnel:[/b][/list][cell][cell][list][list][list][list][b]Signature of Head of Security [small](or stand-in signatory):[/b][/list][/list][/list][/list][cell][br][row][cell][list] - [large][field][/large][/list][cell][cell][list][list][list][list]- [large][field][/large][/list][/list][/list][/list][cell][/grid]"} revision = "Revision: 1.1" + if("SEC-1015") + content = @{"[hr][br]I, [small][u][field][/u][/small], agree to become deputized to the Security department onboard the [station] and agree to be held to the responsibilities and expectations thereof.[br][br]I understand that this means I am granted some powers of a security officer, but that I am not a security officer and do not hold any authority over any member of the security department.[br][br]I understand that this deputization may be revoked at any time by the head of staff that authorized it or by any security officer with probable cause.[br][br]I understand that I am not above the law and agree to abide by all laws and regulations for as long as I am deputized.[br][br]I understand that I will be placed under direct supervision by the Security department and must follow valid orders given to me by a superior.[br][br][grid][row][cell][list][b]Personnel's Signature:[/b][/list][cell][cell][list][list][list][list][b]Autorizing Staff Signature [small](Title: [field])[/small]:[/b][/list][/list][/list][/list][cell][br][row][cell][list] - [large][field][/large][/list][cell][cell][list][list][list][list]- [large][field][/large][/list][/list][/list][/list][cell][/grid]"} + revision = "Revision: 3.2" //Supply forms, SUP-2 if("SUP-2001") content = @{"[grid][row][cell][b]Order Number(s):[/b] [cell][field][br][row][cell][b]Shipment Destination:[/b] [cell][field][br][row][cell][b]Shipment Method:[/b] [cell][field][/grid][br][hr][br][center][b]Goods in this delivery:[/b][/center][br][list][br][*][field][br][*][field][br][*][field][br][*][field][br][*][field][br][/list][br][hr][grid][row][cell][list][b]Supply personnel signature:[/b][/list][cell][cell][list][list][list][list][b]Receiver signature:[/b][/list][/list][/list][/list][cell][br][row][cell][list] - [large][field][/large][/list][cell][cell][list][list][list][list]- [large][field][/large][/list][/list][/list][/list][cell][/grid]"} diff --git a/code/modules/mob/living/silicon/robot/robot_remote_control.dm b/code/modules/mob/living/silicon/robot/robot_remote_control.dm index fba59808b4..344ba44873 100644 --- a/code/modules/mob/living/silicon/robot/robot_remote_control.dm +++ b/code/modules/mob/living/silicon/robot/robot_remote_control.dm @@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(available_ai_shells) var/shell = FALSE var/deployed = FALSE var/mob/living/silicon/ai/mainframe = null + var/first_transfer = TRUE // Premade AI shell, for roundstart shells. /mob/living/silicon/robot/ai_shell/Initialize() @@ -89,6 +90,7 @@ GLOBAL_LIST_EMPTY(available_ai_shells) if(message) to_chat(src, span("notice", message)) mind.transfer_to(mainframe) + src.copy_vore_prefs_to_mob(mainframe) deployed = FALSE update_icon() mainframe.teleop = null diff --git a/code/modules/mob/living/silicon/robot/sprites/civilian.dm b/code/modules/mob/living/silicon/robot/sprites/civilian.dm index cddce131db..18f371accf 100644 --- a/code/modules/mob/living/silicon/robot/sprites/civilian.dm +++ b/code/modules/mob/living/silicon/robot/sprites/civilian.dm @@ -276,7 +276,6 @@ sprite_icon_state = "newmeka" has_eye_light_sprites = TRUE has_custom_open_sprites = TRUE - has_vore_belly_sprites = FALSE rest_sprite_options = list("Default", "Sit") /datum/robot_sprite/dogborg/tall/service/mmeka @@ -284,7 +283,6 @@ sprite_icon_state = "mmeka" has_eye_light_sprites = TRUE has_custom_open_sprites = TRUE - has_vore_belly_sprites = FALSE rest_sprite_options = list("Default", "Sit") /datum/robot_sprite/dogborg/tall/service/fmeka @@ -292,7 +290,6 @@ sprite_icon_state = "fmeka" has_eye_light_sprites = TRUE has_custom_open_sprites = TRUE - has_vore_belly_sprites = FALSE rest_sprite_options = list("Default", "Sit") /datum/robot_sprite/dogborg/tall/service/k4t diff --git a/code/modules/mob/living/silicon/robot/syndicate.dm b/code/modules/mob/living/silicon/robot/syndicate.dm index 286b2abf75..e776608975 100644 --- a/code/modules/mob/living/silicon/robot/syndicate.dm +++ b/code/modules/mob/living/silicon/robot/syndicate.dm @@ -1,6 +1,7 @@ /mob/living/silicon/robot/syndicate lawupdate = 0 scrambledcodes = 1 + emagged = 1 icon_state = "securityrobot" modtype = "Security" lawchannel = "State" diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index fe67168891..1b3cea164c 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -174,6 +174,11 @@ var/injury_enrages = FALSE // Do injuries enrage (aka strengthen) our mob? If yes, we'll interpret how hurt we are differently. // VOREStation Add End + var/has_recoloured = FALSE + var/hunting_cooldown = 0 + var/hasthermals = TRUE + var/isthermal = 0 + /mob/living/simple_mob/Initialize() verbs -= /mob/verb/observe health = maxHealth @@ -193,6 +198,10 @@ if(organ_names) organ_names = GET_DECL(organ_names) + if(config.allow_simple_mob_recolor) + verbs |= /mob/living/simple_mob/proc/ColorMate + + return ..() /mob/living/simple_mob/Destroy() @@ -216,6 +225,8 @@ /mob/living/simple_mob/Login() . = ..() to_chat(src,"You are \the [src]. [player_msg]") + if(hasthermals) + verbs |= /mob/living/simple_mob/proc/hunting_vision //So that maint preds can see prey through walls, to make it easier to find them. /mob/living/simple_mob/SelfMove(turf/n, direct, movetime) var/turf/old_turf = get_turf(src) @@ -337,3 +348,43 @@ . = ..() // Calling parent here, actually updating our mob on how hurt we are. // VOREStation Add End + +/mob/living/simple_mob/proc/ColorMate() + set name = "Recolour" + set category = "Abilities" + set desc = "Allows to recolour once." + + if(!has_recoloured) + var/datum/ColorMate/recolour = new /datum/ColorMate(usr) + recolour.tgui_interact(usr) + return + to_chat(usr, "You've already recoloured yourself once. You are only allowed to recolour yourself once during a around.") + +//Thermal vision adding + +/mob/living/simple_mob/proc/hunting_vision() + set name = "Track Prey Through Walls" + set category = "Abilities" + set desc = "Uses you natural predatory instincts to seek out prey even through walls, or your natural survival instincts to spot predators from a distance." + + if(hunting_cooldown + 5 MINUTES < world.time) + to_chat(usr, "You can sense other creatures by focusing carefully on your surroundings.") + sight |= SEE_MOBS + hunting_cooldown = world.time + spawn(600) + to_chat(usr, "Your concentration wears off.") + sight -= SEE_MOBS + else if(hunting_cooldown + 5 MINUTES > world.time) + to_chat(usr, "You must wait for a while before using this again.") + +/mob/living/simple_mob/proc/hunting_vision_plus() + set name = "Thermal vision toggle" + set category = "Abilities" + set desc = "Uses you natural predatory instincts to seek out prey even through walls, or your natural survival instincts to spot predators from a distance." + + if(!isthermal) + to_chat(usr, "You can sense other creatures by focusing carefully on your surroundings.") + sight |= SEE_MOBS + else + to_chat(usr, "You stop sensing creatures beyond the walls.") + sight -= SEE_MOBS diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/catslug.dm b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/catslug.dm index d203d12949..91e806c8da 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/catslug.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/alien animals/catslug.dm @@ -758,7 +758,7 @@ catalogue_data = list(/datum/category_item/catalogue/fauna/catslug/custom/capslug) holder_type = /obj/item/weapon/holder/catslug/custom/capslug say_list_type = /datum/say_list/catslug/custom/capslug - myid_access = list(access_maint_tunnels) //The all_station_access part below adds onto this. + myid_access = list(access_heads, access_keycard_auth) //Same access as a bridge secretary. /datum/say_list/catslug/custom/capslug speak = list("How open big glass box with shiny inside?.", "What is that?", "Respect my authority!", "What are you doing?", "How did you get here?", "Fax for yellow-shirts!", "WAOW!", "Why is that console blinking and clicking?", "Do we need to call for ERT?", "Have been called comdom before, not sure why they thought I was a balloon.") @@ -773,7 +773,6 @@ mob_radio.ks2type = /obj/item/device/encryptionkey/heads/captain //Might not be able to speak, but the catslug can listen. mob_radio.keyslot2 = new /obj/item/device/encryptionkey/heads/captain(mob_radio) mob_radio.recalculateChannels(1) - myid.access |= get_all_station_access() //============================================================================= //Admin-spawn only catslugs below - Expect overpowered things & silliness below @@ -1025,6 +1024,41 @@ /datum/say_list/catslug/custom/cryptslug speak = list("I have a lot of nasty friends.", "Do not test me.", "I shall rise again!", "How dare you step foot in my domain?", "Dare you indluge in dark desires?", "I am become death, one day.", "Foul creature!", "I used to think my life was a tragedy, but now I realize it's kind of okay actually.") +//jungle slug + +/mob/living/simple_mob/vore/alienanimals/catslug/custom/exploslug + name = "Explorer Pawdiana" + desc = "A green-furred noodley bodied creature with thin arms and legs, and gloomy dark eyes. This one is adorned with an explorers hat and vest." + tt_desc = "Mollusca Felis Exploris" + icon_state = "exploslug" + icon_living = "exploslug" + icon_rest = "exploslug_rest" + icon_dead = "exploslug_dead" + catalogue_data = list(/datum/category_item/catalogue/fauna/catslug/custom/exploslug) + say_list_type = /datum/say_list/catslug/custom/exploslug + +/datum/category_item/catalogue/fauna/catslug/custom/exploslug + name = "Alien Wildlife - Catslug - Explorer Pawdiana" + desc = "Found in the depths of an arugably magical jungle, Pawdiana\ + is a catslug who spends their days treking through the dense foliage \ + of the dangerous wild. Always seen within \ + their fancy explorers kit, they are always ready to brave \ + the hazards of unknown lands. \ + \ + The Catslug is an omnivorous terrestrial creature.\ + Exhibiting properties of both a cat and a slug (hence its name)\ + it moves somewhat awkwardly. However, the unique qualities of\ + its body make it exceedingly flexible and smooth, allowing it to\ + wiggle into and move effectively in even extremely tight spaces.\ + Additionally, it has surprisingly capable hands, and moves quite\ + well on two legs or four. Caution is advised when interacting\ + with these creatures, they are quite intelligent, and proficient\ + tool users." + value = CATALOGUER_REWARD_TRIVIAL + +/datum/say_list/catslug/custom/exploslug + speak = list("Fortune and porls, kid. Fortune and porls.", "Lizards, why'd it have to be lizards.", "That thingy is an important artifact. It belongs in a museum!", "Everything lost is meant to be found. By me.", "I swear I've seen that stone before...", "I should have packed more jellyfishes.", "I better get back before nightfall!", "A comfy bed? Hah! I sleep under the stars!") + //============================= //Admin-spawn only catslugs end diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm index dd6b6db3cf..91ac369940 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm @@ -40,6 +40,8 @@ say_list_type = /datum/say_list/mouse + hasthermals = FALSE + var/body_color //brown, gray, white and black, leave blank for random /mob/living/simple_mob/animal/passive/mouse/New() diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm index f336a45b86..37ef332a14 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/bigdragon.dm @@ -124,6 +124,8 @@ I think I covered everything. var/small_icon = 'icons/mob/bigdragon_small.dmi' var/small_icon_state = "dragon_small" var/flames + var/firebreathtimer + var/chargetimer tame_items = list( /obj/item/weapon/coin/gold = 100, @@ -263,6 +265,10 @@ I think I covered everything. /mob/living/simple_mob/vore/bigdragon/runechat_y_offset(width, height) return (..()*size_multiplier) + 40 +/mob/living/simple_mob/vore/bigdragon/death() + . = ..() + canceltimers() + /// /// Verbs /// @@ -766,10 +772,14 @@ I think I covered everything. do_windup_animation(A, charge_warmup) //callbacks are more reliable than byond's process scheduler - addtimer(CALLBACK(src, PROC_REF(chargeend), A), charge_warmup) + chargetimer = addtimer(CALLBACK(src, PROC_REF(chargeend), A), charge_warmup, TIMER_STOPPABLE) /mob/living/simple_mob/vore/bigdragon/proc/chargeend(var/atom/A, var/explicit = 0, var/gentle = 0) + //make sure our target still exists and is on a turf + if(QDELETED(A) || !isturf(get_turf(A))) + set_AI_busy(FALSE) + return status_flags |= LEAPING flying = 1 //So we can thunk into things hovering = 1 // So we don't hurt ourselves running off cliffs @@ -804,10 +814,14 @@ I think I covered everything. set_AI_busy(TRUE) flames = 1 build_icons() - addtimer(CALLBACK(src, PROC_REF(firebreathend), A), charge_warmup) + firebreathtimer = addtimer(CALLBACK(src, PROC_REF(firebreathend), A), charge_warmup, TIMER_STOPPABLE) playsound(src, "sound/magic/Fireball.ogg", 50, 1) /mob/living/simple_mob/vore/bigdragon/proc/firebreathend(var/atom/A) + //make sure our target still exists and is on a turf + if(QDELETED(A) || !isturf(get_turf(A))) + set_AI_busy(FALSE) + return var/obj/item/projectile/P = new /obj/item/projectile/bullet/dragon(get_turf(src)) src.visible_message("\The [src] spews fire at \the [A]!") playsound(src, "sound/weapons/Flamer.ogg", 50, 1) @@ -891,6 +905,9 @@ I think I covered everything. vore_selected = gut2 //Just incase it eats someone right after being tamed ai_holder = new /datum/ai_holder/simple_mob/healbelly/retaliate/dragon(src) + //Cancel any charges or firebreaths winding up + canceltimers() + /datum/ai_holder/simple_mob/healbelly intelligence_level = 3 can_breakthrough = 0 @@ -1036,6 +1053,17 @@ I think I covered everything. vore_selected = gut1 D.give_target(attacker) +/mob/living/simple_mob/vore/bigdragon/proc/canceltimers() + //Cancel any charges or firebreaths winding up + if(firebreathtimer) + deltimer(firebreathtimer) + firebreathtimer = null + if(chargetimer) + deltimer(chargetimer) + chargetimer = null + //re-enable the AI + set_AI_busy(FALSE) + //Smack people it warns /datum/ai_holder/simple_mob/healbelly/retaliate/dragon/proc/dissuade(var/chump) if(chump in check_trajectory(chump, holder, pass_flags = PASSTABLE)) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/dragon.dm b/code/modules/mob/living/simple_mob/subtypes/vore/dragon.dm index 6438ee1f8e..7c2ee03da4 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/dragon.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/dragon.dm @@ -114,3 +114,17 @@ /datum/say_list/dragonboss say_got_target = list("roars and snaps it jaws!") + +/mob/living/simple_mob/vore/aggressive/dragon/space + name = "space dragon" + tt_desc = "Astra Draconinae" + maxHealth = 300 + health = 300 + faction = "dragon" + icon_dead = "space_dragon_dead" + icon_living = "space_dragon" + icon_state = "space_dragon" + mount_offset_y = 24 + mount_offset_x = -9 + has_eye_glow = TRUE + vore_eyes = TRUE diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm index 97fff51176..afe0c96324 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm @@ -103,3 +103,95 @@ speak = list("NEHEHEHEHEH","Neh?") emote_hear = list("snorts","whinnies") emote_see = list("shakes its head", "stamps a hoof", "looks around") + +//Kelpie resprite of the big horse + +/mob/living/simple_mob/vore/horse/kelpie + name = "kelpie" + icon_state = "kelpie" + icon_living = "kelpie" + icon_dead = "kelpie-dead" + icon = 'icons/mob/vore64x64.dmi' + desc = "A darkly furred horse-like creature with piercing light green eyes. It's mane is a dark green and has an almost seaweed like texture." + tt_desc = "Equus cailpeach" + + vore_capacity = 2 + maxHealth = 250 + health = 250 + meat_amount = 10 + + old_x = -16 + old_y = 0 + default_pixel_x = -16 + pixel_x = -16 + pixel_y = 0 + mount_offset_y = 22 + say_list_type = /datum/say_list/horse/kelpie + ai_holder_type = /datum/ai_holder/simple_mob/vore/kelpie + + vore_bump_chance = 75 + vore_pounce_chance = 75 + vore_pounce_maxhealth = 200 + vore_bump_emote = "chomps down on" + +/mob/living/simple_mob/vore/horse/kelpie/init_vore() + ..() + var/obj/belly/B = vore_selected + B.name = "stomach" + B.desc = "With a final few gulps, the kelpie finishes swallowing you down into its hot, humid gut... and with a slosh, your weight makes the equine's belly hang down slightly like some sort of organic hammock. The thick, damp air is tinged with the smell of seaweed, and the surrounding flesh wastes no time in clenching and massaging down over its newfound fodder." + B.digest_brute = 3 + B.digest_burn = 3 + B.digestchance = 40 + B.absorbchance = 1 + B.escapechance = 7 + B.escape_stun = 5 + + B.emote_lists[DM_HOLD] = list( + "The kelpie's idle trotting helps its stomach gently churn around you, slimily squelching against your figure.", + "The equine predator takes a moment to intentionally clench its gut around you, encapsulating you in a strange, fleshy hug.", + "Some hot, viscous slime oozes down over your form, helping slicken you up during your stay.", + "During a moment of relative silence, you can hear the beast's soft, relaxed breathing as it casually goes about its day.", + "The thick, humid atmosphere within the kelpie's hanging belly works in tandem with its steady, metronome-like heartbeat to soothe you.", + "Your surroundings sway from side to side as the kelpie trots about, as if it is showing off its newest catch.") + + B.emote_lists[DM_DIGEST] = list( + "The kelpie huffs in annoyance before clenching those wrinkled walls tight against your form, grinding away at you!", + "As the beast trots about, you're forced to slip and slide around amidst a pool of thick digestive goop!", + "You can barely hear the horse let out a pleased nicker as its stomach eagerly gurgles around its newfound meal!", + "As the thinning air begins to make you feel dizzy, menacing bworps and grumbles fill that dark, constantly shifting organ!", + "The constant, rhythmic kneading and massaging starts to take its toll along with the muggy heat, making you feel weaker and weaker!", + "The kelpie happily trots around while digesting its meal, almost like it is trying to show off the hanging gut you've given it.") + +/datum/say_list/horse/kelpie + speak = list("...","?") + emote_hear = list("whispers something","lets out a high pitched, distorted neigh") + emote_see = list("beckons you near", "watches", "swishes it's grass-like tail") + +/datum/ai_holder/simple_mob/vore/kelpie + +/datum/ai_holder/simple_mob/vore/kelpie/handle_wander_movement() + if(!holder) + return + ai_log("handle_wander_movement() : Entered.", AI_LOG_TRACE) + if(isturf(holder.loc) && can_act()) + wander_delay-- + var/turf/simulated/floor/water/deep/ocean/diving/sink = holder.loc + var/turf/simulated/floor/water/underwater/surface = holder.loc + var/mob/living/simple_mob/H = holder + if(istype(sink) && H.vore_fullness) + holder.zMove(DOWN) + wander_delay = base_wander_delay + else if(istype(surface) && !H.vore_fullness) + holder.zMove(UP) + wander_delay = base_wander_delay + else if(wander_delay <= 0) + if(!wander_when_pulled && (holder.pulledby || holder.grabbed_by.len)) + ai_log("handle_wander_movement() : Being pulled and cannot wander. Exiting.", AI_LOG_DEBUG) + return + + var/moving_to = 0 // Apparently this is required or it always picks 4, according to the previous developer for simplemob AI. + moving_to = pick(cardinal) + holder.set_dir(moving_to) + holder.IMove(get_step(holder,moving_to)) + wander_delay = base_wander_delay + ai_log("handle_wander_movement() : Exited.", AI_LOG_TRACE) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm b/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm index 49d3a62835..2ec34f4c3d 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm @@ -6,7 +6,8 @@ or even borne out of hunger, but more of a form of playfighting among packmates. Some colonies are known to keep domesticated specimens as a form of pest control \ despite the occasional accidents that can occur as a result of staff becoming overly friendly and triggering their playfighting instincts. \ More mature specimens are identifiable by a greener tint to their skin, and eventually the development of frills \ - around their neck and along the backs of their heads." + around their neck and along the backs of their heads. Other regional subspecies are known to exist, some with a more aggressive temperament than others. \ + While they can be mildly hazardous to the unprepared explorer, they are primarily ambush predators and will rarely attack if you maintain eye contact." value = CATALOGUER_REWARD_TRIVIAL /mob/living/simple_mob/vore/pakkun @@ -109,7 +110,13 @@ our_targets -= list_target continue var/mob/living/L = list_target - if(!(L.can_be_drop_prey && L.throw_vore && L.allowmobvore)) + if(!(L.can_be_drop_prey && L.throw_vore && L.allowmobvore && !L.buckled)) + our_targets -= list_target + continue + if((L.dir == 1 && holder.y >= L.y) || (L.dir == 2 && holder.y <= L.y) || (L.dir == 4 && holder.x >= L.x) || (L.dir == 8 && holder.x <= L.x)) //eliminate targets facing the pakkun's direction + our_targets -= list_target + continue + if(abs(holder.x - L.x)>6 || abs(holder.y - L.y)>6) //finally, pakkuns on the very very edge of the screen won't target you our_targets -= list_target continue if(istype(holder, /mob/living/simple_mob)) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/raptor.dm b/code/modules/mob/living/simple_mob/subtypes/vore/raptor.dm index 5adb15f865..95769934ee 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/raptor.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/raptor.dm @@ -147,3 +147,13 @@ remove_eyes() add_eyes() update_transform() + +/mob/living/simple_mob/vore/raptor/yellow + name = "raptor" + desc = "A massive yellow raptor with piercing blue eyes and a feathered tail. Long sharp claws and huge pointy teeth, I wouldn't want to upset this thing." + icon_dead = "raptoryellow_dead" + icon_living = "raptoryellow" + icon_state = "raptoryellow" + icon_rest = "raptoryellow_rest" + random_skin = 0 + initial_icon = "raptoryellow" diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index cb66434897..bdf6ee00ae 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -42,7 +42,6 @@ lastarea = get_area(src) set_focus(src) // VOREStation Add - Key Handling hook_vr("mob_new",list(src)) //VOREStation Code - update_transform() // Some mobs may start bigger or smaller than normal. return ..() /mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) @@ -1198,12 +1197,24 @@ return 0 //Exploitable Info Update +/obj + var/datum/weakref/exploit_for //if this obj is an exploit for somebody, this points to them /mob/proc/amend_exploitable(var/obj/item/I) if(istype(I)) exploit_addons |= I var/exploitmsg = html_decode("\n" + "Has " + I.name + ".") exploit_record += exploitmsg + I.exploit_for = WEAKREF(src) + + +/obj/Destroy() + if(exploit_for) + var/mob/exploited = exploit_for.resolve() + exploited?.exploit_addons -= src + exploit_for = null + . = ..() + /client/proc/check_has_body_select() return mob && mob.hud_used && istype(mob.zone_sel, /obj/screen/zone_sel) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 797854a283..61d9c6020d 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -85,10 +85,12 @@ /proc/is_admin(var/mob/user) return check_rights(R_ADMIN|R_EVENT, 0, user) != 0 - +/** + * Moved into its own file as part of port from CHOMP. + * /proc/hsl2rgb(h, s, l) return //TODO: Implement - +*/ /* Miss Chance */ diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm index b3850deaa2..a8ea0ebfeb 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm @@ -164,6 +164,13 @@ do_colouration = 1 color_blend_mode = ICON_MULTIPLY +/datum/sprite_accessory/ears/dangles_robot + name = "synth dangle antennae, colorable" + desc = "" + icon_state = "dangleantennae_robot" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/ears/oni_h1 name = "oni horns" desc = "" @@ -296,6 +303,15 @@ color_blend_mode = ICON_MULTIPLY extra_overlay = "kittyinner" +/datum/sprite_accessory/ears/catbig + name = "cat, big, colorable" + desc = "" + icon_state = "catbig" + extra_overlay = "catbig-inner" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + + /datum/sprite_accessory/ears/bunnyhc name = "bunny, colorable" desc = "" @@ -303,6 +319,15 @@ do_colouration = 1 color_blend_mode = ICON_MULTIPLY +/datum/sprite_accessory/ears/hare + name = "hare, colorable" + desc = "" + icon_state = "hare" + extra_overlay = "hare-inner" + extra_overlay2 = "hare-tips" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/ears/antlers name = "antlers" desc = "" @@ -340,6 +365,14 @@ do_colouration = 1 color_blend_mode = ICON_MULTIPLY +/datum/sprite_accessory/ears/deeralt + name = "deer ears, alt" + desc = "" + icon_state = "deer_alt" + extra_overlay = "deer_alt-inner" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/ears/cow name = "cow, horns" desc = "" @@ -464,6 +497,13 @@ color_blend_mode = ICON_MULTIPLY extra_overlay = "fox-inner" +/datum/sprite_accessory/ears/dog_floppy + name = "floppy dog ears, colorable" + desc = "" + icon_state = "dog_flop" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/ears/bunny_floppy name = "floopy bunny ears (colorable)" desc = "" diff --git a/code/modules/mob/new_player/sprite_accessories_tail_vr.dm b/code/modules/mob/new_player/sprite_accessories_tail_vr.dm index 87beb3a6a4..b71cede427 100644 --- a/code/modules/mob/new_player/sprite_accessories_tail_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_tail_vr.dm @@ -101,6 +101,24 @@ icon_state = "bunny" do_colouration = 1 +/datum/sprite_accessory/tail/rabbit + name = "rabbit, colourable (vwag)" + desc = "" + icon_state = "rabbit" + do_colouration = TRUE + color_blend_mode = ICON_MULTIPLY + ani_state = "rabbit_w" + +/datum/sprite_accessory/tail/rabbitalt + name = "rabbit, dual color (vwag)" + desc = "" + icon_state = "rabbitalt" + extra_overlay = "rabbitalt-tips" + do_colouration = TRUE + color_blend_mode = ICON_MULTIPLY + ani_state = "rabbitalt_w" + extra_overlay_w = "rabbitalt-tips_w" + /datum/sprite_accessory/tail/bear_brown name = "bear, brown" desc = "" @@ -428,6 +446,19 @@ extra_overlay = "tigertail_mark" extra_overlay_w = "tigertail_mark_w" +/datum/sprite_accessory/tail/tigeralt + name = "tiger tail, alt (vwag)" + desc = "" + icon_state = "tigeralt" + extra_overlay = "tigeralt-tips" + extra_overlay2 = "tigeralt-tips2" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + ani_state = "tigeralt_w" + extra_overlay_w = "tigeralt-tips_w" + extra_overlay2_w = "tigeralt-tips2_w" + + /datum/sprite_accessory/tail/vulp_new name = "new vulp tail (vwag)" desc = "" @@ -499,6 +530,14 @@ color_blend_mode = ICON_MULTIPLY extra_overlay = "deertail_mark" +/datum/sprite_accessory/tail/deeralt + name = "deer alt, colorable (vwag)" + desc = "" + icon_state = "deeralt" + ani_state = "deeralt_w" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/tail/tesh_feathered name = "Teshari tail" desc = "" @@ -1467,4 +1506,21 @@ icon_state = "spiketail" do_colouration = 1 color_blend_mode = ICON_MULTIPLY - extra_overlay = "spiketail-1" \ No newline at end of file + extra_overlay = "spiketail-1" + +/datum/sprite_accessory/tail/longtail/kaiju_tail_a_long + name = "Kaiju tail A, dual color" + icon = 'icons/mob/vore/taurs_vr.dmi' + icon_state = "kaiju_long_a" + extra_overlay = "kaiju_long_a_spikes" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + +/datum/sprite_accessory/tail/longtail/kaiju_tail_a_long_glow + name = "Kaiju tail A, dual color, glow" + icon = 'icons/mob/vore/taurs_vr.dmi' + icon_state = "kaiju_long_a" + extra_overlay = "kaiju_long_a_spikes" + extra_overlay2 = "kaiju_long_a_spikes_glow" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY diff --git a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm index 49f051662f..17e716fd4e 100644 --- a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm @@ -266,7 +266,7 @@ icon_sprite_tag = "deer" can_loaf = TRUE icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi' - loaf_offset = 6 + loaf_offset = 7 msg_owner_disarm_run = "You quickly push %prey to the ground with your hoof!" msg_prey_disarm_run = "%owner pushes you down to the ground with their hoof!" @@ -283,6 +283,17 @@ msg_owner_grab_fail = "You step down onto %prey, squishing them and forcing them down to the ground!" msg_prey_grab_fail = "%owner steps down and squishes you with their hoof, forcing you down to the ground!" +/datum/sprite_accessory/tail/taur/deer/fatdeer + name = "Fat Deer (Dual-color Taur)" + icon_state = "fatdeer_s" + extra_overlay = "fatdeer_markings" + +/datum/sprite_accessory/tail/taur/deer/deer_wag + name = "Deer vwag (Dual-color, Taur, Fat)" + icon_state = "deer_s" + ani_state = "fatdeer_s" + extra_overlay_w = "fatdeer_markings" + /datum/sprite_accessory/tail/taur/lizard name = "Lizard (Taur)" icon_state = "lizard_s" diff --git a/code/modules/mob/new_player/sprite_accessories_wing_vr.dm b/code/modules/mob/new_player/sprite_accessories_wing_vr.dm index 1f85bd1566..1e8f6d4919 100644 --- a/code/modules/mob/new_player/sprite_accessories_wing_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_wing_vr.dm @@ -374,4 +374,19 @@ icon_state = "pterodactyl_wing_frame" do_colouration = 1 color_blend_mode = ICON_MULTIPLY - extra_overlay = "pterodactyl_wing_membrane" \ No newline at end of file + extra_overlay = "pterodactyl_wing_membrane" + +/datum/sprite_accessory/wing/kaiju_spines_a + name = "kaiju spines A, colorable" + desc = "" + icon_state = "kaiju_spikes_a" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + +/datum/sprite_accessory/wing/kaiju_spines_a_glow + name = "kaiju spines A, colorable, glow" + desc = "" + icon_state = "kaiju_spikes_a" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + extra_overlay = "kaiju_spikes_a_glow" \ No newline at end of file diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index ce58e3d851..1956139fce 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -200,6 +200,8 @@ for(var/mob/M as anything in vis_mobs) if(isnewplayer(M)) continue + if(src.client && M && !(get_z(src) == get_z(M))) + message = "[message]" if(isobserver(M) && (!M.is_preference_enabled(/datum/client_preference/ghost_see_whisubtle) || \ !is_preference_enabled(/datum/client_preference/whisubtle_vis) && !M.client?.holder)) spawn(0) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index e0b3f36a11..e46fbbd571 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -58,10 +58,15 @@ to_chat(src, "You stopped swimming downwards.") return 0 - else if(!destination.CanZPass(src, direction)) + else if(!destination.CanZPass(src, direction)) // one for the down and non-special case to_chat(src, "\The [destination] blocks your way.") return 0 + else if(!destination.CanZPass(src, direction)) // and one for up + to_chat(src, "\The [destination] blocks your way.") + return 0 + + var/area/area = get_area(src) if(area.has_gravity() && !can_overcome_gravity()) if(direction == UP) diff --git a/code/modules/multiz/stairs.dm b/code/modules/multiz/stairs.dm index ee6ad2e0ab..fd9702b9de 100644 --- a/code/modules/multiz/stairs.dm +++ b/code/modules/multiz/stairs.dm @@ -214,11 +214,15 @@ if(L.buckled) L.buckled.forceMove(get_turf(top)) + var/atom/movable/P = null + if(L.pulling && !L.pulling.anchored) + P = L.pulling + P.forceMove(get_turf(L)) + L.forceMove(get_turf(top)) // If the object is pulling or grabbing anything, we'll want to move those too. A grab chain may be disrupted in doing so. - if(L.pulling && !L.pulling.anchored) - var/atom/movable/P = L.pulling + if(P) P.forceMove(get_turf(top)) L.continue_pulling(P) diff --git a/code/modules/news/newspaper.dm b/code/modules/news/newspaper.dm index d58b9446b2..394ce7be26 100644 --- a/code/modules/news/newspaper.dm +++ b/code/modules/news/newspaper.dm @@ -27,7 +27,7 @@ switch(screen) if(0) //Cover dat+="
The Griffon
" - dat+="
[using_map.company_name]-standard newspaper, for use on [using_map.company_name]� Space Facilities

" + dat+="
[using_map.company_name]-standard newspaper, for use on [using_map.company_name] Space Facilities

" if(isemptylist(news_content)) if(important_message) dat+="Contents:
    **Important Security Announcement** \[page [pages+2]\]
" diff --git a/code/modules/nifsoft/nif.dm b/code/modules/nifsoft/nif.dm index c64e196df5..46f9e9b57d 100644 --- a/code/modules/nifsoft/nif.dm +++ b/code/modules/nifsoft/nif.dm @@ -463,7 +463,10 @@ You can also set the stat of a NIF to NIF_TEMPFAIL without any issues to disable //Uninstall a piece of software /obj/item/device/nif/proc/uninstall(var/datum/nifsoft/old_soft) - var/datum/nifsoft/NS = nifsofts[old_soft.list_pos] + var/datum/nifsoft/NS + if(nifsofts) + NS = nifsofts[old_soft.list_pos] + if(!NS || NS != old_soft) return FALSE //what?? diff --git a/code/modules/overmap/ships/computers/engine_control.dm b/code/modules/overmap/ships/computers/engine_control.dm index ab1bb61b74..e2b2defea8 100644 --- a/code/modules/overmap/ships/computers/engine_control.dm +++ b/code/modules/overmap/ships/computers/engine_control.dm @@ -62,7 +62,7 @@ . = TRUE if("set_global_limit") - var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100, 100, 0) + var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100, 100, 0, round_value = FALSE) if(tgui_status(usr, state) != STATUS_INTERACTIVE) return FALSE linked.thrust_limit = clamp(newlim/100, 0, 1) @@ -78,7 +78,7 @@ if("set_limit") var/datum/ship_engine/E = locate(params["engine"]) - var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit(), 100, 0) + var/newlim = tgui_input_number(usr, "Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit(), 100, 0, round_value = FALSE) if(tgui_status(usr, state) != STATUS_INTERACTIVE) return FALSE var/limit = clamp(newlim/100, 0, 1) @@ -100,4 +100,4 @@ . = TRUE if(. && !issilicon(usr)) - playsound(src, "terminal_type", 50, 1) \ No newline at end of file + playsound(src, "terminal_type", 50, 1) diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm index 95657cb43c..518d3955a5 100644 --- a/code/modules/overmap/ships/computers/helm.dm +++ b/code/modules/overmap/ships/computers/helm.dm @@ -217,13 +217,13 @@ GLOBAL_LIST_EMPTY(all_waypoints) . = TRUE if("speedlimit") - var/newlimit = tgui_input_number(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000, 100000) + var/newlimit = tgui_input_number(usr, "Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000, 100000, round_value = FALSE) if(newlimit) speedlimit = CLAMP(newlimit/1000, 0, 100) . = TRUE if("accellimit") - var/newlimit = tgui_input_number(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000) + var/newlimit = tgui_input_number(usr, "Input new acceleration limit", "Acceleration limit", accellimit*1000, round_value = FALSE) if(newlimit) accellimit = max(newlimit/1000, 0) . = TRUE diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm index 5990c6f146..33fb4219f8 100644 --- a/code/modules/overmap/ships/computers/sensors.dm +++ b/code/modules/overmap/ships/computers/sensors.dm @@ -106,7 +106,7 @@ if(sensors) switch(action) if("range") - var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range, world.view) + var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range, world.view, round_value = FALSE ) if(tgui_status(usr, state) != STATUS_INTERACTIVE) return FALSE if(nrange) diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 1d51629705..64a0e611e6 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -26,6 +26,7 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins var/sendcooldown = 0 // to avoid spamming fax messages var/department = "Unknown" // our department var/destination = null // the department we're sending to + var/talon = 0 // So that the talon can access their own crew roles for the request /obj/machinery/photocopier/faxmachine/New() allfaxes += src @@ -85,12 +86,18 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins var/list/jobs = list() for(var/datum/department/dept as anything in SSjob.get_all_department_datums()) - if(!dept.assignable || dept.centcom_only) - continue - for(var/job in SSjob.get_job_titles_in_department(dept.name)) - var/datum/job/J = SSjob.get_job(job) - if(J.requestable) - jobs |= job + if(!src.talon) + if(!dept.assignable || dept.centcom_only) + continue + for(var/job in SSjob.get_job_titles_in_department(dept.name)) + var/datum/job/J = SSjob.get_job(job) + if(J.requestable) + jobs |= job + else + for(var/job in SSjob.get_job_titles_in_department(dept.name)) + var/datum/job/J = SSjob.get_job(job) + if(J.offmap_spawn) + jobs |= job var/role = tgui_input_list(L, "Pick the job to request.", "Job Request", jobs) if(!role) @@ -131,8 +138,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins ping_name = "Expedition" if(DEPARTMENT_SYNTHETIC) ping_name = "Silicon" - //if(DEPARTMENT_TALON) - // ping_name = "Offmap" + if(DEPARTMENT_TALON) + ping_name = "Offmap" if(!ping_name) to_chat(L, "Selected job cannot be requested for \[ERRORUNKNOWNDEPT] reason. Please report this to system administrator.") return diff --git a/code/modules/persistence/storage/smartfridge.dm b/code/modules/persistence/storage/smartfridge.dm index ef7b643ceb..addb37ed16 100644 --- a/code/modules/persistence/storage/smartfridge.dm +++ b/code/modules/persistence/storage/smartfridge.dm @@ -25,12 +25,17 @@ store_per_type = TRUE target_type = /obj/machinery/smartfridge/sheets + var/min_retained = 75 //minimum percentage of current stock for retention + var/max_retained = 80 //maximum percentage of current stock for retention var/stacks_go_missing = FALSE // Variable rate depletion of stacks inter-round + var/minimum_storage_reserve = FALSE // ...but still try to maintain a minimum reserve? /datum/persistent/storage/smartfridge/sheet_storage/lossy name = "sheet storage lossy" - max_storage = 250 + min_storage = 20 //if the amount is at or below this, don't cull + max_storage = 500 //if the amount is above this, cull it to this amount THEN do math stacks_go_missing = TRUE + minimum_storage_reserve = TRUE /datum/persistent/storage/smartfridge/sheet_storage/variable_max name = "variable max storage" @@ -65,8 +70,10 @@ // Delete some stacks if we want if(stacks_go_missing) - var/fuzzy = rand(55,65)*0.01 // loss of 35-45% with rounding down - count = round(count*fuzzy) + var/fuzzy = rand(min_retained,max_retained)*0.01 // loss of 35-45% with rounding down + if(!minimum_storage_reserve || (count > min_storage && minimum_storage_reserve)) + count = round(count*fuzzy) + if(count <= 0) continue diff --git a/code/modules/persistence/storage/storage.dm b/code/modules/persistence/storage/storage.dm index a1b16e39b1..d430ee8cda 100644 --- a/code/modules/persistence/storage/storage.dm +++ b/code/modules/persistence/storage/storage.dm @@ -9,6 +9,7 @@ entry_decay_weight = 0 // // // // + var/min_storage = 0 var/max_storage = 0 var/store_per_type = FALSE // If true, will store up to max_storage for each type stored var/target_type = null // Path of the thing that this expects to put stuff into diff --git a/code/modules/power/sensors/powernet_sensor.dm b/code/modules/power/sensors/powernet_sensor.dm index 6dd58ea102..0cfe7ca95a 100644 --- a/code/modules/power/sensors/powernet_sensor.dm +++ b/code/modules/power/sensors/powernet_sensor.dm @@ -247,7 +247,7 @@ APC_entry["total_load"] = reading_to_text(A.lastused_total) // Hopefully removes those goddamn \improper s which are screwing up the UI var/N = A.area.name - if(findtext(N, "�")) + if(findtext(N, "\improper")) N = copytext(N, 3) APC_entry["name"] = N // Add data into main list of APC data. @@ -266,8 +266,3 @@ data["load_percentage"] = 100 data["alarm"] = powernet.problem ? 1 : 0 return data - - - - - diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 612a19d63f..60c42b037c 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -267,7 +267,7 @@ GLOBAL_LIST_EMPTY(solars_list) var/targetdir = 0 // target angle in manual tracking (since it updates every game minute) var/track = 0 // 0= off 1=timed 2=auto (tracker) var/trackrate = 600 // 300-900 seconds - var/nexttime = 0 // time for a panel to rotate of 1� in manual tracking + var/nexttime = 0 // time for a panel to rotate of 1° in manual tracking var/obj/machinery/power/tracker/connected_tracker = null var/needs_panel_check // Powernet has been updated, need to check if panels are still connected. var/connected_power // Sum of power supplied by connected panels. @@ -451,9 +451,9 @@ GLOBAL_LIST_EMPTY(solars_list) connected_tracker.unset_control() if(track==1 && trackrate) //manual tracking and set a rotation speed - if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1�... + if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1°... targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it - nexttime += 36000/abs(trackrate) //reset the counter for the next 1� + nexttime += 36000/abs(trackrate) //reset the counter for the next 1° if(needs_panel_check) for(var/obj/machinery/power/solar/S in connected_panels) diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index dd9f40ce26..c13e19de99 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -239,7 +239,7 @@ SA_vulnerability = SA_ANIMAL hud_state = "rifle_heavy" -/obj/item/projectile/bullet/rifle/a145 // 14.5�114mm is bigger than a .50 BMG round. +/obj/item/projectile/bullet/rifle/a145 // 14.5×114mm is bigger than a .50 BMG round. fire_sound = 'sound/weapons/Gunshot_cannon.ogg' // This is literally an anti-tank rifle caliber. It better sound like a fucking cannon. damage = 80 stun = 3 @@ -470,4 +470,4 @@ . = ..() var/turf/T = get_turf(loc) if(istype(T)) - new /obj/item/ammo_casing/afoam_dart/riot(get_turf(loc)) \ No newline at end of file + new /obj/item/ammo_casing/afoam_dart/riot(get_turf(loc)) diff --git a/code/modules/reagents/reagents/food_drinks.dm b/code/modules/reagents/reagents/food_drinks.dm index 5397168fd4..4baf2788cd 100644 --- a/code/modules/reagents/reagents/food_drinks.dm +++ b/code/modules/reagents/reagents/food_drinks.dm @@ -58,10 +58,10 @@ if(!M.isSynthetic()) if(!(M.species.allergens & allergen_type)) //assuming it doesn't cause a horrible reaction, we'll be ok! M.heal_organ_damage(0.5 * removed, 0) - M.adjust_nutrition((nutriment_factor * removed) * M.species.organic_food_coeff) + M.adjust_nutrition(((nutriment_factor + M.food_preference(allergen_type)) * removed) * M.species.organic_food_coeff) //RS edit M.add_chemical_effect(CE_BLOODRESTORE, 4 * removed) else - M.adjust_nutrition((nutriment_factor * removed) * M.species.synthetic_food_coeff) + M.adjust_nutrition(((nutriment_factor + M.food_preference(allergen_type)) * removed) * M.species.synthetic_food_coeff) //RS edit //VOREStation Edits Stop @@ -957,7 +957,8 @@ /datum/reagent/drink/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed) if(!(M.species.allergens & allergen_type)) - M.adjust_nutrition(nutrition * removed) + var/bonus = M.food_preference(allergen_type) + M.adjust_nutrition((nutrition + bonus) * removed) //RS edit M.dizziness = max(0, M.dizziness + adj_dizzy) M.drowsyness = max(0, M.drowsyness + adj_drowsy) M.AdjustSleeping(adj_sleepy) @@ -2585,6 +2586,7 @@ if(M.species.robo_ethanol_drunk || !(M.isSynthetic())) if(alien == IS_DIONA) return + M.adjust_nutrition((M.food_preference(allergen_type) / 2) * removed) //RS edit M.jitteriness = max(M.jitteriness - 3, 0) /datum/reagent/ethanol/beer/lite diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index f748545c26..bd85d9e010 100755 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -44,6 +44,7 @@ won't update every console in existence) but it's more of a hassle to do. Also, var/id = 0 //ID of the computer (for server restrictions). var/sync = 1 //If sync = 0, it doesn't show up on Server Control Console + var/is_public = FALSE //Above mentions the option for public consoles. But for that, we need to remove the sync tab from the console entirely req_access = list(access_research) //Data and setting manipulation requires scientist access. diff --git a/code/modules/research/rdconsole_tgui.dm b/code/modules/research/rdconsole_tgui.dm index 24e2a33421..80f2f1167b 100644 --- a/code/modules/research/rdconsole_tgui.dm +++ b/code/modules/research/rdconsole_tgui.dm @@ -49,6 +49,7 @@ if(!locked && !busy_msg) data["info"] = list( "sync" = sync, + "is_public" = is_public, ) data["info"]["linked_destroy"] = list("present" = FALSE) diff --git a/code/modules/tgchat/chat_verbs.dm b/code/modules/tgchat/chat_verbs.dm index 52d423c3d2..29edc5b227 100644 --- a/code/modules/tgchat/chat_verbs.dm +++ b/code/modules/tgchat/chat_verbs.dm @@ -3,4 +3,4 @@ set name = "Export Chatlog" set desc = "Allows to trigger the chat export" - to_chat(usr, "") + tgui_panel.window.send_message("saveToDiskCommand") diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index fd45f9723d..e85b10092a 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -89,7 +89,7 @@ * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open() - if(!user.client) + if(!user?.client) return FALSE if(window) return FALSE @@ -206,7 +206,7 @@ * optional force bool Send an update even if UI is not interactive. */ /datum/tgui/proc/send_full_update(custom_data, force) - if(!user.client || !initialized || closing) + if(!user?.client || !initialized || closing) return //if(!COOLDOWN_FINISHED(src, refresh_cooldown)) //refreshing = TRUE @@ -229,7 +229,7 @@ * optional force bool Send an update even if UI is not interactive. */ /datum/tgui/proc/send_update(custom_data, force) - if(!user.client || !initialized || closing) + if(!user?.client || !initialized || closing) return var/should_update_data = force || status >= STATUS_UPDATE window.send_message("update", get_payload( @@ -321,7 +321,8 @@ */ /datum/tgui/proc/process_status() var/prev_status = status - status = src_object.tgui_status(user, state) + if(src_object) + status = src_object.tgui_status(user, state) if(parent_ui) status = min(status, parent_ui.status) return prev_status != status diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm index 8dc1e5a6c4..5829003298 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -32,9 +32,9 @@ if(!user.client.prefs.tgui_input_mode) if(encode) if(multiline) - return stripped_multiline_input(user, message, title, default, max_length) + return stripped_multiline_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else - return stripped_input(user, message, title, default, max_length) + return stripped_input(user, message, title, default, PREVENT_CHARACTER_TRIM_LOSS(max_length)) else if(multiline) return input(user, message, title, default) as message|null @@ -159,4 +159,4 @@ /datum/tgui_input_text/proc/set_entry(entry) if(!isnull(entry)) var/converted_entry = encode ? html_encode(entry) : entry - src.entry = trim(converted_entry, max_length) + src.entry = trim(converted_entry, PREVENT_CHARACTER_TRIM_LOSS(max_length)) diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 4fecdd077f..9d71f9c50e 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -72,6 +72,7 @@ /datum/tgui_panel/proc/on_message(type, payload) if(type == "ready") broken = FALSE + // window.send_message("connected", list("round_id" = GLOB.round_id)) // Sends the round ID to the chat, requires round IDs window.send_message("update", list( "config" = list( "client" = list( diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm index b147915454..690f03c08a 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -71,6 +71,8 @@ var/tmp/static/list/mode_flag_list = list("Numbing" = DM_FLAG_NUMBING, "Stripping" = DM_FLAG_STRIPPING, "Leave Remains" = DM_FLAG_LEAVEREMAINS, "Muffles" = DM_FLAG_THICKBELLY, "Affect Worn Items" = DM_FLAG_AFFECTWORN, "Jams Sensors" = DM_FLAG_JAMSENSORS, "Complete Absorb" = DM_FLAG_FORCEPSAY) //Item related modes var/tmp/static/list/item_digest_modes = list(IM_HOLD,IM_DIGEST_FOOD,IM_DIGEST) + //drain modes + var/tmp/static/list/drainmodes = list(DR_NORMAL,DR_SLEEP,DR_FAKE,DR_WEIGHT) //List of slots that stripping handles strips var/tmp/static/list/slots = list(slot_back,slot_handcuffed,slot_l_store,slot_r_store,slot_wear_mask,slot_l_hand,slot_r_hand,slot_wear_id,slot_glasses,slot_gloves,slot_head,slot_shoes,slot_belt,slot_wear_suit,slot_w_uniform,slot_s_store,slot_l_ear,slot_r_ear) @@ -79,6 +81,7 @@ var/tmp/digest_mode = DM_HOLD // Current mode the belly is set to from digest_modes (+transform_modes if human) var/tmp/list/items_preserved = list() // Stuff that wont digest so we shouldn't process it again. var/tmp/recent_sound = FALSE // Prevent audio spam + var/tmp/drainmode = DR_NORMAL // Simply drains the prey then does nothing. // Don't forget to watch your commas at the end of each line if you change these. var/list/struggle_messages_outside = list( @@ -339,6 +342,7 @@ "belly_mob_mult", "belly_item_mult", "belly_overall_mult", + "drainmode", ) if (save_digest_mode == 1) @@ -525,6 +529,8 @@ for(var/atom/movable/AM as anything in contents) if(isliving(AM)) var/mob/living/L = AM + if(L.stat) + L.SetSleeping(min(L.sleeping,20)) if(L.absorbed && !include_absorbed) continue count += release_specific_contents(AM, silent = TRUE) @@ -610,6 +616,12 @@ absorbed_count++ Pred.bloodstr.trans_to(Prey, Pred.reagents.total_volume / absorbed_count) + //Makes it so that if prey are heavily asleep, they will wake up shortly after release + if(isliving(M)) + var/mob/living/ML = M + if(ML.stat) + ML.SetSleeping(min(ML.sleeping,20)) + //Clean up our own business if(!ishuman(owner)) owner.update_icons() diff --git a/code/modules/vore/eating/bellymodes_vr.dm b/code/modules/vore/eating/bellymodes_vr.dm index e7548f6b89..20300924a2 100644 --- a/code/modules/vore/eating/bellymodes_vr.dm +++ b/code/modules/vore/eating/bellymodes_vr.dm @@ -301,6 +301,20 @@ owner.adjust_nutrition((nutrition_percent / 100) * compensation * 4.5 * personal_nutrition_modifier * pred_digestion_efficiency) /obj/belly/proc/steal_nutrition(mob/living/L) + if(L.nutrition <= 110) + if(drainmode == DR_SLEEP && istype(L,/mob/living/carbon/human)) //Slowly put prey to sleep + if(L.tiredness <= 105) + L.tiredness = (L.tiredness + 6) + if(L.tiredness <= 90 && L.tiredness >= 75) + to_chat(L, "You are about to fall unconscious!") + to_chat(owner, "[L] is about to fall unconscious!") + if(drainmode == DR_FAKE && istype(L,/mob/living/carbon/human)) //Slowly bring prey to the edge of sleep without crossing it + if(L.tiredness <= 93) + L.tiredness = (L.tiredness + 6) + if(drainmode == DR_WEIGHT && istype(L,/mob/living/carbon/human)) //Slowly drain your prey's weight and add it to your own + if(L.weight > 70) + L.weight -= (0.01 * L.weight_loss) + owner.weight += (0.01 * L.weight_loss) //intentionally dependant on the prey's weight loss ratio rather than the preds weight gain to keep them in pace with one another. if(L.nutrition >= 100) var/oldnutrition = (L.nutrition * 0.05) L.nutrition = (L.nutrition * 0.95) diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index b9593edb63..34b87cbb58 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -33,6 +33,7 @@ 'sound/effects/mob_effects/xenochimera/regen_3.ogg', 'sound/effects/mob_effects/xenochimera/regen_5.ogg' ) + var/trash_catching = FALSE //Toggle for trash throw vore from chompstation // // Hook for generic creation of stuff on new creatures @@ -446,6 +447,7 @@ absorbed = FALSE //Make sure we're not absorbed muffled = FALSE //Removes Muffling forceMove(get_turf(src)) //Just move me up to the turf, let's not cascade through bellies, there's been a problem, let's just leave. + SetSleeping(0) //Wake up instantly if asleep for(var/mob/living/simple_mob/SA in range(10)) LAZYSET(SA.prey_excludes, src, world.time) log_and_message_admins("[key_name(src)] used the OOC escape button to get out of [key_name(B.owner)] ([B.owner ? "JMP" : "null"])") @@ -867,6 +869,13 @@ to_chat(src, "This item is not appropriate for ethical consumption.") return +/mob/living/proc/toggle_trash_catching() //Ported from chompstation + set name = "Toggle Trash Catching" + set category = "Abilities" + set desc = "Toggle Trash Eater throw vore abilities." + trash_catching = !trash_catching + to_chat(src, "Trash catching [trash_catching ? "enabled" : "disabled"].") + /mob/living/proc/eat_minerals() //Actual eating abstracted so the user isn't given a prompt due to an argument in this verb. set name = "Eat Minerals" set category = "Abilities" diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 634072c453..9703b537e3 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -225,6 +225,7 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", "belly_mob_mult" = selected.belly_mob_mult, "belly_item_mult" = selected.belly_item_mult, "belly_overall_mult" = selected.belly_overall_mult, + "drainmode" = selected.drainmode, ) @@ -823,6 +824,8 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", if(ourtarget.absorbable) process_options += "Absorb" + process_options += "Knockout" //Can't think of any mechanical prefs that would restrict this. Even if they are already asleep, you may want to make it permanent. + if(process_options.len) process_options += "Cancel" else @@ -878,6 +881,16 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", var/n = 0 - ourtarget.nutrition ourtarget.adjust_nutrition(n) b.absorb_living(ourtarget) + if("Knockout") + if(tgui_alert(ourtarget, "\The [usr] is attempting to instantly make you unconscious, you will be unable until ejected from the pred. Is this something you are okay with happening to you?","Instant Knockout", list("No", "Yes")) != "Yes") + to_chat(usr, "\The [ourtarget] declined your knockout attempt.") + to_chat(ourtarget, "You declined the knockout attempt.") + return + if(ourtarget.loc != b) + to_chat(usr, "\The [ourtarget] is no longer in \the [b].") + return + ourtarget.AdjustSleeping(500000) + to_chat(ourtarget, "\The [usr] has put you to sleep, you will remain unconscious until ejected from the belly.") if("Cancel") return if("Health Check") @@ -1502,6 +1515,14 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", var/new_new_damage = CLAMP(new_damage, 0, 6) host.vore_selected.digest_clone = new_new_damage . = TRUE + if("b_drainmode") + var/list/menu_list = host.vore_selected.drainmodes.Copy() + var/new_drainmode = tgui_input_list(usr, "Choose Mode (currently [host.vore_selected.digest_mode])", "Mode Choice", menu_list) + if(!new_drainmode) + return FALSE + + host.vore_selected.drainmode = new_drainmode + host.vore_selected.updateVRPanels() if("b_emoteactive") host.vore_selected.emote_active = !host.vore_selected.emote_active . = TRUE diff --git a/code/modules/vore/fluffstuff/custom_clothes_vr.dm b/code/modules/vore/fluffstuff/custom_clothes_vr.dm index 0c2f9afa2a..27e700b82c 100644 --- a/code/modules/vore/fluffstuff/custom_clothes_vr.dm +++ b/code/modules/vore/fluffstuff/custom_clothes_vr.dm @@ -2726,3 +2726,13 @@ Departamental Swimsuits, for general use icon_state = "bigroundglasses" slot_flags = SLOT_EYES | SLOT_EARS glasses_layer_above = TRUE + +//valkaerie: Valkaerie Stoze + +/obj/item/clothing/ears/earring/fluff/valkhorns + name = "valkaerie's horns" + desc = "Curled horns that look that they shouldn't really be pulled off!" + icon = 'icons/vore/custom_clothes_vr.dmi' + icon_state = "valkhorns" + icon_override = 'icons/vore/custom_clothes_vr.dmi' + item_state = "valkhorns_onmob" diff --git a/icons/inventory/accessory/item.dmi b/icons/inventory/accessory/item.dmi index 2fc09f9efb..22d585b770 100644 Binary files a/icons/inventory/accessory/item.dmi and b/icons/inventory/accessory/item.dmi differ diff --git a/icons/inventory/accessory/mob.dmi b/icons/inventory/accessory/mob.dmi index dae029fcde..a8281d1a2e 100644 Binary files a/icons/inventory/accessory/mob.dmi and b/icons/inventory/accessory/mob.dmi differ diff --git a/icons/inventory/face/item_vr.dmi b/icons/inventory/face/item_vr.dmi index 073f1df938..8e7dce114a 100644 Binary files a/icons/inventory/face/item_vr.dmi and b/icons/inventory/face/item_vr.dmi differ diff --git a/icons/mob/alienanimals_x32.dmi b/icons/mob/alienanimals_x32.dmi index f118a398d6..aafc75a89c 100644 Binary files a/icons/mob/alienanimals_x32.dmi and b/icons/mob/alienanimals_x32.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/bishop/bishop_proto.dmi b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto.dmi new file mode 100644 index 0000000000..b5551b5e0a Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_f.dmi b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_f.dmi new file mode 100644 index 0000000000..aa4b37228a Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_f.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_s.dmi b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_s.dmi new file mode 100644 index 0000000000..ed13012a5e Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/bishop/bishop_proto_s.dmi differ diff --git a/icons/mob/human_races/sprite_accessories/ears.dmi b/icons/mob/human_races/sprite_accessories/ears.dmi index 73b647783c..3c96e4a56f 100644 Binary files a/icons/mob/human_races/sprite_accessories/ears.dmi and b/icons/mob/human_races/sprite_accessories/ears.dmi differ diff --git a/icons/mob/human_races/sprite_accessories/wings.dmi b/icons/mob/human_races/sprite_accessories/wings.dmi index 2042d667f2..0251084ee9 100644 Binary files a/icons/mob/human_races/sprite_accessories/wings.dmi and b/icons/mob/human_races/sprite_accessories/wings.dmi differ diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi index f49006df1c..a7034bc2d2 100644 Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ diff --git a/icons/mob/vore/tails_vr.dmi b/icons/mob/vore/tails_vr.dmi index a2b7e44ffe..81c2cf31b3 100644 Binary files a/icons/mob/vore/tails_vr.dmi and b/icons/mob/vore/tails_vr.dmi differ diff --git a/icons/mob/vore/taurs_vr.dmi b/icons/mob/vore/taurs_vr.dmi index 77be24a36c..50941e247e 100644 Binary files a/icons/mob/vore/taurs_vr.dmi and b/icons/mob/vore/taurs_vr.dmi differ diff --git a/icons/mob/vore/taurs_vr_loaf.dmi b/icons/mob/vore/taurs_vr_loaf.dmi index 5ada44b426..8cbfdc581a 100644 Binary files a/icons/mob/vore/taurs_vr_loaf.dmi and b/icons/mob/vore/taurs_vr_loaf.dmi differ diff --git a/icons/mob/vore/wings_vr.dmi b/icons/mob/vore/wings_vr.dmi index a5ebaa2b71..85bd00e920 100644 Binary files a/icons/mob/vore/wings_vr.dmi and b/icons/mob/vore/wings_vr.dmi differ diff --git a/icons/mob/vore64x64.dmi b/icons/mob/vore64x64.dmi index 9dea7f4b62..40bb47dc19 100644 Binary files a/icons/mob/vore64x64.dmi and b/icons/mob/vore64x64.dmi differ diff --git a/icons/mob/vore_raptor.dmi b/icons/mob/vore_raptor.dmi index 479923d44e..14f2c2a46b 100644 Binary files a/icons/mob/vore_raptor.dmi and b/icons/mob/vore_raptor.dmi differ diff --git a/icons/obj/closets/tent.dmi b/icons/obj/closets/tent.dmi new file mode 100644 index 0000000000..939557b190 Binary files /dev/null and b/icons/obj/closets/tent.dmi differ diff --git a/icons/obj/closets/tentB.dmi b/icons/obj/closets/tentB.dmi new file mode 100644 index 0000000000..a1446d2737 Binary files /dev/null and b/icons/obj/closets/tentB.dmi differ diff --git a/icons/obj/pillows.dmi b/icons/obj/pillows.dmi new file mode 100644 index 0000000000..276e8cb3c5 Binary files /dev/null and b/icons/obj/pillows.dmi differ diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi index adc7011de9..34aee83974 100644 Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ diff --git a/icons/system/blank_32x32.dmi b/icons/system/blank_32x32.dmi new file mode 100644 index 0000000000..4f5867e07e Binary files /dev/null and b/icons/system/blank_32x32.dmi differ diff --git a/icons/vore/custom_clothes_vr.dmi b/icons/vore/custom_clothes_vr.dmi index a94316da76..1afb16a499 100644 Binary files a/icons/vore/custom_clothes_vr.dmi and b/icons/vore/custom_clothes_vr.dmi differ diff --git a/maps/gateway_archive_vr/zoo.dmm b/maps/gateway_archive_vr/zoo.dmm index f122e5a0dd..14299b2878 100644 --- a/maps/gateway_archive_vr/zoo.dmm +++ b/maps/gateway_archive_vr/zoo.dmm @@ -645,7 +645,7 @@ "mu" = (/obj/structure/grille,/obj/structure/window/reinforced{dir = 1},/obj/structure/window/reinforced{dir = 4; health = 1e+006},/obj/structure/window/reinforced{dir = 8; health = 1e+006},/turf/simulated/floor/plating,/area/awaymission/zoo) "mv" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "This giant snake is a rare beast native to Paraiso, in the Carnelia system. Though normally only found deep in the jungles, these ravenous monsters have been known to slither out onto the planet's many beach resorts to swallow up unwary sunbathers. Otherwise, the creature's diet consists mainly of monkeys that were accidentally introduced to the planet by pet owners. The presence of the invasive monkey species had caused giant snake populations to swell in the 2530's, but today, due to excessive hunting, the giant snake's numbers have dwindled to the point of being an endangered species."; name = "Giant Snake Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "mw" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "Catgirls (not to be confused with Nekos, an interspecies hybrid born of a union between Human and Tajaran parents) were created artificially with the intention of producing a Human-feline hybrid that could serve aboard a space-faring vessel, control pests aboard such ships, and even alleviate stress among male crew members. Their eyes are between 20% and 50% larger than the average Human, which gives them their famously 'Kawaii' appearance. The large eyes trigger a subconscious psychological 'cute' response among Humans, causing them to usually become relaxed in their presence. Some catgirls have learned to abuse this trait, and have evolved to prey upon Humans who linger too close for too long."; name = "Catgirl Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) -"mx" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"mx" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "my" = (/obj/structure/window/phoronreinforced{dir = 8; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/window/phoronreinforced{dir = 1; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/window/phoronreinforced{dir = 4; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/grille,/turf/simulated/floor/plating,/area/awaymission/zoo) "mz" = (/obj/structure/window/phoronreinforced{dir = 8; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/window/phoronreinforced{maxhealth = 10000; name = "robust borosilicate window"},/obj/structure/grille,/turf/simulated/floor/plating,/area/awaymission/zoo) "mA" = (/obj/structure/window/phoronreinforced{dir = 4; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/window/phoronreinforced{dir = 1; icon_state = "phoronrwindow"; maxhealth = 10000; name = "robust borosilicate window";},/obj/structure/grille,/turf/simulated/floor/plating,/area/awaymission/zoo) @@ -729,7 +729,7 @@ "oa" = (/obj/effect/overlay/palmtree_r,/turf/simulated/floor/holofloor/beach/sand{icon_state = "fullgrass2"},/area/awaymission/zoo) "ob" = (/obj/structure/flora/ausbushes/lavendergrass,/turf/simulated/floor/holofloor/beach/sand{icon_state = "dgrass3"},/area/awaymission/zoo) "oc" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "Catgirls (not to be confused with Nekos, an interspecies hybrid born of a union between Human and Tajaran parents) were created artificially with the intention of producing a Human-feline hybrid that could serve aboard a space-faring vessel, control pests aboard such ships, and even alleviate stress among male crew members. Their eyes are between 20% and 50% larger than the average Human, which gives them their famously 'Kawaii' appearance. The large eyes trigger a subconscious psychological 'cute' response among Humans, causing them to usually become relaxed in their presence. Some catgirls have learned to abuse this trait, and have evolved to prey upon Humans who linger too close for too long."; name = "Catgirl Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) -"od" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"od" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "oe" = (/obj/structure/flora/ausbushes/leafybush,/turf/simulated/floor/holofloor/grass{icon = 'icons/jungle.dmi'; icon_state = "grass2"},/area/awaymission/zoo) "of" = (/mob/living/simple_mob/hostile/frog{faction = "zoo"},/turf/simulated/floor/holofloor/grass{icon = 'icons/jungle.dmi'; icon_state = "grass2"},/area/awaymission/zoo) "og" = (/turf/simulated/floor/holofloor/beach/water{icon_state = "beach"; dir = 10},/area/awaymission/zoo) @@ -783,7 +783,7 @@ "pc" = (/mob/living/simple_mob/catgirl{faction = "zoo"},/turf/simulated/floor/wood,/area/awaymission/zoo) "pd" = (/obj/structure/flora/ausbushes/genericbush,/obj/effect/floor_decal/spline/fancy/wood{icon_state = "spline_fancy"; dir = 10},/turf/simulated/floor/holofloor/grass,/area/awaymission/zoo) "pe" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Space Bumble Bee is a large, usually docile stinging insect native to Virgo-Prime in the Virgo Erigone system. It normally lives in small oases around what few bodies of water exist on the arid landscape, but ever since Humans have come to colonize the world, they have been discovered in city parks even in the capital city of Anur. Despite their gentle demeanor however, they can and will attack when provoked, and are capable of opening their mandibles wide enough to swallow a Human sized victim whole. Deep in their abdomen, they process their victims along with harvested nectar into rich honey."; name = "Space Bumble Bee Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) -"pf" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Sobaka, also known as the Dog Shark, is an amphibious, cold-blooded animal that lives in the tropical waters of Qerrbalak, in the Qerr�valis System. The Sobaka hunts fish and small animals while on land, but would prefer to scavenge than to hunt. Its nose is far more powerful than any canine species, and are able to smell both on land, and in the sea. The Sobaka are commonly employed as drug-sniffing and bomb-sniffing animals by Skrellian security forces. The Sobaka is a close relative of the sentient species called Akula."; name = "Sobaka Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"pf" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Sobaka, also known as the Dog Shark, is an amphibious, cold-blooded animal that lives in the tropical waters of Qerrbalak, in the Qerr'valis System. The Sobaka hunts fish and small animals while on land, but would prefer to scavenge than to hunt. Its nose is far more powerful than any canine species, and are able to smell both on land, and in the sea. The Sobaka are commonly employed as drug-sniffing and bomb-sniffing animals by Skrellian security forces. The Sobaka is a close relative of the sentient species called Akula."; name = "Sobaka Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "pg" = (/mob/living/carbon/human/sharkm{faction = "zoo"},/turf/simulated/floor/holofloor/beach/water,/area/awaymission/zoo) "ph" = (/mob/living/simple_mob/animal/passive/tindalos{faction = "zoo"},/turf/simulated/floor/holofloor/beach/sand{icon_state = "dgrass2"},/area/awaymission/zoo) "pi" = (/obj/structure/flora/ausbushes/fernybush,/turf/simulated/floor/holofloor/beach/sand{icon_state = "fullgrass3"},/area/awaymission/zoo) @@ -901,7 +901,7 @@ "rq" = (/turf/simulated/floor,/area/awaymission/zoo) "rr" = (/obj/effect/floor_decal/spline/fancy/wood{dir = 2},/obj/structure/flora/ausbushes/brflowers,/turf/simulated/floor/grass,/area/awaymission/zoo) "rs" = (/obj/effect/floor_decal/industrial/warning{icon_state = "warning"; dir = 1},/turf/simulated/floor/plating,/area/awaymission/zoo) -"rt" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"rt" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "ru" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "Space spider is a broad classification is used to indicate a subspecies of arachnids discovered in the Tau sector. Extremely dangerous and voracious, this family of arachnids managed to evolve and adapt to grow and thrive in a variety of atmospheric conditions, even the complete lack of an atmosphere altogether. The females, bigger than the males, tend to lay their eggs in the bodies of animals killed during hunts. From there, the young specimens (spiderlings) will feed on the corpse for the first few hours of life until the body is picked clean, after which they will leave the nest to begin hunting for small parasites and anaerobic or aerobic creatures. Once they reach adulthood, their societal structure seems to change, pushing them to hunt in small packs, mostly favoring their numbers and aggressive nature, and inoculating a deadly toxin in their victims before sucking the nutrients dry."; name = "Space Spider Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "rv" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Samak are the apex predator in the various plains regions of the northern hemisphere of Meralar. Unlike many creatures of Meralar, it does not have any fur to speak of. Instead, it is completely covered in thick armored plates which act as a barrier between the Samak's internal warmth and the cold environment. However, this is still not that efficient, so the Samak feed often to keep themselves warm. The Samak have six thick legs tipped with broad claws, and roughly aerodynamic bodies. They burrow underground, detecting prey through incredibly keen thermal detection. They pop up suddenly, grab their next meal, and return underground just as quickly. They prefer larger prey, such as the Baqara, Elu'a Eli, and Tajaran. Due to their voracious appetites and tendency to ruin mine shafts and infrastructure, they have been hunted almost to extinction, mainly by the Slavemasters. For the Tajaran, being able to kill a Samak singlehandedly (and without 'cheating', such as saturation bombing and the like) is an incredible feat, and any individual who pulls it off is lauded greatly."; name = "Samak Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "rw" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "Space bears are the result of reckless experimentation in the early days of interplanetary travel and colonization. Rogue scientists in the beginnings of what is now known as Space Russia got very drunk one night, more than usual, and decided to 'improve' on the terran bear. The result is faster, stronger, and smarter than the original species, but most importantly they are able to survive in the airless void of space without any special equipment. It is unknown if they were originally meant to be living weapons or simply made 'because we can', but the scientists responsible were never heard from again. Now, thanks to faster than light travel and lackluster effort in making sure nothing's riding along, Space Bears can be found living on asteroids and moons across the galaxy, wherever Humanity and friends may roam."; name = "Space Bear Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) @@ -925,7 +925,7 @@ "rO" = (/obj/structure/closet/crate,/obj/effect/floor_decal/industrial/outline/yellow,/turf/simulated/floor/tiled/neutral,/area/awaymission/zoo) "rP" = (/turf/simulated/floor/tiled/neutral,/area/awaymission/zoo) "rQ" = (/obj/structure/flora/grass/both,/turf/simulated/floor/holofloor/snow,/area/awaymission/zoo) -"rR" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"rR" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "rS" = (/mob/living/simple_mob/animal/space/alien/drone{faction = "zoo"},/turf/simulated/floor/holofloor/desert{icon = 'icons/turf/floors.dmi'; icon_state = "asteroidplating"; name = "scorched sand"},/area/awaymission/zoo) "rT" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "Space spider is a broad classification is used to indicate a subspecies of arachnids discovered in the Tau sector. Extremely dangerous and voracious, this family of arachnids managed to evolve and adapt to grow and thrive in a variety of atmospheric conditions, even the complete lack of an atmosphere altogether. The females, bigger than the males, tend to lay their eggs in the bodies of animals killed during hunts. From there, the young specimens (spiderlings) will feed on the corpse for the first few hours of life until the body is picked clean, after which they will leave the nest to begin hunting for small parasites and anaerobic or aerobic creatures. Once they reach adulthood, their societal structure seems to change, pushing them to hunt in small packs, mostly favoring their numbers and aggressive nature, and inoculating a deadly toxin in their victims before sucking the nutrients dry."; name = "Space Spider Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "rU" = (/obj/effect/overlay/palmtree_r,/turf/simulated/floor/holofloor/grass,/area/awaymission/zoo) @@ -1020,7 +1020,7 @@ "tF" = (/obj/structure/flora/ausbushes/grassybush,/turf/simulated/floor/holofloor/wood{icon = 'icons/turf/floors.dmi'; icon_state = "diona"},/area/awaymission/zoo) "tG" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "Decades ago, a group of rogue Skrellian scientists began illegal experimentation on Unathi colonists participating in a nuclear meltdown cleanup effort. In an attempt to create genetically engineered super soldiers, the Deathclaw was born with deadly sharp razor-like claws, incredible agility, robust durability, and high intelligence. When the Skrellian scientists were arrested by the authorities, the Deathclaw was to be destroyed, but some managed to escape captivity into the nuclear wasteland. Normally solitary creatures, they have been known to hunt in packs, and seem to have a preference for Human and Unathi meat."; name = "Deathclaw Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tH" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Space Goose (pural: geese) is an aquatic fowl originating from the planet Earth. This breed of Geese however was bred to the clime of space ships and stations as guard animals. They are extremely loud, highly aggressive, and territorial, while their talons and beaks are much longer and sharper than Earth geese. Their aggressive nature makes them difficult to train, but are highly effective when deployed."; name = "Space Goose Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) -"tI" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43�45 kg (95�99 lb), and females 36�38.5 kg (79�85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"tI" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43-45 kg (95-99 lb), and females 36-38.5 kg (79-85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tJ" = (/obj/structure/grille,/obj/structure/window/reinforced,/obj/structure/window/reinforced{dir = 1},/obj/structure/sign/kiddieplaque{desc = "While all the species of these large predators are commonly known as Dragons (Draconius Regalis), this is mainly an umbrella term for a number of varieties of these large, reptilian creatures. There are numerous varieties of dragons found throughout the galaxy, and recounts of these creatures can be traced back to Skrell civilization's first interplanetary travels speaking of a large, unknown creature attacking and devouring the closeby cargo ships. Mainly solitary creatures, the Dragons roam large areas of space, capable of adapting to a large variety of atmospheres thanks to each sub-species natural affinity to various elements. While in today's population there are a numerous subspecies, all of these can be traced back to what archaeologists claimed to be a 'phoron' Dragon, by the biological analysis of the fossils found on asteroids in the Virgo-Erigone system. The creatures displayed here were reconstituted from such fossils, generously donated by the Virgo Orbital Research Establishment."; name = "Phoron Dragon Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tK" = (/obj/structure/flora/ausbushes/sparsegrass,/turf/simulated/floor/holofloor/desert,/area/awaymission/zoo) "tL" = (/obj/effect/floor_decal/asteroid,/obj/effect/decal/cleanable/greenglow,/turf/simulated/floor/holofloor/desert,/area/awaymission/zoo) @@ -1030,7 +1030,7 @@ "tP" = (/obj/effect/decal/cleanable/greenglow,/turf/simulated/floor/holofloor/desert,/area/awaymission/zoo) "tQ" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "Decades ago, a group of rogue Skrellian scientists began illegal experimentation on Unathi colonists participating in a nuclear meltdown cleanup effort. In an attempt to create genetically engineered super soldiers, the Deathclaw was born with deadly sharp razor-like claws, incredible agility, robust durability, and high intelligence. When the Skrellian scientists were arrested by the authorities, the Deathclaw was to be destroyed, but some managed to escape captivity into the nuclear wasteland. Normally solitary creatures, they have been known to hunt in packs, and seem to have a preference for Human and Unathi meat."; name = "Deathclaw Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tR" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Space Goose (pural: geese) is an aquatic fowl originating from the planet Earth. This breed of Geese however was bred to the clime of space ships and stations as guard animals. They are extremely loud, highly aggressive, and territorial, while their talons and beaks are much longer and sharper than Earth geese. Their aggressive nature makes them difficult to train, but are highly effective when deployed."; name = "Space Goose Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) -"tS" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43�45 kg (95�99 lb), and females 36�38.5 kg (79�85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) +"tS" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43-45 kg (95-99 lb), and females 36-38.5 kg (79-85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tT" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/grille,/obj/structure/sign/kiddieplaque{desc = "While all the species of these large predators are commonly known as Dragons (Draconius Regalis), this is mainly an umbrella term for a number of varieties of these large, reptilian creatures. There are numerous varieties of dragons found throughout the galaxy, and recounts of these creatures can be traced back to Skrell civilization's first interplanetary travels speaking of a large, unknown creature attacking and devouring the closeby cargo ships. Mainly solitary creatures, the Dragons roam large areas of space, capable of adapting to a large variety of atmospheres thanks to each sub-species natural affinity to various elements. While in today's population there are a numerous subspecies, all of these can be traced back to what archaeologists claimed to be a 'phoron' Dragon, by the biological analysis of the fossils found on asteroids in the Virgo-Erigone system. The creatures displayed here were reconstituted from such fossils, generously donated by the Virgo Orbital Research Establishment."; name = "Phoron Dragon Exhibit"},/turf/simulated/floor/plating,/area/awaymission/zoo) "tU" = (/obj/effect/decal/remains/human,/turf/simulated/floor/holofloor/desert{icon = 'icons/turf/floors.dmi'; icon_state = "asteroidplating"; name = "scorched sand"},/area/awaymission/zoo) "tV" = (/mob/living/simple_mob/adultslime{faction = "zoo"},/turf/simulated/floor/holofloor/desert,/area/awaymission/zoo) diff --git a/maps/gateway_vr/zoo.dmm b/maps/gateway_vr/zoo.dmm index c08fdeaed2..6bb8c36d6a 100644 --- a/maps/gateway_vr/zoo.dmm +++ b/maps/gateway_vr/zoo.dmm @@ -4509,7 +4509,7 @@ dir = 1 }, /obj/structure/sign/kiddieplaque{ - desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; + desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit" }, /turf/simulated/floor/plating, @@ -5142,7 +5142,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; + desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit" }, /turf/simulated/floor/plating, @@ -5524,7 +5524,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Sobaka, also known as the Dog Shark, is an amphibious, cold-blooded animal that lives in the tropical waters of Qerrbalak, in the Qerr�valis System. The Sobaka hunts fish and small animals while on land, but would prefer to scavenge than to hunt. Its nose is far more powerful than any canine species, and are able to smell both on land, and in the sea. The Sobaka are commonly employed as drug-sniffing and bomb-sniffing animals by Skrellian security forces. The Sobaka is a close relative of the sentient species called Akula."; + desc = "The Sobaka, also known as the Dog Shark, is an amphibious, cold-blooded animal that lives in the tropical waters of Qerrbalak, in the Qerr'valis System. The Sobaka hunts fish and small animals while on land, but would prefer to scavenge than to hunt. Its nose is far more powerful than any canine species, and are able to smell both on land, and in the sea. The Sobaka are commonly employed as drug-sniffing and bomb-sniffing animals by Skrellian security forces. The Sobaka is a close relative of the sentient species called Akula."; name = "Sobaka Exhibit" }, /turf/simulated/floor/plating, @@ -6312,7 +6312,7 @@ dir = 1 }, /obj/structure/sign/kiddieplaque{ - desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; + desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit" }, /turf/simulated/floor/plating, @@ -6569,7 +6569,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; + desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit" }, /turf/simulated/floor/plating, @@ -7380,7 +7380,7 @@ dir = 1 }, /obj/structure/sign/kiddieplaque{ - desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43�45 kg (95�99 lb), and females 36�38.5 kg (79�85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; + desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43-45 kg (95-99 lb), and females 36-38.5 kg (79-85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit" }, /turf/simulated/floor/plating, @@ -7462,7 +7462,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43�45 kg (95�99 lb), and females 36�38.5 kg (79�85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; + desc = "The Grey Wolf, Scientific name Canis Lupus, is a species native to the Sol system and specifically the planet Earth. It is the largest extant member of its family, with males averaging 43-45 kg (95-99 lb), and females 36-38.5 kg (79-85 lb). Like the red wolf, it is distinguished from other Canis species by its larger size and less pointed features, particularly on the ears and muzzle. Its winter fur is long and bushy, and predominantly a mottled gray in color, although nearly pure white, red, or brown to black also occur. It is also know by the name Timber Wolf. The gray wolf is the second most specialised member of the genus Canis, as demonstrated by its morphological adaptations to hunting large prey, its more gregarious nature, and its highly advanced expressive behavior. It is nonetheless closely related enough to smaller Canis species, such as the Sol Coyote or Golden Jackal to produce fertile hybrids. It is the only species of Canis to have a range encompassing both the Old and New planets, due to being popular as a traveling companion, an easier to tame circus attraction compared to tigers, and its valuable use in ridding polar worlds of large predators. It was also spread through some worlds to deal with Cloud Fox infestations. While normally indifferent to foxes aside from hunting territories, the Grey Wolf has shown great hostility to a special strain of Fox mutation found on the planet Andorss called the Cloud Fox. Bafflingly, these creatures share nothing in common that should lead them to be enemies, as the Grey Wolf is a predator and lives in snowy areas, whereas the Cloud Fox is an herbivore, largely found in Corn rich areas. Scientists have yet to explain this, although several other animals have also been shown to show hostility towards Cloud Foxes, namely the Macromeleon and Carousel Panther, both of whom get along well with Grey Wolves."; name = "Gray Wolf Exhibit" }, /turf/simulated/floor/plating, diff --git a/maps/gateway_vr/zoo_b.dmm b/maps/gateway_vr/zoo_b.dmm index 23ea96b135..0dfcbcb647 100644 --- a/maps/gateway_vr/zoo_b.dmm +++ b/maps/gateway_vr/zoo_b.dmm @@ -1398,7 +1398,7 @@ dir = 1 }, /obj/structure/sign/kiddieplaque{ - desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; + desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit" }, /turf/simulated/floor/plating, @@ -3703,7 +3703,7 @@ dir = 1 }, /obj/structure/sign/kiddieplaque{ - desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; + desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit" }, /turf/simulated/floor/plating, @@ -5168,7 +5168,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr�valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; + desc = "The Ir'ash'uint (translating roughly from Skrellian to 'Giant Frog' in Galactic Common), this amphibian lives on the Skrell homeworld Qerrbalak, in the Qerr'valis System. The Giant Frog shares a common, albeit distant ancestry with the Skrell, but unlike the Skrell, its diet is exclusively carnivorous. Its diet includes large insects, fish, and indeed, even Skrell."; name = "Giant Frog Exhibit" }, /turf/simulated/floor/plating, @@ -5845,7 +5845,7 @@ }, /obj/structure/grille, /obj/structure/sign/kiddieplaque{ - desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7�8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; + desc = "The Xenomorph XX121, better known just as Xenomorph, are an extraterrestrial, endoparasitoid species with multiple life cycles, possibly originating from the planet Proteus (also known as Xenomorph Prime), which orbits the star A6 454. One of the deadliest of all known alien species, these creatures need a host organism in order to reproduce. The appearance of the Xenomorph varies depending on its host. The Human phenotype is generally around 7-8 feet, and roughly 136.0 to 181.4 kilograms in weight, with a long, muscular tail and large, curved, oblong head. The Queen of this species is generally twice as large (they can grow even larger, some even up to 100 feet tall, and stronger if given time) and possesses superior speed, strength and intelligence. The term Xenomorph is derived from the Greek words xeno ('stranger', 'alien', and 'foreigner') and morphe ('form', 'shape'). There are no solid facts as to the origins of the Xenomorph species; instead, there are many assumptions which cannot be confirmed. Based on the limited information we have, the most commonly accepted hypothesis is that they are an artificially created species, although another hypothesis says that they evolved naturally on a planet much different than our own. The Xenomorph lives in a hive together with its queen. Amazingly, the blood and other bodily fluids of the Xenomorph are highly acidic. It is not yet understood how a creature of such biology can exist. The planet Proteus is also home to rare, red-hued Xenomorphs, who frequently fight their black-colored rivals. Some Xenomorphs are alleged to be able to cloak themselves to be invisible to the human eye, but this has not been confirmed by independent study."; name = "Xenomorph Exhibit" }, /turf/simulated/floor/plating, diff --git a/maps/offmap_vr/talon/talon_v2.dm b/maps/offmap_vr/talon/talon_v2.dm index 94af256059..8f174fc6fa 100644 --- a/maps/offmap_vr/talon/talon_v2.dm +++ b/maps/offmap_vr/talon/talon_v2.dm @@ -318,6 +318,7 @@ personally I recommend using the ship's boat if you need to evacuate, but if you /obj/machinery/photocopier/faxmachine/talon department = "ITV Talon" desc = "The ship's fax machine! It's a safe assumption that most of the departments listed aren't on your ship, since the ship only has one." + talon = 1 /obj/item/clothing/head/helmet/space/void/captain/talon name = "talon captain's voidsuit helmet" diff --git a/maps/redgate/eggnogtownunderground.dmm b/maps/redgate/eggnogtownunderground.dmm index d5c5e29c3d..7f4603d53a 100644 --- a/maps/redgate/eggnogtownunderground.dmm +++ b/maps/redgate/eggnogtownunderground.dmm @@ -115,6 +115,10 @@ temperature = 258.15 }, /area/redgate/eggnogtown/vibeout) +"aH" = ( +/obj/machinery/telecomms/relay/preset/station, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "ba" = ( /obj/structure/table/wooden_reinforced, /obj/machinery/chemical_dispenser/bar_alc/full, @@ -224,6 +228,15 @@ temperature = 303.15 }, /area/redgate/eggnogtown/hotsprings) +"fN" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 8 + }, +/obj/machinery/light{ + dir = 8 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "fV" = ( /obj/structure/closet/walllocker{ dir = 1; @@ -328,6 +341,9 @@ temperature = 303.15 }, /area/redgate/eggnogtown/hotsprings) +"kc" = ( +/turf/simulated/wall/shull, +/area/redgate/eggnogtown/telecomms) "kA" = ( /turf/simulated/wall/iron, /area/redgate/eggnogtown/underground) @@ -419,6 +435,12 @@ temperature = 258.15 }, /area/redgate/eggnogtown/hotspring) +"oz" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 10 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "pn" = ( /obj/structure/bed/chair/bay/comfy/red{ dir = 1 @@ -435,6 +457,12 @@ temperature = 303.15 }, /area/redgate/eggnogtown/hotsprings) +"qe" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 1 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "qr" = ( /obj/item/weapon/stool/padded, /turf/simulated/floor/tiled/dark{ @@ -513,6 +541,12 @@ temperature = 258.15 }, /area/redgate/eggnogtown/vibeout) +"tm" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 5 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "tI" = ( /turf/simulated/floor/tiled/dark{ temperature = 258.15 @@ -714,6 +748,14 @@ temperature = 303.15 }, /area/redgate/eggnogtown/hotsprings) +"Gn" = ( +/obj/structure/fans/tiny, +/obj/machinery/door/airlock/hatch{ + name = "Telecomms Access"; + req_one_access = list() + }, +/turf/simulated/floor/tiled/steel_ridged, +/area/redgate/eggnogtown/telecomms) "Gq" = ( /obj/structure/bed/chair/bay/comfy/red, /turf/simulated/floor/plating/eris/under{ @@ -807,6 +849,12 @@ temperature = 258.15 }, /area/redgate/eggnogtown/vibeout) +"LP" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 9 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "Mb" = ( /obj/structure/table/steel, /turf/simulated/floor/tiled{ @@ -851,6 +899,11 @@ temperature = 303.15 }, /area/redgate/eggnogtown/hotsprings) +"MW" = ( +/turf/simulated/mineral/floor/ignore_cavegen/cave{ + temperature = 258.15 + }, +/area/redgate/eggnogtown/telecomms) "Oa" = ( /obj/structure/bed/chair/bay/comfy/black{ dir = 4 @@ -930,6 +983,12 @@ temperature = 258.15 }, /area/redgate/eggnogtown/vibeout) +"Sz" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 6 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "Tx" = ( /obj/structure/table/wooden_reinforced, /obj/item/device/flashlight/lamp/green, @@ -1039,6 +1098,10 @@ temperature = 258.15 }, /area/redgate/eggnogtown/houserhomestead/basement) +"XU" = ( +/obj/effect/floor_decal/industrial/warning, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "XV" = ( /obj/structure/table/steel, /obj/machinery/chemical_dispenser/bar_alc/full{ @@ -1048,6 +1111,15 @@ temperature = 258.15 }, /area/redgate/eggnogtown/houserhomestead/basement) +"Yt" = ( +/obj/effect/floor_decal/industrial/warning{ + dir = 4 + }, +/obj/machinery/light{ + dir = 4 + }, +/turf/simulated/floor/reinforced, +/area/redgate/eggnogtown/telecomms) "YF" = ( /obj/item/weapon/stool/padded, /turf/simulated/floor/tiled{ @@ -7536,11 +7608,11 @@ ab ab ab ab -ab -ab -ab -ab -ab +kc +kc +kc +kc +kc aa ab ab @@ -7678,12 +7750,12 @@ ab ab ab ab -ab -ab -ab -ab -ab -aa +kc +LP +fN +oz +kc +MW ab ab ab @@ -7820,12 +7892,12 @@ ab ab ab ab -ab -ab -ab -ab -ab -aa +kc +qe +aH +XU +Gn +MW aa ab ab @@ -7962,12 +8034,12 @@ ab ab ab ab -ab -ab -ab -ab -ab -aa +kc +tm +Yt +Sz +kc +MW aa ab ab @@ -8104,11 +8176,11 @@ ab ab ab ab -ab -ab -ab -ab -ab +kc +kc +kc +kc +kc aa aa ab diff --git a/maps/southern_cross/southern_cross_areas.dm b/maps/southern_cross/southern_cross_areas.dm index 4d9c76f95f..857f13134e 100644 --- a/maps/southern_cross/southern_cross_areas.dm +++ b/maps/southern_cross/southern_cross_areas.dm @@ -110,13 +110,13 @@ icon_state = "bluenew" /area/surface/outside/river/faxalven - name = "Fax�lven River" + name = "Faxälven River" /area/surface/outside/river/indalsalven - name = "Indals�lven River" + name = "Indalsälven River" /area/surface/outside/river/svartan - name = "Svart�n River" + name = "Svartån River" /area/surface/outside/lake/romsele name = "Romsele Lake" diff --git a/tgui/packages/tgui-panel/chat/constants.ts b/tgui/packages/tgui-panel/chat/constants.ts index 7b2354ca97..e7fbc7cbe5 100644 --- a/tgui/packages/tgui-panel/chat/constants.ts +++ b/tgui/packages/tgui-panel/chat/constants.ts @@ -24,6 +24,7 @@ export const MESSAGE_TYPE_INTERNAL = 'internal'; export const MESSAGE_TYPE_SYSTEM = 'system'; export const MESSAGE_TYPE_LOCALCHAT = 'localchat'; export const MESSAGE_TYPE_NPCEMOTE = 'npcemote'; +export const MESSAGE_TYPE_MULTIZCHAT = 'multizsay'; export const MESSAGE_TYPE_PLOCALCHAT = 'plocalchat'; export const MESSAGE_TYPE_VORE = 'vore'; export const MESSAGE_TYPE_HIVEMIND = 'hivemind'; @@ -64,6 +65,12 @@ export const MESSAGE_TYPES = [ description: 'In-character emotes and says from NPCs', selector: '.npcemote, .npcsay', }, + { + type: MESSAGE_TYPE_MULTIZCHAT, + name: 'MultiZ Emotes / Says', + description: 'In-character emotes and says from levels above/below', + selector: '.multizsay', + }, { type: MESSAGE_TYPE_LOCALCHAT, name: 'Local', diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 3629b5d26e..57dab303b5 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -160,12 +160,14 @@ export const chatMiddleware = (store) => { settings.visibleMessageLimit, settings.combineMessageLimit, settings.combineIntervalLimit, - settings.logLineCount, settings.logEnable, settings.logLimit, settings.storedTypes, game.roundId, settings.prependTimestamps, + settings.hideImportantInAdminTab, + settings.interleave, + settings.interleaveColor, ); // Load the chat once settings are loaded if (!initialized && settings.initialized) { @@ -266,6 +268,10 @@ export const chatMiddleware = (store) => { saveChatToStorage(store); return next(action); } + if (type === 'saveToDiskCommand') { + chatRenderer.saveToDisk(settings.logLineCount); + return; + } if (type === saveChatToDisk.type) { chatRenderer.saveToDisk( settings.logLineCount, diff --git a/tgui/packages/tgui-panel/chat/model.js b/tgui/packages/tgui-panel/chat/model.js index 21fa2bc2b4..218d958bfd 100644 --- a/tgui/packages/tgui-panel/chat/model.js +++ b/tgui/packages/tgui-panel/chat/model.js @@ -11,6 +11,35 @@ import { MESSAGE_TYPE_INTERNAL, MESSAGE_TYPES } from './constants'; export const canPageAcceptType = (page, type) => type.startsWith(MESSAGE_TYPE_INTERNAL) || page.acceptedTypes[type]; +export const typeIsImportant = (type) => { + let isImportant = false; + for (let typeDef of MESSAGE_TYPES) { + if (typeDef.type === type && !!typeDef.important) { + isImportant = true; + break; + } + } + return isImportant; +}; + +export const adminPageOnly = (page) => { + let adminTab = true; + let checked = 0; + for (let typeDef of MESSAGE_TYPES) { + if ( + page.acceptedTypes[typeDef.type] && + !(!!typeDef.important || !!typeDef.admin) + ) { + adminTab = false; + break; + } + if (page.acceptedTypes[typeDef.type] && !typeDef.important) { + checked++; + } + } + return checked > 0 && adminTab; +}; + export const canStoreType = (storedTypes, type) => storedTypes[type]; export const createPage = (obj) => { diff --git a/tgui/packages/tgui-panel/chat/renderer.jsx b/tgui/packages/tgui-panel/chat/renderer.jsx index d1050012c6..1dc17bc897 100644 --- a/tgui/packages/tgui-panel/chat/renderer.jsx +++ b/tgui/packages/tgui-panel/chat/renderer.jsx @@ -20,11 +20,13 @@ import { MESSAGE_TYPES, } from './constants'; import { + adminPageOnly, canPageAcceptType, canStoreType, createMessage, isSameMessage, serializeMessage, + typeIsImportant, } from './model'; import { highlightNode, linkifyNode } from './replaceInTextNode'; @@ -75,6 +77,17 @@ const createMessageNode = () => { return node; }; +const interleaveMessage = (node, interleave, color) => { + if (interleave) { + node.setAttribute('style', 'background-color:' + color); + node.setAttribute('display', 'block'); + } else { + node.removeAttribute('style'); + node.removeAttribute('display'); + } + return node; +}; + const createReconnectedNode = () => { const node = document.createElement('div'); node.className = 'Chat__reconnected'; @@ -150,11 +163,14 @@ class ChatRenderer { this.visibleMessageLimit = 2500; this.combineMessageLimit = 5; this.combineIntervalLimit = 5; - this.exportLimit = 0; this.logLimit = 0; this.logEnable = true; this.roundId = null; this.storedTypes = {}; + this.interleave = false; + this.interleaveEnabled = false; + this.interleaveColor = '#909090'; + this.hideImportantInAdminTab = false; // Scroll handler /** @type {HTMLElement} */ this.scrollNode = null; @@ -366,6 +382,32 @@ class ChatRenderer { this.scrollNode.scrollTop = this.scrollNode.scrollHeight; } + setVisualChatLimits( + visibleMessageLimit, + combineMessageLimit, + combineIntervalLimit, + logEnable, + logLimit, + storedTypes, + roundId, + prependTimestamps, + hideImportantInAdminTab, + interleaveEnabled, + interleaveColor, + ) { + this.visibleMessageLimit = visibleMessageLimit; + this.combineMessageLimit = combineMessageLimit; + this.combineIntervalLimit = combineIntervalLimit; + this.logEnable = logEnable; + this.logLimit = logLimit; + this.storedTypes = storedTypes; + this.roundId = roundId; + this.prependTimestamps = prependTimestamps; + this.hideImportantInAdminTab = hideImportantInAdminTab; + this.interleaveEnabled = interleaveEnabled; + this.interleaveColor = interleaveColor; + } + changePage(page) { if (!this.isReady()) { this.page = page; @@ -380,8 +422,21 @@ class ChatRenderer { const fragment = document.createDocumentFragment(); let node; for (let message of this.messages) { - if (canPageAcceptType(page, message.type)) { + if ( + canPageAcceptType(page, message.type) && + !( + adminPageOnly(page) && + typeIsImportant(message.type) && + this.hideImportantInAdminTab + ) + ) { node = message.node; + node = interleaveMessage( + node, + this.interleaveEnabled && this.interleave, + this.interleaveColor, + ); + this.interleave = !this.interleave; fragment.appendChild(node); this.visibleMessages.push(message); } @@ -392,28 +447,6 @@ class ChatRenderer { } } - setVisualChatLimits( - visibleMessageLimit, - combineMessageLimit, - combineIntervalLimit, - exportLimit, - logEnable, - logLimit, - storedTypes, - roundId, - prependTimestamps, - ) { - this.visibleMessageLimit = visibleMessageLimit; - this.combineMessageLimit = combineMessageLimit; - this.combineIntervalLimit = combineIntervalLimit; - this.exportLimit = exportLimit; - this.logEnable = logEnable; - this.logLimit = logLimit; - this.storedTypes = storedTypes; - this.roundId = roundId; - this.prependTimestamps = prependTimestamps; - } - getCombinableMessage(predicate) { const now = Date.now(); const len = this.visibleMessages.length; @@ -486,15 +519,6 @@ class ChatRenderer { } else { logger.error('Error: message is missing text payload', message); } - // Get our commands we might want to send to chat - const commands = node.querySelectorAll('[data-command]'); - if (commands.length) { - const command = commands[0].getAttribute('data-command'); - if (command === '$do_export') { - this.saveToDisk(this.exportLimit); - } - return; // We do not want those logged or shown! - } // Get all nodes in this message that want to be rendered like jsx const nodes = node.querySelectorAll('[data-component]'); for (let i = 0; i < nodes.length; i++) { @@ -619,7 +643,20 @@ class ChatRenderer { } this.archivedMessages.push(serializeMessage(message, true)); // TODO: Actually having a better message archiving maybe for exports? } - if (canPageAcceptType(this.page, message.type)) { + if ( + canPageAcceptType(this.page, message.type) && + !( + adminPageOnly(this.page) && + typeIsImportant(message.type) && + this.hideImportantInAdminTab + ) + ) { + node = interleaveMessage( + node, + this.interleaveEnabled && this.interleave, + this.interleaveColor, + ); + this.interleave = !this.interleave; fragment.appendChild(node); this.visibleMessages.push(message); } diff --git a/tgui/packages/tgui-panel/game/reducer.ts b/tgui/packages/tgui-panel/game/reducer.ts index 965bfa0cb7..966367e6e5 100644 --- a/tgui/packages/tgui-panel/game/reducer.ts +++ b/tgui/packages/tgui-panel/game/reducer.ts @@ -17,13 +17,21 @@ const initialState = { }; export const gameReducer = (state = initialState, action) => { - const { type, meta } = action; + const { type, meta, payload } = action; if (type === 'roundrestart') { return { ...state, roundRestartedAt: meta.now, }; } + if (type === 'connected') { + if (state.roundId !== payload.round_id) { + return { + ...state, + roundId: payload.round_id, + }; + } + } if (type === connectionLost.type) { return { ...state, diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx index 8d9d8ea616..de146b898a 100644 --- a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx +++ b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx @@ -81,6 +81,7 @@ export const SettingsPanel = (props) => { {activeTab === 'export' && } {activeTab === 'chatPage' && } {activeTab === 'textHighlight' && } + {activeTab === 'adminSettings' && } ); @@ -94,6 +95,8 @@ export const SettingsGeneral = (props) => { lineHeight, showReconnectWarning, prependTimestamps, + interleave, + interleaveColor, } = useSelector(selectSettings); const dispatch = useDispatch(); const [freeFont, setFreeFont] = useState(false); @@ -208,6 +211,37 @@ export const SettingsGeneral = (props) => { } /> + + + dispatch( + updateSettings({ + interleave: !interleave, + }), + ) + } + /> + + + + dispatch( + updateSettings({ + interleaveColor: value, + }), + ) + } + /> + + { ); }; + +export const AdminSettings = (props) => { + const dispatch = useDispatch(); + const { hideImportantInAdminTab } = useSelector(selectSettings); + return ( +
+ + + + dispatch( + updateSettings({ + hideImportantInAdminTab: !hideImportantInAdminTab, + }), + ) + } + /> + + +
+ ); +}; diff --git a/tgui/packages/tgui-panel/settings/constants.ts b/tgui/packages/tgui-panel/settings/constants.ts index 671b1137e9..8be68f3d2c 100644 --- a/tgui/packages/tgui-panel/settings/constants.ts +++ b/tgui/packages/tgui-panel/settings/constants.ts @@ -9,6 +9,10 @@ export const SETTINGS_TABS = [ id: 'general', name: 'General', }, + { + id: 'adminSettings', + name: 'Admin', + }, { id: 'limits', name: 'Visual Limits', diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js index 2b2f44e43b..cf5467831c 100644 --- a/tgui/packages/tgui-panel/settings/reducer.js +++ b/tgui/packages/tgui-panel/settings/reducer.js @@ -58,6 +58,9 @@ const initialState = { lastId: null, initialized: false, storedTypes: {}, + hideImportantInAdminTab: false, + interleave: false, + interleaveColor: '#909090', }; export const settingsReducer = (state = initialState, action) => { diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 8809dbaa43..a92d02427c 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -370,6 +370,7 @@ img.icon.bigicon { .say, .emote, .emotesubtle, +.multizsay, .npcemote, .npcsay, .infoplain, diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index 7d724436a7..5d83c32be2 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -388,6 +388,7 @@ img.icon.bigicon { .say, .emote, .emotesubtle, +.multizsay, .npcemote, .npcsay, .infoplain, diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss index 0666c15b1f..aa15f6ee67 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss @@ -370,6 +370,7 @@ img.icon.bigicon { .say, .emote, .emotesubtle, +.multizsay, .npcemote, .npcsay, .infoplain, diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss index 217ba9a6f2..279dd7bc21 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss @@ -388,6 +388,7 @@ img.icon.bigicon { .say, .emote, .emotesubtle, +.multizsay, .npcemote, .npcsay, .infoplain, diff --git a/tgui/packages/tgui/interfaces/Canvas.jsx b/tgui/packages/tgui/interfaces/Canvas.jsx index 02b2ff9ecf..726acfed84 100644 --- a/tgui/packages/tgui/interfaces/Canvas.jsx +++ b/tgui/packages/tgui/interfaces/Canvas.jsx @@ -52,8 +52,8 @@ class PaintCanvas extends Component { const y_size = this.props.value[0].length; const x_scale = this.canvasRef.current.width / x_size; const y_scale = this.canvasRef.current.height / y_size; - const x = Math.floor(event.offsetX / x_scale) + 1; - const y = Math.floor(event.offsetY / y_scale) + 1; + const x = Math.floor(event.nativeEvent.offsetX / x_scale) + 1; + const y = Math.floor(event.nativeEvent.offsetY / y_scale) + 1; this.onCVClick(x, y); } diff --git a/tgui/packages/tgui/interfaces/ColorMate.jsx b/tgui/packages/tgui/interfaces/ColorMate.jsx new file mode 100644 index 0000000000..f123dec809 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorMate.jsx @@ -0,0 +1,413 @@ +import { useBackend } from '../backend'; +import { + Button, + Icon, + NoticeBox, + NumberInput, + Section, + Slider, + Table, + Tabs, +} from '../components'; +import { Window } from '../layouts'; + +export const ColorMate = (props, context) => { + const { act, data } = useBackend(context); + const { activemode, temp } = data; + const item = data.item || []; + return ( + + +
+ {temp ? {temp} : null} + {Object.keys(item).length ? ( + <> + + +
+
Item:
+ +
+
+ +
+
Preview:
+ +
+
+
+ + + act('switch_modes', { + mode: 1, + }) + } + > + Tint coloring (Simple) + + + act('switch_modes', { + mode: 2, + }) + } + > + HSV coloring (Normal) + + + act('switch_modes', { + mode: 3, + }) + } + > + Matrix coloring (Advanced) + + +
Coloring: {item.name}
+ + +
+ + ) : ( +
No item inserted.
+ )} +
+
+
+ ); +}; + +export const ColorMateTint = (props, context) => { + const { act, data } = useBackend(context); + return ( + - - - - - - -
- {items.map((item, i) => ( - - #{i + 1}: {item} - - ))} -
- - )) || ( -
- No items inserted. -
- )} - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/ResearchConsole.jsx b/tgui/packages/tgui/interfaces/ResearchConsole.jsx index dedfa58f7f..c3a73aa318 100644 --- a/tgui/packages/tgui/interfaces/ResearchConsole.jsx +++ b/tgui/packages/tgui/interfaces/ResearchConsole.jsx @@ -1,5 +1,5 @@ import { toTitleCase } from 'common/string'; -import { Fragment, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import { useBackend, useSharedState } from '../backend'; import { @@ -323,8 +323,8 @@ const ResearchConsoleDisk = (props) => { /> ); @@ -618,7 +618,6 @@ const ResearchConsoleConstructor = (props) => { (props.protoTab === 2 && ( {mats.map((mat) => { - const [ejectAmt, setEjectAmt] = useState(0); return ( { setEjectAmt(val)} + onDrag={(e, val) => + props.onMatsState({ + ...props.matsStates, + [mat.name]: val, + }) + } /> + + + )) || ( + - - - )) || ( - - )} + ))} @@ -836,6 +845,12 @@ export const ResearchConsole = (props) => { false, ); + const [matsStates, setMatsState] = useState({}); + + useEffect(() => { + setMatsState({}); + }, [menu]); + let allTabsDisabled = false; if (busy_msg || locked) { allTabsDisabled = true; @@ -869,7 +884,9 @@ export const ResearchConsole = (props) => { ) : ( '' @@ -878,7 +895,9 @@ export const ResearchConsole = (props) => { )) || (menu === 2 && ( diff --git a/tgui/packages/tgui/interfaces/ShuttleControl.jsx b/tgui/packages/tgui/interfaces/ShuttleControl.jsx index 4ad3271a83..c2b2330eae 100644 --- a/tgui/packages/tgui/interfaces/ShuttleControl.jsx +++ b/tgui/packages/tgui/interfaces/ShuttleControl.jsx @@ -144,7 +144,7 @@ const ShuttleControlConsoleDefault = (props) => { const ShuttleControlConsoleMulti = (props) => { const { act, data } = useBackend(); - const { can_cloak, can_pick, legit, cloaked, destination_name } = data; + const { can_cloak, can_pick, legit, cloaked } = data; return ( <> @@ -164,12 +164,11 @@ const ShuttleControlConsoleMulti = (props) => { null} + /> @@ -467,24 +466,28 @@ const ShuttleControlConsoleWeb = (props) => { ); }; -// This may look tempting to convert to require() or some kind of dynamic call -// Don't do it. XSS abound. -const SubtemplateList = { - ShuttleControlConsoleDefault: , - ShuttleControlConsoleMulti: , - ShuttleControlConsoleExploration: , - ShuttleControlConsoleWeb: , -}; - export const ShuttleControl = (props) => { const { act, data } = useBackend(); - const { subtemplate } = data; + const { subtemplate, destination_name } = data; return ( - {SubtemplateList[subtemplate]} + + {(subtemplate === 'ShuttleControlConsoleDefault' && ( + + )) || + (subtemplate === 'ShuttleControlConsoleMulti' && ( + + )) || + (subtemplate === 'ShuttleControlConsoleExploration' && ( + + )) || + (subtemplate === 'ShuttleControlConsoleWeb' && ( + + ))} + ); }; diff --git a/tgui/packages/tgui/interfaces/Smes.jsx b/tgui/packages/tgui/interfaces/Smes.jsx index 7d2b4e890e..093a0bc946 100644 --- a/tgui/packages/tgui/interfaces/Smes.jsx +++ b/tgui/packages/tgui/interfaces/Smes.jsx @@ -38,7 +38,7 @@ export const Smes = (props) => { const outputState = (outputting && 'good') || (charge > 0 && 'average') || 'bad'; return ( - +
{ bad: [-Infinity, 0.15], }} > - {round(charge / (1000 * 60), 1)} kWh /{' '} + {round(charge / (1000 * 60), 1)} kWh / {round(capacity / (1000 * 60))} kWh ({capacityPercent}%)
diff --git a/tgui/packages/tgui/interfaces/TextInputModal.tsx b/tgui/packages/tgui/interfaces/TextInputModal.tsx index 395ea44870..52517bcbbf 100644 --- a/tgui/packages/tgui/interfaces/TextInputModal.tsx +++ b/tgui/packages/tgui/interfaces/TextInputModal.tsx @@ -78,7 +78,7 @@ export const TextInputModal = (props) => { {message} - + { belly_mob_mult, belly_item_mult, belly_overall_mult, + drainmode, } = belly; return ( @@ -870,6 +871,12 @@ const VoreSelectedBellyOptions = (props) => { content={digest_clone} />
+ +