diff --git a/code/__DEFINES/living.dm b/code/__DEFINES/living.dm index 6721ad28997..920b8054445 100644 --- a/code/__DEFINES/living.dm +++ b/code/__DEFINES/living.dm @@ -10,6 +10,8 @@ * update_body_parts() is going to be called ONE time once everything is done. */ #define STOP_OVERLAY_UPDATE_BODY_PARTS (1<<2) +/// Nutrition changed last life tick, so we should bulk update this tick +#define QUEUE_NUTRITION_UPDATE (1<<3) /// Getter for a mob/living's lying angle, otherwise protected #define GET_LYING_ANGLE(mob) (UNLINT(mob.lying_angle)) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 433a26c6278..772125facc7 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -242,6 +242,7 @@ #define NUTRITION_LEVEL_WELL_FED 450 #define NUTRITION_LEVEL_FED 350 #define NUTRITION_LEVEL_HUNGRY 250 +#define NUTRITION_LEVEL_VERY_HUNGRY 200 #define NUTRITION_LEVEL_STARVING 150 #define NUTRITION_LEVEL_START_MIN 250 diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 837c3eb16ee..aec20020c3e 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -97,7 +97,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/atom/movable/screen/stamina var/atom/movable/screen/healthdoll/healthdoll var/atom/movable/screen/spacesuit - var/atom/movable/screen/hunger + var/atom/movable/screen/hunger/hunger // subtypes can override this to force a specific UI style var/ui_style diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 9bcc565f1d9..ff312bb15ca 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -925,26 +925,30 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) icon_state = "stamina0" screen_loc = ui_stamina -#define HUNGER_STATE_FAT 2 -#define HUNGER_STATE_FULL 1 -#define HUNGER_STATE_FINE 0 -#define HUNGER_STATE_HUNGRY -1 -#define HUNGER_STATE_STARVING -2 +#define HUNGER_STATE_FAT 5 +#define HUNGER_STATE_FULL 4 +#define HUNGER_STATE_FINE 3 +#define HUNGER_STATE_HUNGRY 2 +#define HUNGER_STATE_VERY_HUNGRY 1 +#define HUNGER_STATE_STARVING 0 /atom/movable/screen/hunger name = "hunger" icon_state = "hungerbar" - base_icon_state = "hungerbar" screen_loc = ui_hunger mouse_opacity = MOUSE_OPACITY_TRANSPARENT /// What state of hunger are we in? - VAR_PRIVATE/state = HUNGER_STATE_FINE + VAR_PRIVATE/state + /// What was the last fullness we recorded? + VAR_PRIVATE/fullness /// What food icon do we show by the bar var/food_icon = 'icons/obj/food/burgerbread.dmi' /// What food icon state do we show by the bar var/food_icon_state = "hburger" /// The image shown by the bar. VAR_PRIVATE/image/food_image + /// The actual bar + VAR_PRIVATE/atom/movable/screen/hunger_bar/hunger_bar /atom/movable/screen/hunger/Initialize(mapload, datum/hud/hud_owner) . = ..() @@ -955,14 +959,18 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) if(!ishuman(hungry) || CONFIG_GET(flag/disable_human_mood)) screen_loc = ui_mood // Slot in where mood normally is if mood is disabled + // Burger next to the bar food_image = image(icon = food_icon, icon_state = food_icon_state, pixel_x = -5) food_image.plane = plane food_image.appearance_flags |= KEEP_APART // To be unaffected by filters applied to src food_image.add_filter("simple_outline", 2, outline_filter(1, COLOR_BLACK, OUTLINE_SHARP)) underlays += food_image // To be below filters applied to src - SetInvisibility(INVISIBILITY_ABSTRACT, name) // Start invisible, update later - update_appearance() + // The actual bar + hunger_bar = new(src, null) + vis_contents += hunger_bar + + update_hunger_bar(instant = TRUE) /atom/movable/screen/hunger/proc/update_hunger_state() var/mob/living/hungry = hud?.mymob @@ -970,61 +978,129 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) return if(HAS_TRAIT(hungry, TRAIT_NOHUNGER) || !hungry.get_organ_slot(ORGAN_SLOT_STOMACH)) + fullness = NUTRITION_LEVEL_FED state = HUNGER_STATE_FINE return - if(HAS_TRAIT(hungry, TRAIT_FAT)) + fullness = NUTRITION_LEVEL_FAT state = HUNGER_STATE_FAT return if(HAS_TRAIT(hungry, TRAIT_GLUTTON)) + fullness = NUTRITION_LEVEL_VERY_HUNGRY state = HUNGER_STATE_HUNGRY // Can't get enough return - switch(hungry.nutrition) - if(NUTRITION_LEVEL_FULL to INFINITY) + fullness = round(hungry.get_fullness(only_consumable = TRUE), 0.05) + switch(fullness) + if(1 + NUTRITION_LEVEL_FULL to INFINITY) state = HUNGER_STATE_FULL - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL) + if(1 + NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FULL) state = HUNGER_STATE_FINE - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + if(1 + NUTRITION_LEVEL_VERY_HUNGRY to NUTRITION_LEVEL_HUNGRY) + state = HUNGER_STATE_FINE + if(1 + NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_VERY_HUNGRY) state = HUNGER_STATE_HUNGRY if(0 to NUTRITION_LEVEL_STARVING) state = HUNGER_STATE_STARVING /atom/movable/screen/hunger/update_appearance(updates) + update_hunger_bar() + return ..() + +/// Updates the hunger bar's appearance. +/// If `instant` is TRUE, the bar will update immediately rather than animating. +/atom/movable/screen/hunger/proc/update_hunger_bar(instant = FALSE) var/old_state = state - update_hunger_state() // Do this before we call all the other update procs - if(state == old_state) // Let's not be wasteful - return + var/old_fullness = fullness + update_hunger_state() + if(old_state != state || old_fullness != fullness) + // Fades out if we ARE "fine" AND if our stomach has no food digesting + var/mob/living/hungry = hud?.mymob + if(alpha == 255 && (state == HUNGER_STATE_FINE && abs(fullness - hungry.nutrition) < 1)) + if(instant) + alpha = 0 + else + animate(src, alpha = 0, time = 1 SECONDS) + // Fades in if we WERE "fine" OR if our stomach has food digesting + else if(alpha == 0 && (state != HUNGER_STATE_FINE || abs(fullness - hungry.nutrition) >= 1)) + if(instant) + alpha = 255 + else + animate(src, alpha = 255, time = 1 SECONDS) + + if(old_state != state) + // Update filter around the bar + if(state == HUNGER_STATE_STARVING) + if(!get_filter("hunger_outline")) + add_filter("hunger_outline", 1, list("type" = "outline", "color" = "#FF0033", "alpha" = 0, "size" = 2)) + animate(get_filter("hunger_outline"), alpha = 200, time = 1.5 SECONDS, loop = -1) + animate(alpha = 0, time = 1.5 SECONDS) + + else if(old_state == HUNGER_STATE_STARVING) + remove_filter("hunger_outline") + + // Update color of the food + if((state == HUNGER_STATE_FAT) != (old_state == HUNGER_STATE_FAT)) + underlays -= food_image + food_image.color = state == HUNGER_STATE_FAT ? COLOR_DARK : null + underlays += food_image + + // Update hunger bar + if(old_fullness != fullness) + // instant if invisible OR if instant is set + hunger_bar.update_fullness(fullness, alpha == 0 || instant) + +/atom/movable/screen/hunger_bar + icon_state = "hungerbar_bar" + screen_loc = ui_hunger + vis_flags = VIS_INHERIT_ID | VIS_INHERIT_PLANE + /// Mask + VAR_PRIVATE/static/icon/bar_mask + /// Gradient used to color the bar + VAR_PRIVATE/static/list/hunger_gradient = list( + 0.0, "#FF0000", + 0.2, "#FF8000", + 0.4, "#f0f000", + 0.6, "#00FF00", + 0.8, "#46daff", + 1.0, "#2A72AA", + 1.2, "#494949", + ) + /// Offset of the mask + VAR_PRIVATE/bar_offset + /// Last "fullness" value (rounded) we used to update the bar + VAR_PRIVATE/last_fullness_band = -1 + +/atom/movable/screen/hunger_bar/Initialize(mapload, datum/hud/hud_owner) . = ..() - if(state == HUNGER_STATE_FINE) - SetInvisibility(INVISIBILITY_ABSTRACT, name) + var/atom/movable/movable_loc = ismovable(loc) ? loc : null + screen_loc = movable_loc?.screen_loc + bar_mask ||= icon(icon, "hungerbar_mask") + +/atom/movable/screen/hunger_bar/proc/update_fullness(new_fullness, instant) + new_fullness = round(new_fullness / NUTRITION_LEVEL_FULL, 0.05) + if(new_fullness == last_fullness_band) return - - else if(invisibility) - RemoveInvisibility(name) - - if(state == HUNGER_STATE_STARVING) - if(!get_filter("hunger_outline")) - add_filter("hunger_outline", 1, list("type" = "outline", "color" = "#FF0033", "alpha" = 0, "size" = 2)) - animate(get_filter("hunger_outline"), alpha = 200, time = 1.5 SECONDS, loop = -1) - animate(alpha = 0, time = 1.5 SECONDS) - + last_fullness_band = new_fullness + // Update color + var/new_color = gradient(hunger_gradient, clamp(new_fullness, 0, 1.2)) + if(instant) + color = new_color else - remove_filter("hunger_outline") - - // Update color of the food - if((state == HUNGER_STATE_FAT) != (old_state == HUNGER_STATE_FAT)) - underlays -= food_image - food_image.color = state == HUNGER_STATE_FAT ? COLOR_DARK : null - underlays += food_image - -/atom/movable/screen/hunger/update_icon_state() - . = ..() - icon_state = "[base_icon_state][state]" + animate(src, color = new_color, 0.5 SECONDS) + // Update mask + var/old_bar_offset = bar_offset + bar_offset = clamp(-20 + (20 * new_fullness), -20, 0) + if(old_bar_offset != bar_offset) + if(instant || isnull(old_bar_offset)) + add_filter("hunger_bar_mask", 1, alpha_mask_filter(0, bar_offset, bar_mask)) + else + transition_filter("hunger_bar_mask", alpha_mask_filter(0, bar_offset), 0.5 SECONDS) #undef HUNGER_STATE_FAT -#undef HUNGER_STATE_FULL #undef HUNGER_STATE_FINE +#undef HUNGER_STATE_FULL #undef HUNGER_STATE_HUNGRY #undef HUNGER_STATE_STARVING +#undef HUNGER_STATE_VERY_HUNGRY diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 8faf32fef56..a5e96335c19 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -501,6 +501,7 @@ Behavior that's still missing from this component that original food items had t var/fraction = 0.3 fraction = min(bite_consumption / owner.reagents.total_volume, 1) owner.reagents.trans_to(eater, bite_consumption, transferred_by = feeder, methods = INGEST) + eater.hud_used?.hunger?.update_hunger_bar() bitecount++ checkLiked(fraction, eater) diff --git a/code/datums/elements/food/foodlike_drink.dm b/code/datums/elements/food/foodlike_drink.dm index e83468d68a1..e8f6cf49f9e 100644 --- a/code/datums/elements/food/foodlike_drink.dm +++ b/code/datums/elements/food/foodlike_drink.dm @@ -38,6 +38,7 @@ return source.attack(user, user) + user.hud_used?.hunger?.update_hunger_bar() /datum/element/foodlike_drink/proc/can_keep_drinking(obj/item/reagent_containers/source, mob/living/user) if(QDELETED(source) || user.get_active_held_item() != source) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 5d2e70ec2de..08d493ada1c 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -128,8 +128,10 @@ add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fed) if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) clear_mood_event(MOOD_CATEGORY_NUTRITION) - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + if(NUTRITION_LEVEL_VERY_HUNGRY to NUTRITION_LEVEL_HUNGRY) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/hungry) + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_VERY_HUNGRY) + add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/hungry_very) if(0 to NUTRITION_LEVEL_STARVING) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/starving) @@ -339,7 +341,9 @@ msg += "[span_info("I'm not hungry.")]
" if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) msg += "[span_info("I could use a bite to eat.")]
" - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + if(NUTRITION_LEVEL_VERY_HUNGRY to NUTRITION_LEVEL_HUNGRY) + msg += "[span_warning("I'm feeling hungry.")]
" + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_VERY_HUNGRY) msg += "[span_warning("I feel quite hungry.")]
" if(0 to NUTRITION_LEVEL_STARVING) msg += "[span_boldwarning("I'm starving!")]
" diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index 66f930fa784..96f35708a36 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -17,6 +17,10 @@ /datum/mood_event/hungry description = "I'm getting a bit hungry." + mood_change = -3 + +/datum/mood_event/hungry_very + description = "I'm hungry!" mood_change = -6 /datum/mood_event/starving diff --git a/code/modules/mob/living/carbon/human/init_signals.dm b/code/modules/mob/living/carbon/human/init_signals.dm index a425a5c279f..8e6465cf0f3 100644 --- a/code/modules/mob/living/carbon/human/init_signals.dm +++ b/code/modules/mob/living/carbon/human/init_signals.dm @@ -51,7 +51,7 @@ /mob/living/carbon/human/proc/on_fat(datum/source) SIGNAL_HANDLER - hud_used?.hunger?.update_appearance() + hud_used?.hunger?.update_hunger_bar() mob_mood?.update_nutrition_moodlets() if(HAS_TRAIT(src, TRAIT_FAT)) @@ -68,7 +68,7 @@ overeatduration = 0 remove_traits(list(TRAIT_FAT, TRAIT_OFF_BALANCE_TACKLER), OBESITY) else - hud_used?.hunger?.update_appearance() + hud_used?.hunger?.update_hunger_bar() mob_mood?.update_nutrition_moodlets() /// Signal proc for [COMSIG_ATOM_CONTENTS_WEIGHT_CLASS_CHANGED] to check if an item is suddenly too heavy for our pockets diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 3f81b193d0d..d7471e6b7b3 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -686,22 +686,29 @@ //Stomach// /////////// -/mob/living/carbon/get_fullness() - var/fullness = nutrition +/mob/living/carbon/get_fullness(only_consumable) + . = ..() var/obj/item/organ/stomach/belly = get_organ_slot(ORGAN_SLOT_STOMACH) if(!belly) //nothing to see here if we do not have a stomach - return fullness + return . - for(var/bile in belly.reagents.reagent_list) - var/datum/reagent/bits = bile - if(istype(bits, /datum/reagent/consumable)) - var/datum/reagent/consumable/goodbit = bile - fullness += goodbit.get_nutriment_factor(src) * goodbit.volume / goodbit.metabolization_rate + for(var/datum/reagent/bits as anything in belly.reagents.reagent_list) + // hack to get around stomachs having 5u stomach lining reagent ugugugu + var/effective_volume = bits.volume + if(belly.food_reagents[bits.type]) + effective_volume -= belly.food_reagents[bits.type] + if(effective_volume <= 0) continue - fullness += 0.6 * bits.volume / bits.metabolization_rate //not food takes up space + if(istype(bits, /datum/reagent/consumable)) + var/datum/reagent/consumable/goodbit = bits + . += goodbit.get_nutriment_factor(src) * effective_volume / goodbit.metabolization_rate + continue + if(!only_consumable) + continue + . += 0.6 * effective_volume / bits.metabolization_rate //not food takes up space - return fullness + return . /mob/living/carbon/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE) . = ..() diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 59bc1a9f4d8..374de84bb27 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -63,6 +63,11 @@ handle_wounds(seconds_per_tick, times_fired) + if(living_flags & QUEUE_NUTRITION_UPDATE) + mob_mood?.update_nutrition_moodlets() + hud_used?.hunger?.update_hunger_bar() + living_flags &= ~QUEUE_NUTRITION_UPDATE + if(stat != DEAD) return 1 @@ -97,18 +102,19 @@ /** * Get the fullness of the mob * - * This returns a value form 0 upwards to represent how full the mob is. - * The value is a total amount of consumable reagents in the body combined - * with the total amount of nutrition they have. - * This does not have an upper limit. + * Fullness is a representation of how much nutrition the mob has, + * including the nutrition of stuff yet to be digested (reagents in blood / stomach) + * + * * only_consumable - if TRUE, only consumable reagents are counted. + * Otherwise, all reagents contribute to fullness, despite not adding nutrition as they process. + * + * Returns a number representing fullness, scaled similarly to nutrition. */ -/mob/living/proc/get_fullness() +/mob/living/proc/get_fullness(only_consumable) var/fullness = nutrition // we add the nutrition value of what we're currently digesting - for(var/bile in reagents.reagent_list) - var/datum/reagent/consumable/bits = bile - if(bits) - fullness += bits.get_nutriment_factor(src) * bits.volume / bits.metabolization_rate + for(var/datum/reagent/consumable/bits in reagents.reagent_list) + fullness += bits.get_nutriment_factor(src) * bits.volume / bits.metabolization_rate return fullness /** diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 97f92018200..ec5463b40dd 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1448,11 +1448,15 @@ return nutrition = max(0, nutrition + change) - hud_used?.hunger?.update_appearance() /mob/living/adjust_nutrition(change, forced) . = ..() - mob_mood?.update_nutrition_moodlets() + // Queue update if change is small enough (6 is 1% of nutrition softcap) + if(abs(change) >= 6) + mob_mood?.update_nutrition_moodlets() + hud_used?.hunger?.update_hunger_bar() + else + living_flags |= QUEUE_NUTRITION_UPDATE ///Force set the mob nutrition /mob/proc/set_nutrition(set_to, forced = FALSE) //Seriously fuck you oldcoders. @@ -1460,11 +1464,16 @@ return nutrition = max(0, set_to) - hud_used?.hunger?.update_appearance() /mob/living/set_nutrition(set_to, forced) + var/old_nutrition = nutrition . = ..() - mob_mood?.update_nutrition_moodlets() + // Queue update if change is small enough (6 is 1% of nutrition softcap) + if(abs(old_nutrition - nutrition) >= 6) + mob_mood?.update_nutrition_moodlets() + hud_used?.hunger?.update_hunger_bar() + else + living_flags |= QUEUE_NUTRITION_UPDATE ///Apply a proper movespeed modifier based on items we have equipped /mob/proc/update_equipment_speed_mods() diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index f682a167ac3..53258eec7d2 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -384,7 +384,7 @@ /obj/item/organ/stomach/on_mob_insert(mob/living/carbon/receiver, special, movement_flags) . = ..() - receiver.hud_used?.hunger?.update_appearance() + receiver.hud_used?.hunger?.update_hunger_bar() RegisterSignal(receiver, COMSIG_CARBON_VOMITED, PROC_REF(on_vomit)) RegisterSignal(receiver, COMSIG_HUMAN_GOT_PUNCHED, PROC_REF(on_punched)) @@ -393,7 +393,7 @@ var/mob/living/carbon/human/human_owner = stomach_owner human_owner.clear_alert(ALERT_DISGUST) human_owner.clear_mood_event("disgust") - stomach_owner.hud_used?.hunger?.update_appearance() + stomach_owner.hud_used?.hunger?.update_hunger_bar() UnregisterSignal(stomach_owner, list(COMSIG_CARBON_VOMITED, COMSIG_HUMAN_GOT_PUNCHED)) return ..() diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi index c38725e43e4..f77266a28f1 100644 Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ