Files
Bubberstation/code/modules/library/bookcase.dm
MrMelbert 5261efb67f Re-refactors batons / Refactors attack chain force modifiers (#90809)
## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
2025-05-19 13:32:12 +10:00

255 lines
8.3 KiB
Plaintext

#define BOOKCASE_UNANCHORED 0
#define BOOKCASE_ANCHORED 1
#define BOOKCASE_FINISHED 2
/obj/structure/bookcase
name = "bookcase"
icon = 'icons/obj/service/library.dmi'
icon_state = "bookempty"
desc = "A great place for storing knowledge."
anchored = FALSE
density = TRUE
opacity = FALSE
resistance_flags = FLAMMABLE
max_integrity = 200
armor_type = /datum/armor/structure_bookcase
var/state = BOOKCASE_UNANCHORED
/// When enabled, books_to_load number of random books will be generated for this bookcase
var/load_random_books = FALSE
/// The category of books to pick from when populating random books.
var/random_category = BOOK_CATEGORY_RANDOM
/// Probability that a category will be changed to random regardless of what it was set to.
var/category_prob = 25
/// How many random books to generate.
var/books_to_load = 0
/datum/armor/structure_bookcase
fire = 50
/obj/structure/bookcase/Initialize(mapload)
. = ..()
if(!mapload || QDELETED(src))
return
// Only mapload from here on
set_anchored(TRUE)
state = BOOKCASE_FINISHED
for(var/obj/item/I in loc)
if(!isbook(I))
continue
I.forceMove(src)
update_appearance()
if(SSlibrary.initialized)
INVOKE_ASYNC(src, PROC_REF(load_shelf))
else
SSlibrary.shelves_to_load += src
///proc for doing things after a bookcase is randomly populated
/obj/structure/bookcase/proc/after_random_load()
return
///Loads the shelf, both by allowing it to generate random items, and by adding its contents to a list used by library machines
/obj/structure/bookcase/proc/load_shelf()
//Loads a random selection of books in from the db, adds a copy of their info to a global list
//To send to library consoles as a starting inventory
if(load_random_books)
var/randomizing_categories = prob(category_prob) || random_category == BOOK_CATEGORY_RANDOM
// We only need to run this special logic if we're randomizing a non-adult bookshelf
if(randomizing_categories && random_category != BOOK_CATEGORY_ADULT)
// Category is manually randomized rather than using BOOK_CATEGORY_RANDOM
// So we can exclude adult books in non-adult bookshelves
// And also weight the prime category more heavily
var/list/category_pool = list(
BOOK_CATEGORY_FICTION,
BOOK_CATEGORY_NONFICTION,
BOOK_CATEGORY_REFERENCE,
BOOK_CATEGORY_RELIGION,
)
if(random_category != BOOK_CATEGORY_RANDOM)
category_pool += random_category
var/sub_books_to_load = books_to_load
while(sub_books_to_load > 0 && length(category_pool) > 0)
var/cat_amount = min(rand(1, 2), sub_books_to_load)
sub_books_to_load -= cat_amount
create_random_books(amount = cat_amount, location = src, category = pick_n_take(category_pool))
// Otherwise we can just let the proc handle everything, it will even do randomization for us
else
create_random_books(amount = books_to_load, location = src, category = randomizing_categories ? BOOK_CATEGORY_RANDOM : random_category)
after_random_load()
update_appearance() //Make sure you look proper
var/area/our_area = get_area(src)
var/area_type = our_area.type //Save me from the dark
if(!SSlibrary.books_by_area[area_type])
SSlibrary.books_by_area[area_type] = list()
//Time to populate that list
var/list/books_in_area = SSlibrary.books_by_area[area_type]
for(var/obj/item/book/book in contents)
var/datum/book_info/info = book.book_data
books_in_area += info.return_copy()
/obj/structure/bookcase/examine(mob/user)
. = ..()
if(!anchored)
. += span_notice("The <i>bolts</i> on the bottom are unsecured.")
else
. += span_notice("It's secured in place with <b>bolts</b>.")
switch(state)
if(BOOKCASE_UNANCHORED)
. += span_notice("There's a <b>small crack</b> visible on the back panel.")
if(BOOKCASE_ANCHORED)
. += span_notice("There's space inside for a <i>wooden</i> shelf.")
if(BOOKCASE_FINISHED)
. += span_notice("There's a <b>small crack</b> visible on the shelf.")
/obj/structure/bookcase/set_anchored(anchorvalue)
. = ..()
if(isnull(.))
return
state = anchorvalue
if(!anchorvalue) //in case we were vareditted or uprooted by a hostile mob, ensure we drop all our books instead of having them disappear till we're rebuild.
var/atom/Tsec = drop_location()
for(var/obj/I in contents)
if(!isbook(I))
continue
I.forceMove(Tsec)
update_appearance()
/obj/structure/bookcase/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
if(state == BOOKCASE_UNANCHORED)
if(attacking_item.tool_behaviour == TOOL_WRENCH)
if(attacking_item.use_tool(src, user, 20, volume=50))
balloon_alert(user, "wrenched in place")
set_anchored(TRUE)
return
if(attacking_item.tool_behaviour == TOOL_CROWBAR)
if(attacking_item.use_tool(src, user, 20, volume=50))
balloon_alert(user, "pried apart")
deconstruct(TRUE)
return
return ..()
if(state == BOOKCASE_ANCHORED)
if(istype(attacking_item, /obj/item/stack/sheet/mineral/wood))
var/obj/item/stack/sheet/mineral/wood/W = attacking_item
if(W.get_amount() < 2)
balloon_alert(user, "not enough wood")
return
W.use(2)
balloon_alert(user, "shelf added")
state = BOOKCASE_FINISHED
update_appearance()
return
if(attacking_item.tool_behaviour == TOOL_WRENCH)
attacking_item.play_tool_sound(src, 100)
balloon_alert(user, "unwrenched the frame")
set_anchored(FALSE)
return
return ..()
if(isbook(attacking_item))
if(!user.transferItemToLoc(attacking_item, src))
return ..()
update_appearance()
return
if(atom_storage)
var/found_anything = FALSE
for(var/obj/item/T in attacking_item.contents)
if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook))
atom_storage.attempt_remove(T, src)
found_anything = TRUE
if (found_anything)
balloon_alert(user, "emptied into [src]")
update_appearance()
return
if(IS_WRITING_UTENSIL(attacking_item))
if(!user.can_perform_action(src) || !user.can_write(attacking_item))
return ..()
var/newname = tgui_input_text(user, "What would you like to title this bookshelf?", "Bookshelf Renaming", max_length = MAX_NAME_LEN)
if(!user.can_perform_action(src) || !user.can_write(attacking_item))
return ..()
if(!newname)
return
name = "bookcase ([sanitize(newname)])"
return
if(attacking_item.tool_behaviour == TOOL_CROWBAR)
if(length(contents))
balloon_alert(user, "remove the books first")
return
attacking_item.play_tool_sound(src, 100)
balloon_alert(user, "pried the shelf out")
new /obj/item/stack/sheet/mineral/wood(drop_location(), 2)
state = BOOKCASE_ANCHORED
update_appearance()
return
return ..()
/obj/structure/bookcase/attack_hand(mob/living/user, list/modifiers)
. = ..()
if(.)
return
if(!istype(user))
return
if(!length(contents))
return
var/obj/item/book/choice = tgui_input_list(user, "Book to remove from the shelf", "Remove Book", sort_names(contents.Copy()))
if(isnull(choice))
return
if(!(user.mobility_flags & MOBILITY_USE) || user.stat != CONSCIOUS || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) || !in_range(loc, user))
return
if(ishuman(user))
if(!user.get_active_held_item())
user.put_in_hands(choice)
else
choice.forceMove(drop_location())
update_appearance()
/obj/structure/bookcase/atom_deconstruct(disassembled = TRUE)
var/atom/Tsec = drop_location()
new /obj/item/stack/sheet/mineral/wood(Tsec, 4)
for(var/obj/item/I in contents)
if(!isbook(I)) //Wake me up inside
continue
I.forceMove(Tsec)
/obj/structure/bookcase/update_icon_state()
if(state == BOOKCASE_UNANCHORED || state == BOOKCASE_ANCHORED)
icon_state = "bookempty"
return ..()
var/amount = length(contents)
icon_state = "book-[clamp(amount, 0, 5)]"
return ..()
/obj/structure/bookcase/manuals/engineering
name = "engineering manuals bookcase"
/obj/structure/bookcase/manuals/engineering/Initialize(mapload)
. = ..()
new /obj/item/book/manual/wiki/engineering_construction(src)
new /obj/item/book/manual/wiki/engineering_hacking(src)
new /obj/item/book/manual/wiki/engineering_guide(src)
new /obj/item/book/manual/wiki/robotics_cyborgs(src)
update_appearance()
/obj/structure/bookcase/manuals/research_and_development
name = "\improper R&D manuals bookcase"
/obj/structure/bookcase/manuals/research_and_development/Initialize(mapload)
. = ..()
new /obj/item/book/manual/wiki/research_and_development(src)
update_appearance()
#undef BOOKCASE_UNANCHORED
#undef BOOKCASE_ANCHORED
#undef BOOKCASE_FINISHED