Files
Bubberstation/code/__HELPERS/matrices.dm
Krysonism 5f3930ba05 Da huge stonking cans update! 2025 edition. (#90318)
## About The Pull Request


![bild](https://github.com/user-attachments/assets/d3643592-4907-4d01-8fc6-3d613aa3e772)

![bild](https://github.com/user-attachments/assets/d859e878-72e3-4200-8c1c-8521e5c499d2)

## This PR:

*  Adds a new type of reagent container; the jerrycan!

* Replaces the nutrient bottles in the nutrimax with cans.

* Adds some prefilled cans to the maint loot table.

### Can knowledge

* Jerrycans can hold a hold an incredible 200u.
* They can be printed roundstart at the auto and protolathes for 2
sheets of plastic.
* They are balanced by being bulky.
* They can be thrown around by without spilling their contents.
* They are not beakers and cannot be used to make grenades.

The upfront cost associated with buying nutrients will be greater, but
it will be cheaper per unit.
I would actually like to charge more but this is not really feasible
right now due to the heavy department discount.

#### Maint loot additions
The new maint loot cans have a low weight so far, collectively appearing
half as often as basic healing items.

The cans that have been added to maint loot are as follows:

* space cleaner ( weight: 2)
* oil (weight: 2)
* diethylamine (weight : 1)
* phlogiston (weight: 1)

#### Reagent overlay code

I have added an option to have reagent containers use a colour matrix to
lighten and increase the contrast of the reagent filling overlay in an
attempt to prevent dark coloured reagents from completely obliterating
all contrast.
The math may need to be tweaked but it should offer some improvement
provided the filling overlay has some contrast to begin with.

## Why It's Good For The Game

The jerrycan is not only a flavourful & setting appropriate way to store
reagents, it also offers several design benefits due to its unique
combination of high roundstart capacity and bulky size.

* It prevents one coworker from hogging a shared reagent container.
* The high volume eases grinding and mixing.
* It allows reagents to be stolen or borrowed due to being stored
outside the safety of an inventory.
* The highly visible inhand sprite allows you to see who is holding or
transporting reagents.
* The smart cap flag increases quality of life by allowing you to throw
the container without spilling.
* It reduces the tedium associated with dispensing and throwing away
botany nutrient bottles, encouraging refilling.
* Bulky size prevent players from looting the can unless it is something
they really want.
* Comes with a large number of visual customisation options for coders
who with to add new variants.

## Throwing away the little nutrient bottles when doing botany sucks!

## Changelog

🆑
add: Added a new type of reagent container; the jerrycan.
add: Added some prefilled jerrycans to maint loot.
balance: Botany nutrients now come in bulk packaging.
/🆑

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
2025-05-08 19:21:10 -04:00

219 lines
7.0 KiB
Plaintext

/// Datum which stores information about a matrix decomposed with decompose().
/datum/decompose_matrix
///?
var/scale_x = 1
///?
var/scale_y = 1
///?
var/rotation = 0
///?
var/shift_x = 0
///?
var/shift_y = 0
/// Decomposes a matrix into scale, shift and rotation.
///
/// If other operations were applied on the matrix, such as shearing, the result
/// will not be precise.
///
/// Negative scales are now supported. =)
/matrix/proc/decompose()
var/datum/decompose_matrix/decompose_matrix = new
. = decompose_matrix
var/flip_sign = (a*e - b*d < 0)? -1 : 1 // Det < 0 => only 1 axis is flipped - start doing some sign flipping
// If both axis are flipped, nothing bad happens and Det >= 0, it just treats it like a 180° rotation
// If only 1 axis is flipped, we need to flip one direction - in this case X, so we flip a, b and the x scaling
decompose_matrix.scale_x = sqrt(a * a + d * d) * flip_sign
decompose_matrix.scale_y = sqrt(b * b + e * e)
decompose_matrix.shift_x = c
decompose_matrix.shift_y = f
if(!decompose_matrix.scale_x || !decompose_matrix.scale_y)
return
// If only translated, scaled and rotated, a/xs == e/ys and -d/xs == b/xy
var/cossine = (a/decompose_matrix.scale_x + e/decompose_matrix.scale_y) / 2
var/sine = (b/decompose_matrix.scale_y - d/decompose_matrix.scale_x) / 2 * flip_sign
decompose_matrix.rotation = arctan(cossine, sine) * flip_sign
/matrix/proc/TurnTo(old_angle, new_angle)
return Turn(new_angle - old_angle) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
/**
* Shear the transform on either or both axes.
* * x - X axis shearing
* * y - Y axis shearing
*/
/matrix/proc/Shear(x, y)
return Multiply(matrix(1, x, 0, y, 1, 0))
//Dumps the matrix data in format a-f
/matrix/proc/tolist()
. = list()
. += a
. += b
. += c
. += d
. += e
. += f
//Dumps the matrix data in a matrix-grid format
/*
a d 0
b e 0
c f 1
*/
/matrix/proc/togrid()
. = list()
. += a
. += d
. += 0
. += b
. += e
. += 0
. += c
. += f
. += 1
///The X pixel offset of this matrix
/matrix/proc/get_x_shift()
. = c
///The Y pixel offset of this matrix
/matrix/proc/get_y_shift()
. = f
///The angle of this matrix
/matrix/proc/get_angle()
. = -ATAN2(a,d)
/////////////////////
// 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)
*/
//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)
//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)
//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)
//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
/**
* Converts RGB shorthands into RGBA matrices complete of constants rows (ergo a 20 keys list in byond).
* if return_identity_on_fail is true, stack_trace is called instead of CRASH, and an identity is returned.
*/
/proc/color_to_full_rgba_matrix(color, return_identity_on_fail = TRUE)
if(!color)
return COLOR_MATRIX_IDENTITY
if(istext(color))
var/list/L = rgb2num(color)
if(!L)
var/message = "Invalid/unsupported color ([color]) argument in color_to_full_rgba_matrix()"
if(return_identity_on_fail)
stack_trace(message)
return COLOR_MATRIX_IDENTITY
CRASH(message)
return list(L[1]/255,0,0,0, 0,L[2]/255,0,0, 0,0,L[3]/255,0, 0,0,0,L.len>3?L[4]/255:1, 0,0,0,0)
if(!islist(color)) //invalid format
CRASH("Invalid/unsupported color ([color]) argument in color_to_full_rgba_matrix()")
var/list/L = color
switch(L.len)
if(3 to 5) // row-by-row hexadecimals
. = list()
for(var/a in 1 to L.len)
var/list/rgb = rgb2num(L[a])
for(var/b in rgb)
. += b/255
if(length(rgb) % 4) // RGB has no alpha instruction
. += a != 4 ? 0 : 1
if(L.len < 4) //missing both alphas and constants rows
. += list(0,0,0,1, 0,0,0,0)
else if(L.len < 5) //missing constants row
. += list(0,0,0,0)
if(9 to 12) //RGB
. = list(L[1],L[2],L[3],0, L[4],L[5],L[6],0, L[7],L[8],L[9],0, 0,0,0,1)
for(var/b in 1 to 3) //missing constants row
. += L.len < 9+b ? 0 : L[9+b]
. += 0
if(16 to 20) // RGBA
. = L.Copy()
if(L.len < 20) //missing constants row
for(var/b in 1 to 20-L.len)
. += 0
else
var/message = "Invalid/unsupported color (list of length [L.len]) argument in color_to_full_rgba_matrix()"
if(return_identity_on_fail)
stack_trace(message)
return COLOR_MATRIX_IDENTITY
CRASH(message)