mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-22 23:46:55 +01:00
214 lines
6.8 KiB
Plaintext
214 lines
6.8 KiB
Plaintext
|
|
/**
|
|
* spritesheet implementation - coalesces various icons into a single .png file
|
|
* and uses CSS to select icons out of that file - saves on transferring some
|
|
* 1400-odd individual PNG files
|
|
*
|
|
* To use, use classes of "[name][size_key]" and the state name used in insert().
|
|
* If you used insert_all(), don't forget the prefix.
|
|
*
|
|
* Example: <div class='sheetmaterials32x32 glass-3'>
|
|
* In tgui, usually would be className={classes(['sheetmaterials32x32', 'glass-3'])}
|
|
*/
|
|
#define SPR_SIZE 1
|
|
#define SPR_IDX 2
|
|
|
|
#define SPRSZ_COUNT 1
|
|
#define SPRSZ_ICON 2
|
|
#define SPRSZ_STRIPPED 3
|
|
|
|
/datum/asset_pack/spritesheet
|
|
abstract_type = /datum/asset_pack/spritesheet
|
|
do_not_separate = TRUE
|
|
/// so unfortunately we can't mangle it due to the fact that
|
|
/// * the .css needs to know where the image is
|
|
/// * we don't know what to name the image until the entire pack is encoded
|
|
/// * so the easiest way is to set do_not_mangle to TRUE for now, and deal with it later
|
|
/// todo: completely unnecessarily overengineer some more and fix this in 2025 ~silicons
|
|
do_not_mangle = TRUE
|
|
/// the spritesheet's name
|
|
var/name
|
|
|
|
/**
|
|
* List of arguments to pass into do_insert.
|
|
* Exists so we can queue icon insertion, mostly for stuff like preferences.
|
|
*/
|
|
var/list/to_generate = list()
|
|
|
|
/// "32x32" -> list(10, icon/normal, icon/stripped)
|
|
var/list/sizes = list()
|
|
/// "foo_bar" -> list("32x32", 5)
|
|
var/list/sprites = list()
|
|
|
|
/datum/asset_pack/spritesheet/unload()
|
|
..()
|
|
to_generate = list()
|
|
sizes = list()
|
|
sprites = list()
|
|
|
|
/datum/asset_pack/spritesheet/register(generation)
|
|
return construct()
|
|
|
|
/datum/asset_pack/spritesheet/proc/get_css_url()
|
|
return get_url("spritesheet_[name].css")
|
|
|
|
/datum/asset_pack/spritesheet/proc/construct()
|
|
. = list()
|
|
|
|
while(length(to_generate))
|
|
var/list/stored_args = to_generate[to_generate.len]
|
|
to_generate.len--
|
|
do_insert(arglist(stored_args))
|
|
CHECK_TICK
|
|
|
|
ensure_stripped()
|
|
for(var/size_id in sizes)
|
|
var/size = sizes[size_id]
|
|
.["[name]_[size_id].png"] = size[SPRSZ_STRIPPED]
|
|
|
|
var/fname = "tmp/assets/spritesheets/[name].css"
|
|
fdel(fname)
|
|
rustg_file_write(generate_css(), fname)
|
|
var/res_name = "spritesheet_[name].css"
|
|
.[res_name] = fname
|
|
|
|
/datum/asset_pack/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
|
|
for(var/size_id in sizes_to_strip)
|
|
var/size = sizes[size_id]
|
|
if (size[SPRSZ_STRIPPED])
|
|
continue
|
|
|
|
// save flattened version
|
|
var/fname = "tmp/assets/spritesheets/[name]_[size_id].png"
|
|
fdel(fname)
|
|
fcopy(size[SPRSZ_ICON], fname)
|
|
var/error = rustg_dmi_strip_metadata(fname)
|
|
if(length(error))
|
|
stack_trace("Failed to strip [name]_[size_id].png: [error]")
|
|
size[SPRSZ_STRIPPED] = icon(fname)
|
|
|
|
/datum/asset_pack/spritesheet/proc/generate_css()
|
|
var/list/out = list()
|
|
|
|
for (var/size_id in sizes)
|
|
var/size = sizes[size_id]
|
|
var/list/dimensions = get_icon_dimensions(size[SPRSZ_ICON])
|
|
out += ".[name][size_id]{display:inline-block;width:[dimensions["width"]]px;height:[dimensions["height"]]px;background:url('[get_background_url("[name]_[size_id].png")]') no-repeat;}"
|
|
|
|
for (var/sprite_id in sprites)
|
|
var/sprite = sprites[sprite_id]
|
|
var/size_id = sprite[SPR_SIZE]
|
|
var/idx = sprite[SPR_IDX]
|
|
var/size = sizes[size_id]
|
|
|
|
var/list/tiny_dimensions = get_icon_dimensions(size[SPRSZ_ICON])
|
|
var/icon/big = size[SPRSZ_STRIPPED]
|
|
// big width won't be cached ever
|
|
var/per_line = big.Width() / tiny_dimensions["width"]
|
|
var/x = (idx % per_line) * tiny_dimensions["width"]
|
|
var/y = round(idx / per_line) * tiny_dimensions["height"]
|
|
|
|
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
|
|
|
|
return out.Join("\n")
|
|
|
|
/// Returns the URL to put in the background:url of the CSS asset
|
|
/datum/asset_pack/spritesheet/proc/get_background_url(image_name)
|
|
return image_name
|
|
|
|
/**
|
|
* LEMON NOTE
|
|
* A GOON CODER SAYS BAD ICON ERRORS CAN BE THROWN BY THE "ICON CACHE"
|
|
* APPARENTLY IT MAKES ICONS IMMUTABLE
|
|
* LOOK INTO USING THE MUTABLE APPEARANCE PATTERN HERE
|
|
*
|
|
* neither prefixes nor states may have spaces!
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/do_insert(sprite_name, icon/inserted_icon, icon_state = "", dir = SOUTH, frame = 1, moving = FALSE, skip_checks)
|
|
// #ifdef UNIT_TESTS
|
|
// if (inserted_icon && icon_state && !icon_exists(inserted_icon, icon_state)) // check the base icon prior to extracting the state we want
|
|
// stack_trace("Tried to insert nonexistent icon_state '[icon_state]' from [inserted_icon] into spritesheet [name] ([type])")
|
|
// return
|
|
// #endif
|
|
inserted_icon = icon(inserted_icon, icon_state, dir, frame, moving)
|
|
// Check that it exists.
|
|
if(!skip_checks && !length(adaptive_icon_states(inserted_icon)))
|
|
return
|
|
|
|
// Any sprite modifications we want to do (aka, coloring a greyscaled asset)
|
|
inserted_icon = modify_inserted(inserted_icon)
|
|
var/list/dimensions = get_icon_dimensions(inserted_icon)
|
|
var/size_id = "[dimensions["width"]]x[dimensions["height"]]"
|
|
var/size = sizes[size_id]
|
|
|
|
if (sprites[sprite_name])
|
|
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
|
|
|
|
if (size)
|
|
var/position = size[SPRSZ_COUNT]++
|
|
var/icon/sheet = size[SPRSZ_ICON]
|
|
var/icon/sheet_copy = icon(sheet)
|
|
size[SPRSZ_STRIPPED] = null
|
|
sheet_copy.Insert(inserted_icon, icon_state=sprite_name)
|
|
size[SPRSZ_ICON] = sheet_copy
|
|
sprites[sprite_name] = list(size_id, position)
|
|
else
|
|
sizes[size_id] = size = list(1, inserted_icon, null)
|
|
sprites[sprite_name] = list(size_id, 0)
|
|
|
|
/**
|
|
* neither prefixes nor states may have spaces!
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE, skip_checks)
|
|
to_generate += list(args.Copy())
|
|
|
|
/**
|
|
* neither prefixes nor states may have spaces!
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/insert_all(prefix, icon/I, list/directions)
|
|
if (length(prefix))
|
|
prefix = "[prefix]-"
|
|
|
|
if (!directions)
|
|
directions = list(SOUTH)
|
|
|
|
for (var/icon_state_name in icon_states(I))
|
|
for (var/direction in directions)
|
|
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
|
|
insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
|
|
|
|
/**
|
|
* A simple proc handing the Icon for you to modify before it gets turned into an asset.
|
|
*
|
|
* Arguments:
|
|
* * I: icon being turned into an asset
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/modify_inserted(icon/pre_asset)
|
|
return pre_asset
|
|
|
|
/**
|
|
* todo: deprecated; logic should be tgui-side.
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/icon_tag(sprite_name)
|
|
var/sprite = sprites[sprite_name]
|
|
if (!sprite)
|
|
return null
|
|
var/size_id = sprite[SPR_SIZE]
|
|
return {"<span class='[name][size_id] [sprite_name]'></span>"}
|
|
|
|
/**
|
|
* todo: deprecated; logic should be tgui-side.
|
|
*/
|
|
/datum/asset_pack/spritesheet/proc/icon_class_name(sprite_name)
|
|
var/sprite = sprites[sprite_name]
|
|
if (!sprite)
|
|
return null
|
|
var/size_id = sprite[SPR_SIZE]
|
|
return {"[name][size_id] [sprite_name]"}
|
|
|
|
#undef SPR_SIZE
|
|
#undef SPR_IDX
|
|
#undef SPRSZ_COUNT
|
|
#undef SPRSZ_ICON
|
|
#undef SPRSZ_STRIPPED
|