Skip to content

Commit 765dbca

Browse files
committed
Merge branch 'master' of github.com:Monkestation/Monkestation2.0 into websocket-server
2 parents 4db50fe + 662a68b commit 765dbca

File tree

19 files changed

+2045
-1404
lines changed

19 files changed

+2045
-1404
lines changed

_maps/map_files/BoxStation/BoxStation.dmm

Lines changed: 1855 additions & 1344 deletions
Large diffs are not rendered by default.

code/__DEFINES/do_afters.dm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
#define DOAFTER_SOURCE_PLANTING_DEVICE "doafter_planting_device"
99
#define DOAFTER_SOURCE_CHARGE_GUNCRANK "doafter_charge_guncrank"
1010
#define DOAFTER_SOURCE_SEED_MESH "doafter_seed_mesh"
11+
#define DOAFTER_SOURCE_ATM "doafter_atm"

code/__DEFINES/rust_g.dm

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,27 @@
179179
/proc/rustg_git_commit_date_head(format = "%F")
180180
return RUSTG_CALL(RUST_G, "rg_git_commit_date_head")(format)
181181

182+
#define rustg_hash_string(algorithm, text) RUSTG_CALL(RUST_G, "hash_string")(algorithm, text)
183+
#define rustg_hash_file(algorithm, fname) RUSTG_CALL(RUST_G, "hash_file")(algorithm, fname)
184+
#define rustg_hash_generate_totp(seed) RUSTG_CALL(RUST_G, "generate_totp")(seed)
185+
#define rustg_hash_generate_totp_tolerance(seed, tolerance) RUSTG_CALL(RUST_G, "generate_totp_tolerance")(seed, tolerance)
186+
187+
#define RUSTG_HASH_MD5 "md5"
188+
#define RUSTG_HASH_SHA1 "sha1"
189+
#define RUSTG_HASH_SHA256 "sha256"
190+
#define RUSTG_HASH_SHA512 "sha512"
191+
#define RUSTG_HASH_XXH64 "xxh64"
192+
#define RUSTG_HASH_BASE64 "base64"
193+
194+
/// Encode a given string into base64
195+
#define rustg_encode_base64(str) rustg_hash_string(RUSTG_HASH_BASE64, str)
196+
/// Decode a given base64 string
197+
#define rustg_decode_base64(str) RUSTG_CALL(RUST_G, "decode_base64")(str)
198+
199+
#ifdef RUSTG_OVERRIDE_BUILTINS
200+
#define md5(thing) (isfile(thing) ? rustg_hash_file(RUSTG_HASH_MD5, "[thing]") : rustg_hash_string(RUSTG_HASH_MD5, thing))
201+
#endif
202+
182203
#define RUSTG_HTTP_METHOD_GET "get"
183204
#define RUSTG_HTTP_METHOD_PUT "put"
184205
#define RUSTG_HTTP_METHOD_DELETE "delete"
@@ -189,6 +210,80 @@
189210
#define rustg_http_request_async(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_async")(method, url, body, headers, options)
190211
#define rustg_http_check_request(req_id) RUSTG_CALL(RUST_G, "http_check_request")(req_id)
191212

213+
/// Generates a spritesheet at: [file_path][spritesheet_name]_[size_id].png
214+
/// The resulting spritesheet arranges icons in a random order, with the position being denoted in the "sprites" return value.
215+
/// All icons have the same y coordinate, and their x coordinate is equal to `icon_width * position`.
216+
///
217+
/// hash_icons is a boolean (0 or 1), and determines if the generator will spend time creating hashes for the output field dmi_hashes.
218+
/// These hashes can be heplful for 'smart' caching (see rustg_iconforge_cache_valid), but require extra computation.
219+
///
220+
/// Spritesheet will contain all sprites listed within "sprites".
221+
/// "sprites" format:
222+
/// list(
223+
/// "sprite_name" = list( // <--- this list is a [SPRITE_OBJECT]
224+
/// icon_file = 'icons/path_to/an_icon.dmi',
225+
/// icon_state = "some_icon_state",
226+
/// dir = SOUTH,
227+
/// frame = 1,
228+
/// transform = list([TRANSFORM_OBJECT], ...)
229+
/// ),
230+
/// ...,
231+
/// )
232+
/// TRANSFORM_OBJECT format:
233+
/// list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = "#ff0000", "blend_mode" = ICON_MULTIPLY)
234+
/// list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = [SPRITE_OBJECT], "blend_mode" = ICON_OVERLAY)
235+
/// list("type" = RUSTG_ICONFORGE_SCALE, "width" = 32, "height" = 32)
236+
/// list("type" = RUSTG_ICONFORGE_CROP, "x1" = 1, "y1" = 1, "x2" = 32, "y2" = 32) // (BYOND icons index from 1,1 to the upper bound, inclusive)
237+
///
238+
/// Returns a SpritesheetResult as JSON, containing fields:
239+
/// list(
240+
/// "sizes" = list("32x32", "64x64", ...),
241+
/// "sprites" = list("sprite_name" = list("size_id" = "32x32", "position" = 0), ...),
242+
/// "dmi_hashes" = list("icons/path_to/an_icon.dmi" = "d6325c5b4304fb03", ...),
243+
/// "sprites_hash" = "a2015e5ff403fb5c", // This is the xxh64 hash of the INPUT field "sprites".
244+
/// "error" = "[A string, empty if there were no errors.]"
245+
/// )
246+
/// In the case of an unrecoverable panic from within Rust, this function ONLY returns a string containing the error.
247+
#define rustg_iconforge_generate(file_path, spritesheet_name, sprites, hash_icons) RUSTG_CALL(RUST_G, "iconforge_generate")(file_path, spritesheet_name, sprites, "[hash_icons]")
248+
/// Returns a job_id for use with rustg_iconforge_check()
249+
#define rustg_iconforge_generate_async(file_path, spritesheet_name, sprites, hash_icons) RUSTG_CALL(RUST_G, "iconforge_generate_async")(file_path, spritesheet_name, sprites, "[hash_icons]")
250+
/// Returns the status of an async job_id, or its result if it is completed. See RUSTG_JOB DEFINEs.
251+
#define rustg_iconforge_check(job_id) RUSTG_CALL(RUST_G, "iconforge_check")("[job_id]")
252+
/// Clears all cached DMIs and images, freeing up memory.
253+
/// This should be used after spritesheets are done being generated.
254+
#define rustg_iconforge_cleanup RUSTG_CALL(RUST_G, "iconforge_cleanup")
255+
/// Takes in a set of hashes, generate inputs, and DMI filepaths, and compares them to determine cache validity.
256+
/// input_hash: xxh64 hash of "sprites" from the cache.
257+
/// dmi_hashes: xxh64 hashes of the DMIs in a spritesheet, given by `rustg_iconforge_generate` with `hash_icons` enabled. From the cache.
258+
/// sprites: The new input that will be passed to rustg_iconforge_generate().
259+
/// Returns a CacheResult with the following structure: list(
260+
/// "result": "1" (if cache is valid) or "0" (if cache is invalid)
261+
/// "fail_reason": "" (emtpy string if valid, otherwise a string containing the invalidation reason or an error with ERROR: prefixed.)
262+
/// )
263+
/// In the case of an unrecoverable panic from within Rust, this function ONLY returns a string containing the error.
264+
#define rustg_iconforge_cache_valid(input_hash, dmi_hashes, sprites) RUSTG_CALL(RUST_G, "iconforge_cache_valid")(input_hash, dmi_hashes, sprites)
265+
/// Returns a job_id for use with rustg_iconforge_check()
266+
#define rustg_iconforge_cache_valid_async(input_hash, dmi_hashes, sprites) RUSTG_CALL(RUST_G, "iconforge_cache_valid_async")(input_hash, dmi_hashes, sprites)
267+
/// Provided a /datum/greyscale_config typepath, JSON string containing the greyscale config, and path to a DMI file containing the base icons,
268+
/// Loads that config into memory for later use by rustg_iconforge_gags(). The config_path is the unique identifier used later.
269+
/// JSON Config schema: https://hackmd.io/@tgstation/GAGS-Layer-Types
270+
/// Unsupported features: color_matrix layer type, 'or' blend_mode. May not have BYOND parity with animated icons or varying dirs between layers.
271+
/// Returns "OK" if successful, otherwise, returns a string containing the error.
272+
#define rustg_iconforge_load_gags_config(config_path, config_json, config_icon_path) RUSTG_CALL(RUST_G, "iconforge_load_gags_config")("[config_path]", config_json, config_icon_path)
273+
/// Given a config_path (previously loaded by rustg_iconforge_load_gags_config), and a string of hex colors formatted as "#ff00ff#ffaa00"
274+
/// Outputs a DMI containing all of the states within the config JSON to output_dmi_path, creating any directories leading up to it if necessary.
275+
/// Returns "OK" if successful, otherwise, returns a string containing the error.
276+
#define rustg_iconforge_gags(config_path, colors, output_dmi_path) RUSTG_CALL(RUST_G, "iconforge_gags")("[config_path]", colors, output_dmi_path)
277+
/// Returns a job_id for use with rustg_iconforge_check()
278+
#define rustg_iconforge_load_gags_config_async(config_path, config_json, config_icon_path) RUSTG_CALL(RUST_G, "iconforge_load_gags_config_async")("[config_path]", config_json, config_icon_path)
279+
/// Returns a job_id for use with rustg_iconforge_check()
280+
#define rustg_iconforge_gags_async(config_path, colors, output_dmi_path) RUSTG_CALL(RUST_G, "iconforge_gags_async")("[config_path]", colors, output_dmi_path)
281+
282+
#define RUSTG_ICONFORGE_BLEND_COLOR "BlendColor"
283+
#define RUSTG_ICONFORGE_BLEND_ICON "BlendIcon"
284+
#define RUSTG_ICONFORGE_CROP "Crop"
285+
#define RUSTG_ICONFORGE_SCALE "Scale"
286+
192287
#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
193288
#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB"
194289
#define RUSTG_JOB_ERROR "JOB PANICKED"

code/modules/admin/holder2.dm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ GLOBAL_PROTECT(href_token)
197197
if (!SSdbcore.IsConnected())
198198
return null
199199

200-
var/datum/db_query/feedback_query = SSdbcore.NewQuery("SELECT feedback FROM [format_table_name("admin")] WHERE ckey = '[owner.ckey]'")
200+
var/datum/db_query/feedback_query = SSdbcore.NewQuery(
201+
"SELECT feedback FROM [format_table_name("admin")] WHERE ckey = :ckey",
202+
list("ckey" = owner.ckey)
203+
)
201204

202205
if(!feedback_query.Execute())
203206
log_sql("Error retrieving feedback link for [src]")

code/modules/wiremod/core/component_printer.dm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171

7272
/obj/machinery/component_printer/ui_assets(mob/user)
7373
return list(
74-
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
74+
get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
75+
get_asset_datum(/datum/asset/spritesheet/research_designs)
7576
)
7677

7778
/obj/machinery/component_printer/proc/calculate_efficiency()

dependencies.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export BYOND_MAJOR=515
88
export BYOND_MINOR=1647
99

1010
#rust_g git tag
11-
export RUST_G_VERSION=3.5.1
11+
export RUST_G_VERSION=3.6.0
1212

1313
#node version
1414
export NODE_VERSION_LTS=22.11.0

html/changelogs/AutoChangeLog-pr-5728.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.

html/changelogs/AutoChangeLog-pr-5742.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
author: "Absolucy"
2+
delete-after: True
3+
changes:
4+
- bugfix: "Fixed the monkecoin dupe exploit some more."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
author: "Absolucy, Aylong"
2+
delete-after: True
3+
changes:
4+
- bugfix: "Fixed random chat BSOD on Byond 516"

html/changelogs/archive/2025-03.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,18 @@
7878
Veth-s:
7979
- admin: You can now choose unholy as an option in select equipment, spawning you
8080
from hell instead.
81+
2025-03-03:
82+
Absolucy:
83+
- bugfix: Prevent stacking ATM interactions to potentially dupe monkecoins.
84+
MichiRecRoom:
85+
- bugfix: The component printer now shows icons for the items in its UI.
86+
The-Black-Screen:
87+
- map: added light switches and wall mounted radios to more areas / fixed a disposal
88+
pipe junction in engineering storage / modified the hydroponics, northeast solar
89+
array, ranch, ship breaking hut, and western maintenance areas / removed lattices
90+
at the arrivals shuttle dock / miscellaneous things.
91+
Thedragmeme:
92+
- rscadd: Adds a bunch of new items in various categories available to buy via monkey
93+
coin in the loadout menu
94+
adamsong:
95+
- rscdel: Removed doubled text from syndicate listening post job description

monkestation/code/modules/client/preferences/inventory.dm

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
if(!ckey || !SSdbcore.IsConnected())
2121
metacoins = 5000
2222
return
23-
var/datum/db_query/query_get_metacoins = SSdbcore.NewQuery("SELECT metacoins FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
23+
var/datum/db_query/query_get_metacoins = SSdbcore.NewQuery(
24+
"SELECT metacoins FROM [format_table_name("player")] WHERE ckey = :ckey",
25+
list("ckey" = ckey)
26+
)
2427
var/mc_count = 0
2528
if(query_get_metacoins.warn_execute())
2629
if(query_get_metacoins.NextRow())
@@ -67,8 +70,13 @@
6770
logger.Log(LOG_CATEGORY_META, "[parent]'s monkecoins were changed by [amount] Reason: [reason]", list("currency_left" = metacoins, "reason" = reason))
6871

6972
//SQL query - updates the metacoins in the database (this is where the storage actually happens)
70-
var/datum/db_query/query_inc_metacoins = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET metacoins = metacoins + '[amount]' WHERE ckey = '[ckey]'")
71-
query_inc_metacoins.warn_execute()
73+
var/datum/db_query/query_inc_metacoins = SSdbcore.NewQuery(
74+
"UPDATE [format_table_name("player")] SET metacoins = metacoins + :amount WHERE ckey = :ckey",
75+
list("amount" = amount, "ckey" = ckey)
76+
)
77+
if(!query_inc_metacoins.warn_execute())
78+
qdel(query_inc_metacoins)
79+
return FALSE
7280
qdel(query_inc_metacoins)
7381

7482
//Output to chat

monkestation/code/modules/overwatch/subsystem.dm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ SUBSYSTEM_DEF(overwatch)
179179
if(!CheckDBCon())
180180
return
181181

182-
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM overwatch_whitelist WHERE ckey = '[ckey]'")
182+
var/datum/db_query/query = SSdbcore.NewQuery(
183+
"SELECT ckey FROM overwatch_whitelist WHERE ckey = :ckey",
184+
list("ckey" = ckey)
185+
)
183186
query.Execute()
184187

185188
if(query.NextRow())

monkestation/code/modules/slimecore/mobs/ai_controller/behaviours/find_target_without_trait.dm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
var/vision_range = 9
55
/// Blackboard key for aggro range, uses vision range if not specified
66
var/aggro_range_key = BB_AGGRO_RANGE
7-
/// Static typecache list of potentially dangerous objs
8-
var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha))
97
///our max size
108
var/checks_size = FALSE
119

@@ -27,9 +25,9 @@
2725
controller.clear_blackboard_key(target_key)
2826
var/list/potential_targets = hearers(aggro_range, controller.pawn) - living_mob //Remove self, so we don't suicide
2927

30-
for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines?
31-
if(can_see(living_mob, HM, aggro_range))
32-
potential_targets += HM
28+
for(var/atom/hostile_machine as anything in GLOB.hostile_machines) //Can we see any hostile machines?
29+
if (can_see(living_mob, hostile_machine, aggro_range))
30+
potential_targets += hostile_machine
3331

3432
if(!potential_targets.len)
3533
finish_action(controller, succeeded = FALSE)

monkestation/code/modules/slimecore/mobs/ai_controller/behaviours/flee_from_item.dm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
var/vision_range = 9
55
/// Blackboard key for aggro range, uses vision range if not specified
66
var/aggro_range_key = BB_AGGRO_RANGE
7-
/// Static typecache list of potentially dangerous objs
8-
var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha))
97

108
/datum/ai_behavior/find_potential_targets_with_item/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, scared_item_key)
119
. = ..()
@@ -26,9 +24,9 @@
2624
controller.clear_blackboard_key(target_key)
2725
var/list/potential_targets = hearers(aggro_range, controller.pawn) - living_mob //Remove self, so we don't suicide
2826

29-
for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines?
30-
if(can_see(living_mob, HM, aggro_range))
31-
potential_targets += HM
27+
for(var/atom/hostile_machine as anything in GLOB.hostile_machines) //Can we see any hostile machines?
28+
if (can_see(living_mob, hostile_machine, aggro_range))
29+
potential_targets += hostile_machine
3230

3331
if(!potential_targets.len)
3432
finish_action(controller, succeeded = FALSE)

monkestation/code/modules/store/atm/_atm.dm

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -188,40 +188,45 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/atm, 30)
188188

189189
/obj/machinery/atm/attacked_by(obj/item/attacking_item, mob/living/user)
190190
. = ..()
191-
if(do_after(user, 1 SECONDS, src))
192-
if(istype(attacking_item, /obj/item/stack/monkecoin))
193-
var/obj/item/stack/monkecoin/attacked_coins = attacking_item
194-
if(!user.client.prefs.adjust_metacoins(user.client.ckey, attacked_coins.amount, donator_multipler = FALSE))
195-
say("Error acceptings coins, please try again later.")
196-
return
197-
qdel(attacked_coins)
198-
say("Coins deposited to your account, have a nice day.")
199-
200-
if(attacking_item in subtypesof(/obj/item/stack/spacecash))
201-
var/obj/item/stack/spacecash/attacked_cash = attacking_item
202-
var/obj/item/user_id = user.get_item_by_slot(ITEM_SLOT_ID)
203-
if(user_id && istype(user_id, /obj/item/card/id))
204-
var/obj/item/card/id/id_card = user_id.GetID()
205-
id_card.registered_account.account_balance += attacked_cash.get_item_credit_value()
206-
else
207-
if(ishuman(user))
208-
var/mob/living/carbon/human/human_user = user
209-
var/datum/bank_account/user_account = SSeconomy.bank_accounts_by_id["[human_user.account_id]"]
210-
user_account.account_balance += attacked_cash.get_item_credit_value()
211-
qdel(attacked_cash)
212-
213-
else if(istype(attacking_item, /obj/item/holochip))
214-
var/obj/item/holochip/attacked_chip = attacking_item
215-
var/obj/item/user_id = user.get_item_by_slot(ITEM_SLOT_ID)
216-
if(user_id && istype(user_id, /obj/item/card/id))
217-
var/obj/item/card/id/id_card = user_id.GetID()
218-
id_card.registered_account.account_balance += attacked_chip.credits
219-
else
220-
if(ishuman(user))
221-
var/mob/living/carbon/human/human_user = user
222-
var/datum/bank_account/user_account = SSeconomy.bank_accounts_by_id["[human_user.account_id]"]
223-
user_account.account_balance += attacked_chip.credits
224-
qdel(attacked_chip)
191+
if(QDELETED(user) || QDELETED(attacking_item) || DOING_INTERACTION(user, DOAFTER_SOURCE_ATM))
192+
return
193+
if(!do_after(user, 1 SECONDS, src, interaction_key = DOAFTER_SOURCE_ATM))
194+
return
195+
if(QDELETED(user) || QDELETED(attacking_item) || DOING_INTERACTION(user, DOAFTER_SOURCE_ATM))
196+
return
197+
if(istype(attacking_item, /obj/item/stack/monkecoin))
198+
var/obj/item/stack/monkecoin/attacked_coins = attacking_item
199+
if(!user.client.prefs.adjust_metacoins(user.client.ckey, attacked_coins.amount, donator_multipler = FALSE))
200+
say("Error accepting coins, please try again later.")
201+
return
202+
qdel(attacked_coins)
203+
say("Coins deposited to your account, have a nice day.")
204+
205+
else if(istype(attacking_item, /obj/item/stack/spacecash))
206+
var/obj/item/stack/spacecash/attacked_cash = attacking_item
207+
var/obj/item/user_id = user.get_item_by_slot(ITEM_SLOT_ID)
208+
if(user_id && istype(user_id, /obj/item/card/id))
209+
var/obj/item/card/id/id_card = user_id.GetID()
210+
id_card.registered_account.account_balance += attacked_cash.get_item_credit_value()
211+
else
212+
if(ishuman(user))
213+
var/mob/living/carbon/human/human_user = user
214+
var/datum/bank_account/user_account = SSeconomy.bank_accounts_by_id["[human_user.account_id]"]
215+
user_account.account_balance += attacked_cash.get_item_credit_value()
216+
qdel(attacked_cash)
217+
218+
else if(istype(attacking_item, /obj/item/holochip))
219+
var/obj/item/holochip/attacked_chip = attacking_item
220+
var/obj/item/user_id = user.get_item_by_slot(ITEM_SLOT_ID)
221+
if(user_id && istype(user_id, /obj/item/card/id))
222+
var/obj/item/card/id/id_card = user_id.GetID()
223+
id_card.registered_account.account_balance += attacked_chip.credits
224+
else
225+
if(ishuman(user))
226+
var/mob/living/carbon/human/human_user = user
227+
var/datum/bank_account/user_account = SSeconomy.bank_accounts_by_id["[human_user.account_id]"]
228+
user_account.account_balance += attacked_chip.credits
229+
qdel(attacked_chip)
225230

226231
/obj/machinery/atm/proc/withdraw_cash()
227232
var/mob/living/living_mob = usr

rust_g.dll

683 KB
Binary file not shown.

tgui/packages/tgui-panel/chat/renderer.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ const handleImageError = (e) => {
8181
setTimeout(() => {
8282
/** @type {HTMLImageElement} */
8383
const node = e.target;
84-
if (!node) return;
84+
if (!node) {
85+
return;
86+
}
8587
const attempts = parseInt(node.getAttribute('data-reload-n'), 10) || 0;
8688
if (attempts >= IMAGE_RETRY_LIMIT) {
8789
logger.error(`failed to load an image after ${attempts} attempts`);

tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ img {
2222
img.icon {
2323
height: 1em;
2424
min-height: 16px;
25+
min-width: 16px;
2526
width: auto;
2627
vertical-align: bottom;
2728
}

0 commit comments

Comments
 (0)