diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index a853fde0c5dee..af219a90eb1f4 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -162,6 +162,10 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_BAMBOO_WALLS S_TURF(17) //![/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo] #define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(18) //![turf/closed/indestructible/riveted/plastinum] +//DOPPLER EDIT ADDITION +#define SMOOTH_GROUP_STONE_WALLS S_OBJ(20) ///turf/closed/wall/mineral/stone, /obj/structure/falsewall/stone +//DOPPLER EDIT END + #define SMOOTH_GROUP_PAPERFRAME S_OBJ(21) ///obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe #define SMOOTH_GROUP_WINDOW_FULLTILE S_OBJ(22) ///turf/closed/indestructible/fakeglass, /obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/reinforced/plasma/fulltile diff --git a/code/__DEFINES/~doppler_defines/is_helpers.dm b/code/__DEFINES/~doppler_defines/is_helpers.dm new file mode 100644 index 0000000000000..ec8c01e36274c --- /dev/null +++ b/code/__DEFINES/~doppler_defines/is_helpers.dm @@ -0,0 +1,4 @@ +//Species +#define isprimitivedemihuman(A) (is_species(A, /datum/species/human/felinid/primitive)) +//Customization bases +#define isfeline(A) (isfelinid(A) || HAS_TRAIT(A, TRAIT_FELINE)) diff --git a/code/__DEFINES/~doppler_defines/keybindings.dm b/code/__DEFINES/~doppler_defines/keybindings.dm index 23bcff0827dc0..ef4105eaad209 100644 --- a/code/__DEFINES/~doppler_defines/keybindings.dm +++ b/code/__DEFINES/~doppler_defines/keybindings.dm @@ -1,2 +1,3 @@ +#define COMSIG_KB_LIVING_COMBAT_INDICATOR "keybinding_living_combat_indicator" #define COMSIG_KB_MOB_PIXEL_SHIFT_DOWN "keybinding_mob_pixel_shift_down" #define COMSIG_KB_MOB_PIXEL_SHIFT_UP "keybinding_mob_pixel_shift_up" diff --git a/code/__DEFINES/~doppler_defines/obj_flags_doppler.dm b/code/__DEFINES/~doppler_defines/obj_flags_doppler.dm new file mode 100644 index 0000000000000..da544ce37cbc1 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/obj_flags_doppler.dm @@ -0,0 +1,2 @@ +/// Whether something is repairable by the anvil +#define ANVIL_REPAIR (1<<0) diff --git a/code/__DEFINES/~doppler_defines/reagent_forging_tools.dm b/code/__DEFINES/~doppler_defines/reagent_forging_tools.dm new file mode 100644 index 0000000000000..e64be4c609a38 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/reagent_forging_tools.dm @@ -0,0 +1,4 @@ +#define TOOL_BILLOW "billow" +#define TOOL_TONG "tong" +#define TOOL_HAMMER "hammer" +#define TOOL_BLOWROD "blowrod" diff --git a/code/__DEFINES/~doppler_defines/reskin_defines.dm b/code/__DEFINES/~doppler_defines/reskin_defines.dm new file mode 100644 index 0000000000000..035e00b038c53 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/reskin_defines.dm @@ -0,0 +1,8 @@ +#define RESKIN_ICON "reskin_icon" +#define RESKIN_ICON_STATE "reskin_icon_state" +#define RESKIN_WORN_ICON "reskin_worn_icon" +#define RESKIN_WORN_ICON_STATE "reskin_worn_icon_state" +#define RESKIN_SUPPORTS_VARIATIONS_FLAGS "reskin_supports_variations_flags" +#define RESKIN_INHAND_L "reskin_inhand_l" +#define RESKIN_INHAND_R "reskin_inhand_r" +#define RESKIN_INHAND_STATE "reskin_inhand_state" diff --git a/code/__DEFINES/~doppler_defines/signals.dm b/code/__DEFINES/~doppler_defines/signals.dm new file mode 100644 index 0000000000000..9142e6086a8ab --- /dev/null +++ b/code/__DEFINES/~doppler_defines/signals.dm @@ -0,0 +1,2 @@ +///Fired in combat_indicator.dm, used for syncing CI between mech and pilot +#define COMSIG_MOB_CI_TOGGLED "mob_ci_toggled" diff --git a/code/__DEFINES/~doppler_defines/sound.dm b/code/__DEFINES/~doppler_defines/sound.dm new file mode 100644 index 0000000000000..b125bb5460ac7 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/sound.dm @@ -0,0 +1,6 @@ +/** + * Sound effect defines, used in modular_sounds on proc get_sfx. + */ + +#define SFX_BRICK_DROP "brick_drop" +#define SFX_BRICK_PICKUP "brick_pickup" diff --git a/code/__DEFINES/~doppler_defines/span.dm b/code/__DEFINES/~doppler_defines/span.dm new file mode 100644 index 0000000000000..c72af85fd2664 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/span.dm @@ -0,0 +1 @@ +#define span_italics(str) ("" + str + "") diff --git a/code/__DEFINES/~doppler_defines/species.dm b/code/__DEFINES/~doppler_defines/species.dm index 77e3b78260ad1..a354e5dfbdbb0 100644 --- a/code/__DEFINES/~doppler_defines/species.dm +++ b/code/__DEFINES/~doppler_defines/species.dm @@ -1,2 +1,4 @@ +// Hearthkin, from the Ice Moon +#define SPECIES_FELINE_PRIMITIVE "primitive_felinid" // Slugcats, from Talon III. #define SPECIES_SLUGCAT "slugcat" diff --git a/code/__DEFINES/~doppler_defines/techweb_nodes.dm b/code/__DEFINES/~doppler_defines/techweb_nodes.dm new file mode 100644 index 0000000000000..3070194171011 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/techweb_nodes.dm @@ -0,0 +1,4 @@ +#define TECHWEB_NODE_XENOARCH_ADVANCED "adv_xenoarch" +#define TECHWEB_NODE_XENOARCH_BASIC "basic_xenoarch" +#define TECHWEB_NODE_XENOARCH_MACHINES "xenoarch_machines" +#define TECHWEB_NODE_XENOARCH_STORAGE "xenoarch_storage" diff --git a/code/__DEFINES/~doppler_defines/traits.dm b/code/__DEFINES/~doppler_defines/traits.dm new file mode 100644 index 0000000000000..2c9804211f5a6 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/traits.dm @@ -0,0 +1,11 @@ +/// trait that lets you do xenoarch magnification +#define TRAIT_XENOARCH_QUALIFIED "trait_xenoarch_qualified" + +/// Traits granted by glassblowing +#define TRAIT_GLASSBLOWING "glassblowing" + +/// Trait that is applied whenever someone or something is glassblowing +#define TRAIT_CURRENTLY_GLASSBLOWING "currently_glassblowing" + +// felinid traits +#define TRAIT_FELINE "feline_aspect" diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 1bacd0ce2c774..8d49d79c240d2 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -15,6 +15,7 @@ // I tried to eliminate this proc but I couldn't untangle their init-order interdependencies -Dominion/Cyberboss init_keybindings() GLOB.emote_list = init_emote_list() // WHY DOES THIS NEED TO GO HERE? IT JUST INITS DATUMS + init_doppler_stack_recipes() //DOPPLER EDIT ADDITION - MODULAR CRAFTING init_crafting_recipes() init_crafting_recipes_atoms() diff --git a/code/__HELPERS/~doppler_helpers/global_lists.dm b/code/__HELPERS/~doppler_helpers/global_lists.dm new file mode 100644 index 0000000000000..5691430eecd6d --- /dev/null +++ b/code/__HELPERS/~doppler_helpers/global_lists.dm @@ -0,0 +1,29 @@ +/proc/init_doppler_stack_recipes() + var/list/additional_stack_recipes = list( + /obj/item/stack/sheet/leather = list(GLOB.doppler_leather_recipes, GLOB.doppler_leather_belt_recipes), + /obj/item/stack/sheet/iron = list(GLOB.doppler_metal_recipes), + /obj/item/stack/sheet/plasteel = list(GLOB.doppler_plasteel_recipes), + /obj/item/stack/sheet/mineral/wood = list(GLOB.doppler_wood_recipes), + /obj/item/stack/sheet/cloth = list(GLOB.doppler_cloth_recipes), + /obj/item/stack/ore/glass = list(GLOB.doppler_sand_recipes), + /obj/item/stack/rods = list(GLOB.doppler_rod_recipes), + /obj/item/stack/sheet/mineral/stone = list(GLOB.stone_recipes), + /obj/item/stack/sheet/mineral/clay = list(GLOB.clay_recipes), + ) + for(var/stack in additional_stack_recipes) + for(var/material_list in additional_stack_recipes[stack]) + for(var/stack_recipe in material_list) + if(istype(stack_recipe, /datum/stack_recipe_list)) + var/datum/stack_recipe_list/stack_recipe_list = stack_recipe + for(var/nested_recipe in stack_recipe_list.recipes) + if(!nested_recipe) + continue + var/datum/crafting_recipe/stack/recipe = new/datum/crafting_recipe/stack(stack, nested_recipe) + if(recipe.name != "" && recipe.result) + GLOB.crafting_recipes += recipe + else + if(!stack_recipe) + continue + var/datum/crafting_recipe/stack/recipe = new/datum/crafting_recipe/stack(stack, stack_recipe) + if(recipe.name != "" && recipe.result) + GLOB.crafting_recipes += recipe diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index dcda2365c4b0f..0dfcc67c5e1be 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -658,6 +658,14 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_TURF_IGNORE_SLIPPERY" = TRAIT_TURF_IGNORE_SLIPPERY, "TRAIT_TURF_IGNORE_SLOWDOWN" = TRAIT_TURF_IGNORE_SLOWDOWN, ), + // DOPPLER EDIT ADDITION START - DOPPLER TRAITS + /obj/item/clothing/suit/jacket/doppler = list( + "TRAIT_CURRENTLY_GLASSBLOWING" = TRAIT_CURRENTLY_GLASSBLOWING, + "TRAIT_FELINE" = TRAIT_FELINE, + "TRAIT_GLASSBLOWING" = TRAIT_GLASSBLOWING, + "TRAIT_XENOARCH_QUALIFIED" = TRAIT_XENOARCH_QUALIFIED, + ), + // DOPPLER EDIT ADDITION END )) /// value -> trait name, list of ALL traits that exist in the game, used for any type of accessing. diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 98c3fd6ab1fca..585d121e6ce67 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -357,6 +357,14 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( /obj/item/organ/internal/lungs = list( "TRAIT_SPACEBREATHING" = TRAIT_SPACEBREATHING, ), + // DOPPLER EDIT ADDITION START - DOPPLER TRAITS + /obj/item/clothing/suit/jacket/doppler = list( + "TRAIT_CURRENTLY_GLASSBLOWING" = TRAIT_CURRENTLY_GLASSBLOWING, + "TRAIT_FELINE" = TRAIT_FELINE, + "TRAIT_GLASSBLOWING" = TRAIT_GLASSBLOWING, + "TRAIT_XENOARCH_QUALIFIED" = TRAIT_XENOARCH_QUALIFIED, + ), + // DOPPLER EDIT ADDITION END )) /// value -> trait name, generated as needed for adminning. diff --git a/code/_globalvars/~doppler_globalvars/bitfields.dm b/code/_globalvars/~doppler_globalvars/bitfields.dm new file mode 100644 index 0000000000000..2795c9528166d --- /dev/null +++ b/code/_globalvars/~doppler_globalvars/bitfields.dm @@ -0,0 +1,4 @@ +///reagent forging module +DEFINE_BITFIELD(obj_flags_doppler, list( + "ANVIL_REPAIR" = ANVIL_REPAIR, +)) diff --git a/code/_globalvars/~doppler_globalvars/objective.dm b/code/_globalvars/~doppler_globalvars/objective.dm new file mode 100644 index 0000000000000..8658fb496e17e --- /dev/null +++ b/code/_globalvars/~doppler_globalvars/objective.dm @@ -0,0 +1,2 @@ +///new addition to cryosleep module +GLOBAL_LIST_EMPTY(objectives) diff --git a/code/_globalvars/~doppler_globalvars/religion.dm b/code/_globalvars/~doppler_globalvars/religion.dm new file mode 100644 index 0000000000000..b7ac48a69008c --- /dev/null +++ b/code/_globalvars/~doppler_globalvars/religion.dm @@ -0,0 +1,8 @@ +/// list of weakrefs to highpriest successor candidates. Every chaplain who joins after the initial chaplain is added to this list. The next high priest is chosen from them by seniority. +GLOBAL_LIST(holy_successors) +/// A weakref to the current high priest mob +GLOBAL_VAR(current_highpriest) +/// The previous sect's favor value +GLOBAL_VAR(prev_favor) +/// The previous sect's typepath +GLOBAL_VAR(prev_sect_type) diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index 45354d4bd6164..1f52c6812021f 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -26,6 +26,9 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list( list(/datum/quirk/photophobia, /datum/quirk/nyctophobia), list(/datum/quirk/item_quirk/settler, /datum/quirk/freerunning), list(/datum/quirk/numb, /datum/quirk/selfaware), + //DOPPLER EDIT ADDITION BEGIN + list(/datum/quirk/feline_aspect) + //DOPPLER EDIT ADDITION END )) GLOBAL_LIST_INIT(quirk_string_blacklist, generate_quirk_string_blacklist()) diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm index 10bed5a407760..8c71fc17c1b6a 100644 --- a/code/game/atom/atom_tool_acts.dm +++ b/code/game/atom/atom_tool_acts.dm @@ -109,6 +109,16 @@ act_result = is_left_clicking ? welder_act(user, tool) : welder_act_secondary(user, tool) if(TOOL_ANALYZER) act_result = is_left_clicking ? analyzer_act(user, tool) : analyzer_act_secondary(user, tool) + // DOPPLER EDIT ADDITION START - REAGENT FORGING AND GLASSBLOWING TOOLS + if(TOOL_BILLOW) + act_result = is_left_clicking ? billow_act(user, tool) : billow_act_secondary(user, tool) + if(TOOL_TONG) + act_result = is_left_clicking ? tong_act(user, tool) : tong_act_secondary(user, tool) + if(TOOL_HAMMER) + act_result = is_left_clicking ? hammer_act(user, tool) : hammer_act_secondary(user, tool) + if(TOOL_BLOWROD) + act_result = is_left_clicking ? blowrod_act(user, tool) : blowrod_act_secondary(user, tool) + // DOPPLER EDIT ADDITION END if(!act_result) return NONE diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index 64f4ee35f6076..53fef07eb194a 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -666,6 +666,10 @@ /obj/machinery/vending/wardrobe/science_wardrobe = "SciDrobe", /obj/machinery/vending/wardrobe/sec_wardrobe = "SecDrobe", /obj/machinery/vending/wardrobe/viro_wardrobe = "ViroDrobe", + /obj/machinery/vending/imported/nt = "NT Sustenance Supplier", //DOPPLER ADDITION + /obj/machinery/vending/imported/yangyu = "Fudobenda", //DOPPLER ADDITION + /obj/machinery/vending/imported/mothic = "Nomad Fleet Ration Chit Exchange", //DOPPLER ADDITION + /obj/machinery/vending/imported/tiziran = "Tiziran Imported Delicacies", //DOPPLER ADDITION ) /obj/item/circuitboard/machine/vendor/screwdriver_act(mob/living/user, obj/item/tool) diff --git a/code/game/sound.dm b/code/game/sound.dm index 49c716d8e0c56..17e5f6e3e3209 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -205,6 +205,7 @@ /proc/get_sfx(soundin) if(!istext(soundin)) return soundin + soundin = get_sfx_doppler(soundin) // DOPPLER EDIT ADDITION - MODULAR SOUNDS switch(soundin) if(SFX_SHATTER) soundin = pick('sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg') diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 4af39c20d148a..6a47eb5a7f17b 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -34,6 +34,8 @@ var/datum/weakref/spawned_mob_ref /// DOPPLER SHIFT ADDITION: allowing players to have their current character loaded var/allow_prefs = TRUE + /// DOPPLER SHIFT ADDITION: allowing players to have their current loadout and clothes loaded + var/allow_loadout = TRUE /obj/effect/mob_spawn/Initialize(mapload) . = ..() @@ -100,14 +102,18 @@ if(allow_prefs && spawned_human.client) spawned_human.client?.prefs.safe_transfer_prefs_to(spawned_human) SSquirks.AssignQuirks(spawned_human, spawned_human.client) - spawned_human.equip_outfit_and_loadout(outfit, spawned_human.client?.prefs) + if(allow_loadout) + spawned_human.equip_outfit_and_loadout(outfit, spawned_human.client?.prefs) + else + spawned_human.equipOutfit(outfit) else spawned_human.equipOutfit(outfit) else if(allow_prefs && spawned_mob.client) var/mob/living/carbon/human/spawned_human = spawned_mob spawned_human.client?.prefs.safe_transfer_prefs_to(spawned_human) SSquirks.AssignQuirks(spawned_human, spawned_human.client) - spawned_human.equip_outfit_and_loadout(new /datum/outfit(), spawned_human.client?.prefs) + if(allow_loadout) + spawned_human.equip_outfit_and_loadout(new /datum/outfit(), spawned_human.client?.prefs) /// DOPPLER SHIFT ADDITION END ///these mob spawn subtypes do not trigger until attacked by a ghost. @@ -178,7 +184,10 @@ prompt += " (Warning, You can no longer be revived!)" /// DOPPLER SHIFT ADDITION BEGIN if(allow_prefs) - prompt += "\nYou will be loaded in with your current character, [realname] - loadout & quirks included! Make sure they fit the role!" + prompt += "\nYou will be loaded in with your current character, [realname] -" + if(allow_loadout) + prompt += " loadout &" + prompt += " quirks included! Make sure they fit the role!" /// DOPPLER SHIFT ADDITION END var/ghost_role = tgui_alert(usr, prompt, buttons = list("Yes", "No"), timeout = 10 SECONDS) if(ghost_role != "Yes" || !loc || QDELETED(user)) diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_human_felinid_primitive.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_human_felinid_primitive.png new file mode 100644 index 0000000000000..762355d1c894d Binary files /dev/null and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_human_felinid_primitive.png differ diff --git a/code/modules/unit_tests/turf_icons.dm b/code/modules/unit_tests/turf_icons.dm index 6f37117289880..d7e415a408ec2 100644 --- a/code/modules/unit_tests/turf_icons.dm +++ b/code/modules/unit_tests/turf_icons.dm @@ -1,6 +1,6 @@ /// Makes sure turf icons actually exist. :) /datum/unit_test/turf_icons - var/modular_mineral_turf_file //= 'icons/turf/mining.dmi' //MODULARITY SUPPORT - insert your snowflake MAP_SWITCH icon file here if you use that define. + var/modular_mineral_turf_file = 'modular_doppler/xenoarch/icons/mining.dmi' //MODULARITY SUPPORT - insert your snowflake MAP_SWITCH icon file here if you use that define. /datum/unit_test/turf_icons/Run() for(var/turf/turf_path as anything in (subtypesof(/turf) - typesof(/turf/closed/mineral))) diff --git a/config/config.txt b/config/config.txt index 6252c3bae659e..d7cd6f5a33df9 100644 --- a/config/config.txt +++ b/config/config.txt @@ -2,6 +2,7 @@ $include game_options.txt $include dbconfig.txt +$include doppler/config_doppler.txt $include comms.txt $include logging.txt $include resources.txt diff --git a/config/doppler/config_doppler.txt b/config/doppler/config_doppler.txt new file mode 100644 index 0000000000000..0da710789294f --- /dev/null +++ b/config/doppler/config_doppler.txt @@ -0,0 +1,8 @@ +## Combat indicator, comment out to disable it +COMBAT_INDICATOR + +## How long until someone can be put in cryo if they are SSD, default is 9000 (15 minutes) +CRYO_MIN_SSD_TIME 9000 + +## Primitive demihumans +ROUNDSTART_RACES primitive_felinid diff --git a/modular_doppler/advanced_reskin/code/advanced_reskin.dm b/modular_doppler/advanced_reskin/code/advanced_reskin.dm new file mode 100644 index 0000000000000..6b461839c85f6 --- /dev/null +++ b/modular_doppler/advanced_reskin/code/advanced_reskin.dm @@ -0,0 +1,68 @@ +/obj/item + /// Does this use the advanced reskinning setup? + var/uses_advanced_reskins = FALSE + +/obj/item/reskin_obj(mob/M) + if(!uses_advanced_reskins) + return ..() + if(!LAZYLEN(unique_reskin)) + return + + /// Is the obj a glasses icon with swappable item states? + var/is_swappable = FALSE + // /// if the item are glasses, this variable stores the item. + // var/obj/item/clothing/glasses/reskinned_glasses + + // if(istype(src, /obj/item/clothing/glasses)) // TODO - Remove this mess about glasses, it shouldn't be necessary anymore. + // reskinned_glasses = src + // if(reskinned_glasses.can_switch_eye) + // is_swappable = TRUE + + var/list/items = list() + + + for(var/reskin_option in unique_reskin) + var/image/item_image = image(icon = unique_reskin[reskin_option][RESKIN_ICON] ? unique_reskin[reskin_option][RESKIN_ICON] : icon, icon_state = "[unique_reskin[reskin_option][RESKIN_ICON_STATE]]") + items += list("[reskin_option]" = item_image) + sort_list(items) + + var/pick = show_radial_menu(M, src, items, custom_check = CALLBACK(src, PROC_REF(check_reskin_menu), M), radius = 38, require_near = TRUE) + if(!pick) + return + if(!unique_reskin[pick]) + return + current_skin = pick + + if(unique_reskin[pick][RESKIN_ICON]) + icon = unique_reskin[pick][RESKIN_ICON] + + if(unique_reskin[pick][RESKIN_ICON_STATE]) + if(is_swappable) + base_icon_state = unique_reskin[pick][RESKIN_ICON_STATE] + icon_state = base_icon_state + else + icon_state = unique_reskin[pick][RESKIN_ICON_STATE] + + if(unique_reskin[pick][RESKIN_WORN_ICON]) + worn_icon = unique_reskin[pick][RESKIN_WORN_ICON] + + if(unique_reskin[pick][RESKIN_WORN_ICON_STATE]) + worn_icon_state = unique_reskin[pick][RESKIN_WORN_ICON_STATE] + + if(unique_reskin[pick][RESKIN_INHAND_L]) + lefthand_file = unique_reskin[pick][RESKIN_INHAND_L] + if(unique_reskin[pick][RESKIN_INHAND_R]) + righthand_file = unique_reskin[pick][RESKIN_INHAND_R] + if(unique_reskin[pick][RESKIN_INHAND_STATE]) + inhand_icon_state = unique_reskin[pick][RESKIN_INHAND_STATE] + if(unique_reskin[pick][RESKIN_SUPPORTS_VARIATIONS_FLAGS]) + supports_variations_flags = unique_reskin[pick][RESKIN_SUPPORTS_VARIATIONS_FLAGS] + if(ishuman(M)) + var/mob/living/carbon/human/wearer = M + wearer.regenerate_icons() // update that mf + to_chat(M, "[src] is now skinned as '[pick].'") + post_reskin(M) + +/// Automatically called after a reskin, for any extra variable changes. +/obj/item/proc/post_reskin(mob/our_mob) + return diff --git a/modular_doppler/advanced_reskin/readme.md b/modular_doppler/advanced_reskin/readme.md new file mode 100644 index 0000000000000..8ac6d8dc3f3da --- /dev/null +++ b/modular_doppler/advanced_reskin/readme.md @@ -0,0 +1,25 @@ +## Title: Advanced Reskin + +MODULE ID: ADVANCED_RESKIN + +### Description: + +This module adds more customization to the reskin function, as well as serving as a place to add more to it. + +### TG Proc Changes: + +N/A + +### Defines: + +- `code\__DEFINES\~doppler_defines\reskin_defines.dm` module's defines + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: diff --git a/modular_doppler/clutter objects/icons/janitor.dmi b/modular_doppler/clutter objects/icons/janitor.dmi new file mode 100644 index 0000000000000..fb41f7464c829 Binary files /dev/null and b/modular_doppler/clutter objects/icons/janitor.dmi differ diff --git a/modular_doppler/cryosleep/code/admin.dm b/modular_doppler/cryosleep/code/admin.dm new file mode 100644 index 0000000000000..4b6ce389eee24 --- /dev/null +++ b/modular_doppler/cryosleep/code/admin.dm @@ -0,0 +1,27 @@ +/// Send player in not-quiet cryopod. If with_paper = TRUE, place a paper with notification under player. +/mob/proc/send_to_cryo(with_paper = FALSE) + //effect + playsound(loc, 'sound/magic/Repulse.ogg', 100, 1) + var/datum/effect_system/spark_spread/quantum/sparks = new + sparks.set_up(10, 1, loc) + sparks.attach(loc) + sparks.start() + + //make a paper if need + if(with_paper) + var/obj/item/paper/cryo_paper = new /obj/item/paper(loc) + cryo_paper.name = "Notification - [name]" + cryo_paper.add_raw_text("Our sincerest apologies, [name][job ? ", [job]," : ""] had to be sent back in Cryogenic Storage for reasons that cannot be elaborated on at the moment.

Sincerely,
Nanotrasen Anti-Sudden Sleep Disorder Agency") + cryo_paper.update_appearance() + //find cryopod + for(var/obj/machinery/cryopod/cryo in GLOB.valid_cryopods) + if(!cryo.occupant && cryo.state_open && !cryo.panel_open) //free, opened, and panel closed? + if(buckled) + buckled.unbuckle_mob(src, TRUE) + if(buckled_mobs) + for(var/mob/buckled_mob in buckled_mobs) + unbuckle_mob(buckled_mob) + cryo.close_machine(src) //put player + break + + diff --git a/modular_doppler/cryosleep/code/ai.dm b/modular_doppler/cryosleep/code/ai.dm new file mode 100644 index 0000000000000..5397311dc2f89 --- /dev/null +++ b/modular_doppler/cryosleep/code/ai.dm @@ -0,0 +1,20 @@ +/mob/living/silicon/ai/verb/ai_cryo() + set name = "AI Cryogenic Stasis" + set desc = "Puts the current AI personality into cryogenic stasis, freeing the space for another." + set category = "AI Commands" + + if(incapacitated()) + return + switch(alert("Would you like to enter cryo? This will ghost you. Remember to AHELP before cryoing out of important roles, even with no admins online.",,"Yes.","No.")) + if("Yes.") + src.ghostize(FALSE) + minor_announce("Station AI has disconnected from system networks and moved to remote storage. Preparing for new AI personality upload.", "Station AI") + new /obj/structure/ai_core/latejoin_inactive(loc) + if(src.mind) + //Handle job slot/tater cleanup. + if(src.mind.assigned_role.title == JOB_AI) + SSjob.FreeRole(JOB_AI) + src.mind.special_role = null + qdel(src) + else + return diff --git a/modular_doppler/cryosleep/code/config.dm b/modular_doppler/cryosleep/code/config.dm new file mode 100644 index 0000000000000..cd99eca3b9829 --- /dev/null +++ b/modular_doppler/cryosleep/code/config.dm @@ -0,0 +1,2 @@ +/datum/config_entry/number/cryo_min_ssd_time + config_entry_value = 9000 diff --git a/modular_doppler/cryosleep/code/cryo_console_return.dm b/modular_doppler/cryosleep/code/cryo_console_return.dm new file mode 100644 index 0000000000000..4da60b9ad1876 --- /dev/null +++ b/modular_doppler/cryosleep/code/cryo_console_return.dm @@ -0,0 +1,23 @@ +/// Returns any items inside of the `items_to_send` list to a cryo console on station. +/mob/living/carbon/human/proc/return_items_to_console(list/items_to_send) + var/list/held_contents = get_contents() + if(!held_contents || !items_to_send) + return FALSE + + var/obj/machinery/computer/cryopod/target_console + for(var/obj/machinery/computer/cryopod/cryo_console in GLOB.cryopod_computers) + target_console = cryo_console + var/turf/target_turf = get_turf(target_console) + if(is_station_level(target_turf.z)) //If we find a cryo console on station, send items to it first and foremost. + break + + if(!target_console) + return FALSE + + for(var/obj/item/found_item in held_contents) + if(!is_type_in_list(found_item, items_to_send)) + continue + transferItemToLoc(found_item, target_console, force = TRUE, silent = TRUE) + target_console.frozen_item += found_item + + return TRUE diff --git a/modular_doppler/cryosleep/code/cryopod.dm b/modular_doppler/cryosleep/code/cryopod.dm new file mode 100644 index 0000000000000..8c0f6d47169bb --- /dev/null +++ b/modular_doppler/cryosleep/code/cryopod.dm @@ -0,0 +1,533 @@ +#define AHELP_FIRST_MESSAGE "Please adminhelp before leaving the round, even if there are no administrators online!" + +/* + * Cryogenic refrigeration unit. Basically a despawner. + * Stealing a lot of concepts/code from sleepers due to massive laziness. + * The despawn tick will only fire if it's been more than time_till_despawned ticks + * since time_entered, which is world.time when the occupant moves in. + * ~ Zuhayr + */ +GLOBAL_LIST_EMPTY(cryopod_computers) + +GLOBAL_LIST_EMPTY(ghost_records) + +/// A list of all cryopods that aren't quiet, to be used by the "Send to Cryogenic Storage" VV action. +GLOBAL_LIST_EMPTY(valid_cryopods) + +//Main cryopod console. + +/obj/machinery/computer/cryopod + name = "cryogenic oversight console" + desc = "An interface between crew and the cryogenic storage oversight systems." + icon = 'modular_doppler/cryosleep/icons/cryogenics.dmi' + icon_state = "cellconsole_1" + icon_keyboard = null + icon_screen = null + use_power = FALSE + density = FALSE + interaction_flags_machine = INTERACT_MACHINE_OFFLINE + req_one_access = list(ACCESS_COMMAND, ACCESS_ARMORY) // Heads of staff or the warden can go here to claim recover items from their department that people went were cryodormed with. + verb_say = "coldly states" + verb_ask = "queries" + verb_exclaim = "alarms" + + /// Used for logging people entering cryosleep and important items they are carrying. + var/list/frozen_crew = list() + /// The items currently stored in the cryopod control panel. + var/list/frozen_item = list() + + /// This is what the announcement system uses to make announcements. Make sure to set a radio that has the channel you want to broadcast on. + var/obj/item/radio/headset/radio = /obj/item/radio/headset/silicon/ai + /// The channel to be broadcast on, valid values are the values of any of the "RADIO_CHANNEL_" defines. + var/announcement_channel = null // RADIO_CHANNEL_COMMON doesn't work here. + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/computer/cryopod, 32) + +/obj/machinery/computer/cryopod/Initialize(mapload) + . = ..() + GLOB.cryopod_computers += src + radio = new radio(src) + +/obj/machinery/computer/cryopod/Destroy() + GLOB.cryopod_computers -= src + QDEL_NULL(radio) + return ..() + +/obj/machinery/computer/cryopod/update_icon_state() + if(machine_stat & (NOPOWER|BROKEN)) + icon_state = "cellconsole" + return ..() + icon_state = "cellconsole_1" + return ..() + +/obj/machinery/computer/cryopod/ui_interact(mob/user, datum/tgui/ui) + . = ..() + if(machine_stat & (NOPOWER|BROKEN)) + return + + add_fingerprint(user) + + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CryopodConsole", name) + ui.open() + +/obj/machinery/computer/cryopod/ui_data(mob/user) + var/list/data = list() + data["frozen_crew"] = frozen_crew + + /// The list of references to the stored items. + var/list/item_ref_list = list() + /// The associative list of the reference to an item and its name. + var/list/item_ref_name = list() + + for(var/obj/item/item in frozen_item) + var/ref = REF(item) + item_ref_list += ref + item_ref_name[ref] = item.name + + data["item_ref_list"] = item_ref_list + data["item_ref_name"] = item_ref_name + + // Check Access for item dropping. + var/item_retrieval_allowed = allowed(user) + data["item_retrieval_allowed"] = item_retrieval_allowed + + var/obj/item/card/id/id_card + if(isliving(user)) + var/mob/living/person = user + id_card = person.get_idcard() + if(id_card?.registered_name) + data["account_name"] = id_card.registered_name + + return data + +/obj/machinery/computer/cryopod/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("item_get") + // This is using references, kinda clever, not gonna lie. Good work Zephyr + var/item_get = params["item_get"] + var/obj/item/item = locate(item_get) + if(item in frozen_item) + item.forceMove(drop_location()) + frozen_item.Remove(item_get, item) + visible_message("[src] dispenses \the [item].") + message_admins("[item] was retrieved from cryostorage at [ADMIN_COORDJMP(src)]") + else + CRASH("Invalid REF# for ui_act. Not inside internal list!") + return TRUE + + else + CRASH("Illegal action for ui_act: '[action]'") + +/obj/machinery/computer/cryopod/proc/announce(message_type, user, rank, occupant_departments_bitflags, occupant_job_radio) + switch(message_type) + if("CRYO_JOIN") + radio.talk_into(src, "[user][rank ? ", [rank]" : ""] has woken up from cryo storage.", announcement_channel) + if("CRYO_LEAVE") + if (occupant_job_radio) + if (occupant_departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) + if (occupant_job_radio != RADIO_CHANNEL_COMMAND) + radio.talk_into(src, "[user][rank ? ", [rank]" : ""] has been moved to cryo storage.", RADIO_CHANNEL_COMMAND) + radio.use_command = TRUE + radio.talk_into(src, "[user][rank ? ", [rank]" : ""] has been moved to cryo storage.", occupant_job_radio) + radio.use_command = FALSE + radio.talk_into(src, "[user][rank ? ", [rank]" : ""] has been moved to cryo storage.", announcement_channel) + +// Cryopods themselves. +/obj/machinery/cryopod + name = "cryogenic freezer" + desc = "Suited for Cyborgs and Humanoids, the pod is a safe place for personnel affected by the Space Sleep Disorder to get some rest." + icon = 'modular_doppler/cryosleep/icons/cryogenics.dmi' + icon_state = "cryopod-open" + base_icon_state = "cryopod" + use_power = FALSE + density = TRUE + anchored = TRUE + state_open = TRUE + interaction_flags_mouse_drop = NEED_DEXTERITY + + var/open_icon_state = "cryopod-open" + /// Whether the cryopod respects the minimum time someone has to be disconnected before they can be put into cryo by another player + var/allow_timer_override = FALSE + /// Minimum time for someone to be SSD before another player can cryo them. Customizable in "cryo_min_ssd_time" in config_doppler + var/ssd_time = 15 MINUTES + + /// Time until despawn when a mob enters a cryopod. You cannot other people in pods unless they're catatonic. + var/time_till_despawn = 30 SECONDS + /// Cooldown for when it's now safe to try an despawn the player. + COOLDOWN_DECLARE(despawn_world_time) + + ///Weakref to our controller + var/datum/weakref/control_computer_weakref + COOLDOWN_DECLARE(last_no_computer_message) + /// if false, plays announcement on cryo + var/quiet = FALSE + /// Has the occupant been tucked in? + var/tucked = FALSE + +/obj/machinery/cryopod/quiet + quiet = TRUE + +/obj/machinery/cryopod/Initialize(mapload) + ..() + ssd_time = CONFIG_GET(number/cryo_min_ssd_time) + if(!quiet) + GLOB.valid_cryopods += src + return INITIALIZE_HINT_LATELOAD //Gotta populate the cryopod computer GLOB first + +/obj/machinery/cryopod/post_machine_initialize() + . = ..() + update_icon() + find_control_computer() + +// This is not a good situation +/obj/machinery/cryopod/Destroy() + GLOB.valid_cryopods -= src + control_computer_weakref = null + return ..() + +/obj/machinery/cryopod/proc/find_control_computer(urgent = FALSE) + for(var/cryo_console as anything in GLOB.cryopod_computers) + var/obj/machinery/computer/cryopod/console = cryo_console + if(get_area(console) == get_area(src)) + control_computer_weakref = WEAKREF(console) + break + + // Don't send messages unless we *need* the computer, and less than five minutes have passed since last time we messaged + if(!control_computer_weakref && urgent && COOLDOWN_FINISHED(src, last_no_computer_message)) + COOLDOWN_START(src, last_no_computer_message, 5 MINUTES) + log_admin("Cryopod in [get_area(src)] could not find control computer!") + message_admins("Cryopod in [get_area(src)] could not find control computer!") + last_no_computer_message = world.time + + return control_computer_weakref != null + +/obj/machinery/cryopod/close_machine(atom/movable/target, density_to_set = TRUE) + if(!control_computer_weakref) + find_control_computer(TRUE) + if((isnull(target) || isliving(target)) && state_open && !panel_open) + ..(target) + var/mob/living/mob_occupant = occupant + if(mob_occupant && mob_occupant.stat != DEAD) + to_chat(occupant, span_notice("You feel cool air surround you. You go numb as your senses turn inward.")) + + COOLDOWN_START(src, despawn_world_time, time_till_despawn) + +/obj/machinery/cryopod/open_machine(drop = TRUE, density_to_set = FALSE) + ..() + set_density(TRUE) + name = initial(name) + tucked = FALSE + +/obj/machinery/cryopod/container_resist_act(mob/living/user) + visible_message(span_notice("[occupant] emerges from [src]!"), + span_notice("You climb out of [src]!")) + open_machine() + +/obj/machinery/cryopod/relaymove(mob/user) + container_resist_act(user) + +/obj/machinery/cryopod/process() + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + if(mob_occupant.stat == DEAD) + open_machine() + + if(!mob_occupant.client && COOLDOWN_FINISHED(src, despawn_world_time)) + if(!control_computer_weakref) + find_control_computer(urgent = TRUE) + + despawn_occupant() + +/obj/machinery/cryopod/proc/handle_objectives() + var/mob/living/mob_occupant = occupant + // Update any existing objectives involving this mob. + for(var/datum/objective/objective in GLOB.objectives) + // We don't want revs to get objectives that aren't for heads of staff. Letting + // them win or lose based on cryo is silly so we remove the objective. + if(istype(objective,/datum/objective/mutiny) && objective.target == mob_occupant.mind) + objective.team.objectives -= objective + qdel(objective) + for(var/datum/mind/mind in objective.team.members) + to_chat(mind.current, "
[span_userdanger("Your target is no longer within reach. Objective removed!")]") + mind.announce_objectives() + else if(istype(objective.target) && objective.target == mob_occupant.mind) + if(!istype(objective, /datum/objective/contract)) + return + else if(istype(objective.target) && objective.target == mob_occupant.mind) + var/old_target = objective.target + objective.target = null + if(!objective) + return + objective.find_target() + if(!objective.target && objective.owner) + to_chat(objective.owner.current, "
[span_userdanger("Your target is no longer within reach. Objective removed!")]") + for(var/datum/antagonist/antag in objective.owner.antag_datums) + antag.objectives -= objective + if (!objective.team) + objective.update_explanation_text() + objective.owner.announce_objectives() + to_chat(objective.owner.current, "
[span_userdanger("You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")]") + else + var/list/objectivestoupdate + for(var/datum/mind/objective_owner in objective.get_owners()) + to_chat(objective_owner.current, "
[span_userdanger("You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")]") + for(var/datum/objective/update_target_objective in objective_owner.get_all_objectives()) + LAZYADD(objectivestoupdate, update_target_objective) + objectivestoupdate += objective.team.objectives + for(var/datum/objective/update_objective in objectivestoupdate) + if(update_objective.target != old_target || !istype(update_objective,objective.type)) + continue + update_objective.target = objective.target + update_objective.update_explanation_text() + to_chat(objective.owner.current, "
[span_userdanger("You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")]") + update_objective.owner.announce_objectives() + qdel(objective) + +/// This function can not be undone; do not call this unless you are sure. +/// Handles despawning the player. +/obj/machinery/cryopod/proc/despawn_occupant() + var/mob/living/mob_occupant = occupant + + var/occupant_ckey = mob_occupant.ckey || mob_occupant.mind?.key + var/occupant_name = mob_occupant.name + var/occupant_rank = mob_occupant.mind?.assigned_role.title + var/occupant_departments_bitflags = mob_occupant.mind?.assigned_role.departments_bitflags + var/occupant_job_radio = mob_occupant.mind?.assigned_role.default_radio_channel + + SSjob.FreeRole(occupant_rank) + + // Handle holy successor removal + var/list/holy_successors = list_holy_successors() + if(mob_occupant in holy_successors) // if this mob was a holy successor then remove them from the pool + GLOB.holy_successors -= WEAKREF(mob_occupant) + + if(mob_occupant.mind) + // Handle tater cleanup. + if(LAZYLEN(mob_occupant.mind.objectives)) + mob_occupant.mind.objectives.Cut() + mob_occupant.mind.special_role = null + // Handle freeing the high priest role for the next chaplain in line + if(mob_occupant.mind.holy_role == HOLY_ROLE_HIGHPRIEST) + reset_religion() + else + // handle the case of the high priest no longer having a mind + var/datum/weakref/current_highpriest = GLOB.current_highpriest + if(current_highpriest?.resolve() == mob_occupant) + reset_religion() + + // Delete them from datacore and ghost records. + var/announce_rank = null + + for(var/list/record in GLOB.ghost_records) + if(record["name"] == occupant_name) + announce_rank = record["rank"] + GLOB.ghost_records.Remove(list(record)) + break + + if(!announce_rank) // No need to loop over all of those if we already found it beforehand. + for(var/datum/record/crew/possible_target_record as anything in GLOB.manifest.general) + if(possible_target_record.name == occupant_name && (occupant_rank == "N/A" || possible_target_record.trim == occupant_rank)) + announce_rank = possible_target_record.rank + qdel(possible_target_record) + break + + var/obj/machinery/computer/cryopod/control_computer = control_computer_weakref?.resolve() + if(!control_computer) + control_computer_weakref = null + else + control_computer.frozen_crew += list(list("name" = occupant_name, "job" = occupant_rank)) + + // Make an announcement and log the person entering storage. If set to quiet, does not make an announcement. + if(!quiet) + control_computer.announce("CRYO_LEAVE", mob_occupant.real_name, announce_rank, occupant_departments_bitflags, occupant_job_radio) + + visible_message(span_notice("[src] hums and hisses as it moves [mob_occupant.real_name] into storage.")) + + for(var/obj/item/item_content as anything in mob_occupant) + if(!istype(item_content) || HAS_TRAIT(item_content, TRAIT_NODROP)) + continue + if (issilicon(mob_occupant) && istype(item_content, /obj/item/mmi)) + continue + if(control_computer) + if(istype(item_content, /obj/item/modular_computer)) + var/obj/item/modular_computer/computer = item_content + for(var/datum/computer_file/program/messenger/message_app in computer.stored_files) + message_app.invisible = TRUE + mob_occupant.transferItemToLoc(item_content, control_computer, force = TRUE, silent = TRUE) + item_content.dropped(mob_occupant) + control_computer.frozen_item += item_content + else + mob_occupant.transferItemToLoc(item_content, drop_location(), force = TRUE, silent = TRUE) + + GLOB.joined_player_list -= occupant_ckey + + handle_objectives() + mob_occupant.ghostize() + QDEL_NULL(occupant) + open_machine() + name = initial(name) + +/obj/machinery/cryopod/mouse_drop_receive(mob/living/target, mob/user, params) + if(!istype(target) || !ismob(target) || isanimal(target) || !istype(user.loc, /turf) || target.buckled) + return + + if(occupant) + to_chat(user, span_notice("[src] is already occupied!")) + return + + if(target.stat == DEAD) + to_chat(user, span_notice("Dead people can not be put into cryo.")) + return + +// Allows admins to enable players to override SSD Time check. + if(allow_timer_override) + if(tgui_alert(user, "Would you like to place [target] into [src]?", "Place into Cryopod?", list("Yes", "No")) != "No") + to_chat(user, span_danger("You put [target] into [src]. [target.p_Theyre()] in the cryopod.")) + log_admin("[key_name(user)] has put [key_name(target)] into a overridden stasis pod.") + message_admins("[key_name(user)] has put [key_name(target)] into a overridden stasis pod. [ADMIN_JMP(src)]") + + add_fingerprint(target) + + close_machine(target) + name = "[name] ([target.name])" + +// Allows players to cryo others. Checks if they have been AFK for 30 minutes. + if(target.key && user != target) + if (target.get_organ_by_type(/obj/item/organ/internal/brain) ) //Target the Brain + if(!target.mind || target.ssd_indicator ) // Is the character empty / AI Controlled + if(target.lastclienttime + ssd_time >= world.time) + to_chat(user, span_notice("You can't put [target] into [src] for another [round(((ssd_time - (world.time - target.lastclienttime)) / (1 MINUTES)), 1)] minutes.")) + log_admin("[key_name(user)] has attempted to put [key_name(target)] into a stasis pod, but they were only disconnected for [round(((world.time - target.lastclienttime) / (1 MINUTES)), 1)] minutes.") + message_admins("[key_name(user)] has attempted to put [key_name(target)] into a stasis pod. [ADMIN_JMP(src)]") + return + else if(tgui_alert(user, "Would you like to place [target] into [src]?", "Place into Cryopod?", list("Yes", "No")) == "Yes") + if(target.mind.assigned_role.req_admin_notify) + tgui_alert(user, "They are an important role! [AHELP_FIRST_MESSAGE]") + to_chat(user, span_danger("You put [target] into [src]. [target.p_Theyre()] in the cryopod.")) + log_admin("[key_name(user)] has put [key_name(target)] into a stasis pod.") + message_admins("[key_name(user)] has put [key_name(target)] into a stasis pod. [ADMIN_JMP(src)]") + + add_fingerprint(target) + + close_machine(target) + name = "[name] ([target.name])" + + else if(iscyborg(target)) + to_chat(user, span_danger("You can't put [target] into [src]. [target.p_Theyre()] online.")) + else + to_chat(user, span_danger("You can't put [target] into [src]. [target.p_Theyre()] conscious.")) + return + + if(target == user && (tgui_alert(target, "Would you like to enter cryosleep?", "Enter Cryopod?", list("Yes", "No")) != "Yes")) + return + + if(target == user) + if(target.mind.assigned_role.req_admin_notify) + tgui_alert(target, "You're an important role! [AHELP_FIRST_MESSAGE]") + var/datum/antagonist/antag = target.mind.has_antag_datum(/datum/antagonist) + if(antag) + tgui_alert(target, "You're \a [antag.name]! [AHELP_FIRST_MESSAGE]") + + if(LAZYLEN(target.buckled_mobs) > 0) + if(target == user) + to_chat(user, span_danger("You can't fit into the cryopod while someone is buckled to you.")) + else + to_chat(user, span_danger("You can't fit [target] into the cryopod while someone is buckled to them.")) + return + + if(!istype(target) || !can_interact(user) || !target.Adjacent(user) || !ismob(target) || isanimal(target) || !istype(user.loc, /turf) || target.buckled) + return + // rerun the checks in case of shenanigans + + if(occupant) + to_chat(user, span_notice("[src] is already occupied!")) + return + + if(target == user) + visible_message(span_infoplain("[user] starts climbing into the cryo pod.")) + else + visible_message(span_infoplain("[user] starts putting [target] into the cryo pod.")) + + to_chat(target, span_warning("If you ghost, log out or close your client now, your character will shortly be permanently removed from the round.")) + + log_admin("[key_name(target)] entered a stasis pod.") + message_admins("[key_name_admin(target)] entered a stasis pod. [ADMIN_JMP(src)]") + add_fingerprint(target) + + close_machine(target) + name = "[name] ([target.name])" + +// Attacks/effects. +/obj/machinery/cryopod/blob_act() + return // Sorta gamey, but we don't really want these to be destroyed. + +/obj/machinery/cryopod/attackby(obj/item/weapon, mob/living/carbon/human/user, params) + . = ..() + if(istype(weapon, /obj/item/bedsheet)) + if(!occupant || !istype(occupant, /mob/living)) + return + if(tucked) + to_chat(user, span_warning("[occupant.name] already looks pretty comfortable!")) + return + to_chat(user, span_notice("You tuck [occupant.name] into their pod!")) + qdel(weapon) + user.add_mood_event("tucked", /datum/mood_event/tucked_in, occupant) + tucked = TRUE + +/obj/machinery/cryopod/update_icon_state() + icon_state = state_open ? open_icon_state : base_icon_state + return ..() + +/// Special wall mounted cryopod for the prison, making it easier to autospawn. +/obj/machinery/cryopod/prison + icon_state = "prisonpod" + base_icon_state = "prisonpod" + open_icon_state = "prisonpod" + density = FALSE + +MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/cryopod/prison, 18) + +/obj/machinery/cryopod/prison/set_density(new_value) + // Simple way to make it always non-dense. + return ..(FALSE) + +/obj/machinery/cryopod/prison/close_machine(atom/movable/target, density_to_set = TRUE) + . = ..() + // Flick the pod for a second when user enters + flick("prisonpod-open", src) + +// Wake-up notifications + +/obj/effect/mob_spawn/ghost_role + /// For figuring out where the local cryopod computer is. Must be set for cryo computer announcements. + var/area/computer_area + +/obj/effect/mob_spawn/ghost_role/create(mob/mob_possessor, newname) + var/mob/living/spawned_mob = ..() + var/obj/machinery/computer/cryopod/control_computer = find_control_computer() + + GLOB.ghost_records.Add(list(list("name" = spawned_mob.real_name, "rank" = name))) + if(control_computer) + control_computer.announce("CRYO_JOIN", spawned_mob.real_name, name) + + return spawned_mob + +/obj/effect/mob_spawn/ghost_role/proc/find_control_computer() + if(!computer_area) + return + for(var/cryo_console as anything in GLOB.cryopod_computers) + var/obj/machinery/computer/cryopod/console = cryo_console + var/area/area = get_area(cryo_console) // Define moment + if(area.type == computer_area) + return console + + return + +#undef AHELP_FIRST_MESSAGE diff --git a/modular_doppler/cryosleep/code/job.dm b/modular_doppler/cryosleep/code/job.dm new file mode 100644 index 0000000000000..fcd218d473918 --- /dev/null +++ b/modular_doppler/cryosleep/code/job.dm @@ -0,0 +1,19 @@ +/datum/controller/subsystem/job/proc/FreeRole(rank) + if(!rank) + return + JobDebug("Freeing role: [rank]") + var/datum/job/job = GetJob(rank) + if(!job) + return FALSE + job.current_positions = max(0, job.current_positions - 1) + +/// Used for clocking back in, re-claiming the previously freed role. Returns false if no slot is available. +/datum/controller/subsystem/job/proc/OccupyRole(rank) + if(!rank) + return FALSE + JobDebug("Occupying role: [rank]") + var/datum/job/job = GetJob(rank) + if(!job || job.current_positions >= job.total_positions) + return FALSE + job.current_positions = job.current_positions + 1 + return TRUE diff --git a/modular_doppler/cryosleep/code/jobs.dm b/modular_doppler/cryosleep/code/jobs.dm new file mode 100644 index 0000000000000..0d225d934f3ca --- /dev/null +++ b/modular_doppler/cryosleep/code/jobs.dm @@ -0,0 +1,20 @@ +/datum/job + var/default_radio_channel = null +/datum/job/chief_medical_officer + default_radio_channel = RADIO_CHANNEL_MEDICAL +/datum/job/chief_engineer + default_radio_channel = RADIO_CHANNEL_ENGINEERING +/datum/job/head_of_security + default_radio_channel = RADIO_CHANNEL_SECURITY +/datum/job/head_of_personnel + default_radio_channel = RADIO_CHANNEL_SERVICE +/datum/job/research_director + default_radio_channel = RADIO_CHANNEL_SCIENCE +/datum/job/quartermaster + default_radio_channel = RADIO_CHANNEL_SUPPLY +/datum/job/captain + default_radio_channel = RADIO_CHANNEL_COMMAND +/datum/job/blueshield + default_radio_channel = RADIO_CHANNEL_COMMAND +/datum/job/nanotrasen_consultant + default_radio_channel = RADIO_CHANNEL_COMMAND diff --git a/modular_doppler/cryosleep/code/mind.dm b/modular_doppler/cryosleep/code/mind.dm new file mode 100644 index 0000000000000..c71983ae4845d --- /dev/null +++ b/modular_doppler/cryosleep/code/mind.dm @@ -0,0 +1,2 @@ +/datum/mind + var/list/datum/objective/objectives = list() diff --git a/modular_doppler/cryosleep/code/mood.dm b/modular_doppler/cryosleep/code/mood.dm new file mode 100644 index 0000000000000..a64cfaf19c5d3 --- /dev/null +++ b/modular_doppler/cryosleep/code/mood.dm @@ -0,0 +1,9 @@ +/datum/mood_event/tucked_in + description = "I feel better having tucked someone in for a good night's rest!" + mood_change = 3 + timeout = 2 MINUTES + +/datum/mood_event/tucked_in/add_effects(mob/tuckee) + if(!tuckee) + return + description = "I feel better having tucked in [tuckee.name] for a good night's rest!" diff --git a/modular_doppler/cryosleep/code/objective.dm b/modular_doppler/cryosleep/code/objective.dm new file mode 100644 index 0000000000000..b279c7e21bd8e --- /dev/null +++ b/modular_doppler/cryosleep/code/objective.dm @@ -0,0 +1,9 @@ +//Redefining objective New and Destroy to avoid editing TG original files. + +/datum/objective/New(text) + GLOB.objectives += src + ..() + +/datum/objective/Destroy() + GLOB.objectives -= src + return ..() diff --git a/modular_doppler/cryosleep/icons/cryogenics.dmi b/modular_doppler/cryosleep/icons/cryogenics.dmi new file mode 100644 index 0000000000000..9b7ff616aafd7 Binary files /dev/null and b/modular_doppler/cryosleep/icons/cryogenics.dmi differ diff --git a/modular_doppler/cryosleep/readme.md b/modular_doppler/cryosleep/readme.md new file mode 100644 index 0000000000000..6a8dde917b021 --- /dev/null +++ b/modular_doppler/cryosleep/readme.md @@ -0,0 +1,25 @@ +## Title: Cryosleep + +MODULE ID: CRYOSLEEP + +### Description: + +Adds in a green sleeper-like cryosleep machine, to allow people who wish to stop playing the game to exit with an IC way. + +The time of how long until someone can be put in cryo if they are SSD `CRYO_MIN_SSD_TIME` was added in the configuration file `config\doppler\config_doppler.txt` + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Included files: + +N/A + +### Credits: + +Azarak - Porting/refactoring diff --git a/modular_doppler/emotes/sound/female_sniff.ogg b/modular_doppler/emotes/sound/female_sniff.ogg new file mode 100644 index 0000000000000..6f4ce34b0b894 Binary files /dev/null and b/modular_doppler/emotes/sound/female_sniff.ogg differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/clothing.dm b/modular_doppler/hearthkin/primitive_catgirls/code/clothing.dm new file mode 100644 index 0000000000000..15e6e8a61451f --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/clothing.dm @@ -0,0 +1,253 @@ +// The naming of every path in this file is going to be awful :smiling_imp: + +// Outfit Datum + +/datum/outfit/primitive_catgirl + name = "Icemoon Dweller" + + uniform = /obj/item/clothing/under/dress/skirt/primitive_catgirl_body_wraps + shoes = /obj/item/clothing/shoes/winterboots/ice_boots/primitive_catgirl_boots + gloves = /obj/item/clothing/gloves/fingerless/primitive_catgirl_armwraps + suit = /obj/item/clothing/suit/jacket/primitive_catgirl_coat + neck = /obj/item/clothing/neck/scarf/primitive_catgirl_scarf + + back = /obj/item/forging/reagent_weapon/axe/fake_copper + +// Under + +/obj/item/clothing/under/dress/skirt/primitive_catgirl_body_wraps + name = "body wraps" + desc = "Some pretty simple wraps to cover up your lower bits." + icon_state = "wraps" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + body_parts_covered = GROIN + greyscale_config = /datum/greyscale_config/primitive_catgirl_wraps + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_wraps/worn + greyscale_colors = "#cec8bf#364660" + flags_1 = IS_PLAYER_COLORABLE_1 + has_sensor = FALSE + +/obj/item/clothing/under/dress/skirt/primitive_catgirl_tailored_dress + name = "tailored dress" + desc = "A handmade dress, tailored to fit perfectly to its wearer's body measurements." + icon_state = "tailored_dress" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + body_parts_covered = GROIN|CHEST + greyscale_config = /datum/greyscale_config/primitive_catgirl_tailored_dress + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_tailored_dress/worn + greyscale_colors = "#cec8bf#364660" + flags_1 = IS_PLAYER_COLORABLE_1 + has_sensor = FALSE + +/obj/item/clothing/under/dress/skirt/primitive_catgirl_tunic + name = "handmade tunic" + desc = "A simple garment that reaches from the shoulders to above the knee. This one has a belt to secure it." + icon_state = "tunic" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + body_parts_covered = GROIN|CHEST + greyscale_config = /datum/greyscale_config/primitive_catgirl_tunic + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_tunic/worn + greyscale_colors = "#cec8bf#faece4#594032" + flags_1 = IS_PLAYER_COLORABLE_1 + has_sensor = FALSE + +/obj/item/clothing/under/dress/skirt/loincloth + name = "loincloth" + desc = "A simple elegant cloth, to use wrapped around your waist and groin." + icon_state = "loincloth" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi' + greyscale_config = /datum/greyscale_config/loincloth + greyscale_config_worn = /datum/greyscale_config/loincloth/worn + greyscale_colors = "#413069" + flags_1 = IS_PLAYER_COLORABLE_1 + body_parts_covered = GROIN|LEGS + has_sensor = NO_SENSORS + supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON + +/obj/item/clothing/under/dress/skirt/loincloth/loincloth_alt + name = "shorter loincloth" + desc = "A simple elegant cloth, to use wrapped around your waist and groin. This one uses a shorter cloth." + icon_state = "loincloth_alt" + greyscale_config = /datum/greyscale_config/loincloth_alt + greyscale_config_worn = /datum/greyscale_config/loincloth_alt/worn + +// Hands + +/obj/item/clothing/gloves/fingerless/primitive_catgirl_armwraps + name = "arm wraps" + desc = "Simple cloth to wrap around one's arms." + icon_state = "armwraps" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + greyscale_config = /datum/greyscale_config/primitive_catgirl_armwraps + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_armwraps/worn + greyscale_colors = "#cec8bf" + flags_1 = IS_PLAYER_COLORABLE_1 + +/obj/item/clothing/gloves/fingerless/primitive_catgirl_gauntlets + name = "gauntlets" + desc = "Simple cloth arm wraps with overlying metal protection." + icon_state = "gauntlets" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + greyscale_config = /datum/greyscale_config/primitive_catgirl_gauntlets + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_gauntlets/worn + greyscale_config_inhand_left = null + greyscale_config_inhand_right = null + greyscale_colors = "#cec8bf#c55a1d" + flags_1 = IS_PLAYER_COLORABLE_1 + +// Suit + +/obj/item/clothing/suit/jacket/primitive_catgirl_coat + name = "primitive fur coat" + desc = "A large piece of animal hide stuffed with fur, likely from the same animal." + icon_state = "coat" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + body_parts_covered = CHEST + cold_protection = CHEST + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON + greyscale_config = /datum/greyscale_config/primitive_catgirl_coat + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_coat/worn + greyscale_colors = "#594032#cec8bf" + flags_1 = IS_PLAYER_COLORABLE_1 + +/obj/item/clothing/suit/apron/chef/colorable_apron/primitive_catgirl_leather + greyscale_colors = "#594032" + +// Shoes + +/obj/item/clothing/shoes/winterboots/ice_boots/primitive_catgirl_boots + name = "primitive hiking boots" + desc = "A pair of heavy boots lined with fur and with soles special built to prevent slipping on ice." + icon_state = "boots" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + greyscale_config = /datum/greyscale_config/primitive_catgirl_boots + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_boots/worn + greyscale_colors = "#594032#cec8bf" + flags_1 = IS_PLAYER_COLORABLE_1 + +// Neck + +/obj/item/clothing/neck/scarf/primitive_catgirl_scarf + greyscale_colors = "#cec8bf#cec8bf" + +/obj/item/clothing/neck/large_scarf/primitive_catgirl_off_white + greyscale_colors = "#cec8bf#cec8bf" + +/obj/item/clothing/neck/infinity_scarf/primitive_catgirl_blue + greyscale_colors = "#364660" + +/obj/item/clothing/neck/mantle/recolorable/primitive_catgirl_off_white + greyscale_colors = "#cec8bf" + +/obj/item/clothing/neck/ranger_poncho/primitive_catgirl_leather + greyscale_colors = "#594032#594032" + +// Masks + +/obj/item/clothing/mask/neck_gaiter/primitive_catgirl_gaiter + greyscale_colors = "#364660" + +// Head + +/obj/item/clothing/head/standalone_hood/primitive_catgirl_colors + greyscale_colors = "#594032#364660" + +/obj/item/clothing/head/primitive_catgirl_ferroniere + name = "Ferroniere" + desc = "A style of headband that encircles the wearer's forehead, with a small jewel suspended in the centre." + icon_state = "ferroniere" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + greyscale_config = /datum/greyscale_config/primitive_catgirl_ferroniere + greyscale_config_worn = /datum/greyscale_config/primitive_catgirl_ferroniere/worn + greyscale_colors = "#f1f6ff#364660" + w_class = WEIGHT_CLASS_TINY + flags_1 = IS_PLAYER_COLORABLE_1 + +// Misc Items + +/obj/item/forging/reagent_weapon/axe/fake_copper + custom_materials = list(/datum/material/copporcitite = SHEET_MATERIAL_AMOUNT) + +/obj/item/clothing/suit/armor/forging_plate_armor/hearthkin + name = "handcrafted hearthkin armor" + desc = "An armor obviously crafted by the expertise of a hearthkin. It has leather shoulder pads and a chain mail underneath." + icon_state = "chained_leather_armor" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + body_parts_covered = GROIN|CHEST + +/datum/crafting_recipe/handcrafted_hearthkin_armor + name = "Handcrafted Hearthkin Armor" + category = CAT_CLOTHING + + //recipe given to icecats as part of their spawner/team setting + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED + + reqs = list( + /obj/item/forging/complete/chain = 4, + /obj/item/stack/sheet/leather = 2, + ) + + result = /obj/item/clothing/suit/armor/forging_plate_armor/hearthkin + +//Pelts +/obj/item/clothing/head/pelt + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/pelt.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/pelt_worn.dmi' + name = "bear pelt" + desc = "A luxurious bear pelt, good to keep warm in winter. Or to sleep through it." + icon_state = "bearpelt_brown" + inhand_icon_state = "cowboy_hat_brown" + cold_protection = CHEST|HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + +/obj/item/clothing/head/pelt/black + icon_state = "bearpelt_black" + inhand_icon_state = "cowboy_hat_black" + +/obj/item/clothing/head/pelt/white + icon_state = "bearpelt_white" + inhand_icon_state = "cowboy_hat_white" + +/obj/item/clothing/head/pelt/tiger + name = "shiny tiger pelt" + desc = "A vibrant tiger pelt, particularly fabulous." + icon_state = "tigerpelt_shiny" + inhand_icon_state = "cowboy_hat_grey" + +/obj/item/clothing/head/pelt/snow_tiger + name = "snow tiger pelt" + desc = "A pelt of a less vibrant tiger, but rather warm." + icon_state = "tigerpelt_snow" + inhand_icon_state = "cowboy_hat_white" + +/obj/item/clothing/head/pelt/pink_tiger + name = "pink tiger pelt" + desc = "A particularly vibrant tiger pelt, for those who want to be the most fabulous at parties." + icon_state = "tigerpelt_pink" + inhand_icon_state = "cowboy_hat_red" + +/obj/item/clothing/head/pelt/wolf + name = "wolf pelt" + desc = "A fuzzy wolf pelt that demands respect as a hunter... assuming it wasn't just purchased, that is, for all the glory but none of the credit." + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/pelt_big.dmi' + icon_state = "wolfpelt_brown" + +/obj/item/clothing/head/pelt/wolf/black + icon_state = "wolfpelt_gray" + inhand_icon_state = "cowboy_hat_grey" + +/obj/item/clothing/head/pelt/wolf/white + icon_state = "wolfpelt_white" + inhand_icon_state = "cowboy_hat_white" +//End Pelts diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/clothing_vendor.dm b/modular_doppler/hearthkin/primitive_catgirls/code/clothing_vendor.dm new file mode 100644 index 0000000000000..04fe22a194a53 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/clothing_vendor.dm @@ -0,0 +1,54 @@ +/obj/machinery/vending/primitive_catgirl_clothing_vendor + name = "wardrobe" + desc = "It's a big wardrobe filled up with all sorts of clothing." + icon = 'icons/obj/storage/closet.dmi' + icon_state = "cabinet" + + use_power = FALSE + + shut_up = TRUE + vend_reply = null + + products = list( + /obj/item/clothing/under/dress/skirt/primitive_catgirl_body_wraps = 15, + /obj/item/clothing/under/dress/skirt/primitive_catgirl_tailored_dress = 15, + /obj/item/clothing/under/dress/skirt/primitive_catgirl_tunic = 15, + /obj/item/clothing/under/dress/skirt/loincloth = 5, + /obj/item/clothing/under/dress/skirt/loincloth/loincloth_alt = 5, + /obj/item/clothing/suit/jacket/primitive_catgirl_coat = 15, + /obj/item/clothing/gloves/fingerless/primitive_catgirl_armwraps = 15, + /obj/item/clothing/shoes/winterboots/ice_boots/primitive_catgirl_boots = 15, + /obj/item/clothing/gloves/fingerless/primitive_catgirl_gauntlets = 10, + // /obj/item/clothing/mask/neck_gaiter/primitive_catgirl_gaiter = 10, + // /obj/item/clothing/suit/apron/chef/colorable_apron/primitive_catgirl_leather = 10, + // /obj/item/clothing/head/standalone_hood/primitive_catgirl_colors = 10, + /obj/item/clothing/neck/scarf/primitive_catgirl_scarf = 5, + // /obj/item/clothing/neck/face_scarf = 5, + /obj/item/clothing/neck/large_scarf/primitive_catgirl_off_white = 5, + /obj/item/clothing/neck/infinity_scarf/primitive_catgirl_blue = 5, + // /obj/item/clothing/neck/mantle/recolorable/primitive_catgirl_off_white = 5, + // /obj/item/clothing/neck/ranger_poncho/primitive_catgirl_leather = 5, + // /obj/item/clothing/neck/wide_cape = 5, + // /obj/item/clothing/neck/robe_cape = 5, + // /obj/item/clothing/neck/long_cape = 5, + // /obj/item/clothing/glasses/eyepatch/wrap = 5, + /obj/item/clothing/head/primitive_catgirl_ferroniere = 5, + /obj/item/clothing/head/pelt/snow_tiger = 5, + /obj/item/clothing/head/pelt = 5, + /obj/item/clothing/head/pelt/black = 5, + /obj/item/clothing/head/pelt/white = 5, + /obj/item/clothing/head/pelt/wolf = 5, + /obj/item/clothing/head/pelt/wolf/black = 5, + /obj/item/clothing/head/pelt/wolf/white = 5, + // /obj/item/clothing/head/costume/nova/papakha = 5, + // /obj/item/clothing/head/costume/nova/papakha/white = 5, + // /obj/item/clothing/head/hair_tie = 5, + ) + +/obj/machinery/vending/primitive_catgirl_clothing_vendor/Initialize(mapload) + . = ..() + + onstation = FALSE + +/obj/machinery/vending/primitive_catgirl_clothing_vendor/speak(message) + return diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/greyscale_config.dm b/modular_doppler/hearthkin/primitive_catgirls/code/greyscale_config.dm new file mode 100644 index 0000000000000..31620b2368ee4 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/greyscale_config.dm @@ -0,0 +1,89 @@ +/datum/greyscale_config/primitive_catgirl_wraps + name = "Primitive Body Wraps" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps.json' + +/datum/greyscale_config/primitive_catgirl_wraps/worn + name = "Primitive Body Wraps (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps_worn.json' + +/datum/greyscale_config/primitive_catgirl_armwraps + name = "Arm Wraps" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps.json' + +/datum/greyscale_config/primitive_catgirl_armwraps/worn + name = "Arm Wraps (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps_worn.json' + +/datum/greyscale_config/primitive_catgirl_coat + name = "Primitive Fur Coat" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat.json' + +/datum/greyscale_config/primitive_catgirl_coat/worn + name = "Primitive Fur Coat (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat_worn.json' + +/datum/greyscale_config/primitive_catgirl_boots + name = "Primitive Winter Boots" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots.json' + +/datum/greyscale_config/primitive_catgirl_boots/worn + name = "Primitive Winter Boots (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots_worn.json' + +/datum/greyscale_config/primitive_catgirl_gauntlets + name = "Gauntlets" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets.json' + +/datum/greyscale_config/primitive_catgirl_gauntlets/worn + name = "Gauntlets (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets_worn.json' + +/datum/greyscale_config/primitive_catgirl_tailored_dress + name = "Tailored Dress" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress.json' + +/datum/greyscale_config/primitive_catgirl_tailored_dress/worn + name = "Tailored Dress (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress_worn.json' + +/datum/greyscale_config/primitive_catgirl_ferroniere + name = "Ferroniere" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere.json' + +/datum/greyscale_config/primitive_catgirl_ferroniere/worn + name = "Ferroniere (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere_worn.json' + +/datum/greyscale_config/primitive_catgirl_tunic + name = "Handmade Tunic" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic.json' + +/datum/greyscale_config/primitive_catgirl_tunic/worn + name = "Handmade Tunic (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic_worn.json' + +/datum/greyscale_config/loincloth + name = "Loincloth" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth.json' + +/datum/greyscale_config/loincloth/worn + name = "Loincloth (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_worn.json' + +/datum/greyscale_config/loincloth_alt + name = "Shorter Loincloth" + icon_file = 'modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi' + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt.json' + +/datum/greyscale_config/loincloth_alt/worn + name = "Shorter Loincloth (Worn)" + json_config = 'modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt_worn.json' diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/language.dm b/modular_doppler/hearthkin/primitive_catgirls/code/language.dm new file mode 100644 index 0000000000000..8a97197d279fc --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/language.dm @@ -0,0 +1,18 @@ +/datum/language/primitive_catgirl + name = "Ættmál" + desc = "A liturgical language passed through three centuries of Hearthkin culture, the only tongue which their literature is allowed to be spoken in; \ + especially relating to their pagan practices. While Galactic Uncommon is used as a trade language with outsiders, Ættmál remains sacred and mostly unknown \ + to those outside the Hearth." + key = "H" + flags = TONGUELESS_SPEECH + space_chance = 70 + syllables = list ( + "al", "an", "ar", "að", "eg", "en", "er", "ha", "he", "il", "in", "ir", "ið", "ki", "le", "na", "nd", "ng", "nn", "og", "ra", "ri", + "se", "st", "ta", "ur", "ði", "va", "ve", "sem", "sta", "til", "tur", "var", "ver", "við", "ður", "það", "þei", "með", "ega", "ann", + "tur", "egr", "eda", "eva", "ada", "the", "tre", "tai", "thor", "thur", "ohd", "din", "gim", "per", "ger", "héð", "bur", "kóp", "vog", + "bar", "dar", "akur", "jer", "bær", "múl", "fjörð", "jah", "dah", "dim", "din", "dir", "dur", "nya", "miau", "mjau", "ný", "kt", "hø", + ) + icon_state = "omgkittyhiii" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/language_icon.dmi' + default_priority = 94 + // secret = TRUE //this needs a dedicated module for language diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/map_items.dm b/modular_doppler/hearthkin/primitive_catgirls/code/map_items.dm new file mode 100644 index 0000000000000..c1b383741fe9c --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/map_items.dm @@ -0,0 +1,100 @@ +// Bonfires but with a grill pre-attached + +/obj/structure/bonfire/grill_pre_attached + +/obj/structure/bonfire/grill_pre_attached/Initialize(mapload) + . = ..() + + grill = TRUE + add_overlay("bonfire_grill") + +// Dirt but icebox and also farmable + +/turf/open/misc/dirt/icemoon + baseturfs = /turf/open/openspace/icemoon + initial_gas_mix = "ICEMOON_ATMOS" + +/turf/open/misc/dirt/icemoon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/simple_farm, set_plant = TRUE) + +// Water that can be fished out of + +/turf/open/water/hot_spring + desc = "Water kept warm through some unknown heat source, possibly a geothermal heat source far underground. \ + Whatever it is, it feels pretty damn nice to swim in given the rest of the environment around here, and you \ + can even catch a glimpse of the odd fish darting through the water." + baseturfs = /turf/open/openspace/icemoon + initial_gas_mix = "ICEMOON_ATMOS" + /// Holder for the steam particles that show up sometimes + var/obj/effect/abstract/particle_holder/particle_effect + +/turf/open/water/hot_spring/Initialize(mapload) + . = ..() + AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/icecat_hot_spring) + if(prob(60)) + particle_effect = new(src, /particles/hotspring_steam) + +/turf/open/water/hot_spring/Destroy() + QDEL_NULL(particle_effect) + return ..() + +/turf/open/water/hot_spring/Entered(atom/movable/arrived) + ..() + wash_atom(arrived) + wash_atom(loc) + +/// Cleans the given atom of whatever dirties it +/turf/open/water/hot_spring/proc/wash_atom(atom/nasty) + nasty.wash(CLEAN_WASH) + +/turf/open/water/hot_spring/Entered(atom/movable/arrived) + ..() + if(istype(arrived, /mob/living)) + hotspring_mood(arrived) + +/// Applies the hot water mood buff on the passed mob +/turf/open/water/hot_spring/proc/hotspring_mood(mob/living/swimmer) + swimmer.add_mood_event("hotspring", /datum/mood_event/hotspring/nerfed) + +/datum/mood_event/hotspring/nerfed + description = span_nicegreen("The water was enjoyably warm!\n") + mood_change = 2 + +// Steam particles for pairing with the hotsprings above + +/particles/hotspring_steam + icon = 'icons/effects/particles/smoke.dmi' + icon_state = list( + "steam_1" = 2, + "steam_2" = 2, + "steam_3" = 1, + ) + width = 64 + height = 64 + count = 5 + spawning = 0.2 + lifespan = 1 SECONDS + fade = 0.5 SECONDS + color = "#ffffff" + position = generator(GEN_BOX, list(-32,-32,0), list(32,32,0), NORMAL_RAND) + scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND) + drift = generator(GEN_VECTOR, list(-0.1,0), list(0.1,0.025), UNIFORM_RAND) + spin = generator(GEN_NUM, list(-15,15), NORMAL_RAND) + +// Fishing source for the above water turfs + +/datum/fish_source/icecat_hot_spring + fish_table = list( + /obj/item/fish/dwarf_moonfish = 5, + /obj/item/fish/needlefish = 10, + /obj/item/fish/armorfish = 10, + /obj/item/fish/chasm_crab/ice = 5, + /obj/item/stack/sheet/bone = 5, + ) + catalog_description = "Hot Springs" + +// The area + +/area/ruin/unpowered/primitive_catgirl_den + name = "\improper Icewalker Camp" diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/objects.dm b/modular_doppler/hearthkin/primitive_catgirls/code/objects.dm new file mode 100644 index 0000000000000..f1c7b430b780d --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/objects.dm @@ -0,0 +1,111 @@ +/obj/item/anointing_oil + name = "anointing bloodresin" + desc = "And so Helgar Knife-Arm spoke to the Hearth, and decreed that all of the Kin who gave name to beasts would do so with conquest and blood." + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi' + icon_state = "anointingbloodresin" + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + + var/being_used = FALSE + +/obj/item/anointing_oil/attack(mob/living/target_mob, mob/living/user, params) + if (!is_species(user, /datum/species/human/felinid/primitive)) + to_chat(user, span_warning("You have no idea what this disgusting concoction is used for.")) + return + if(being_used || !ismob(target_mob)) //originally this was going to check if the mob was friendly, but if an icecat wants to name some terror mob while it's tearing chunks out of them, why not? + return + if(target_mob.ckey) + to_chat(user, span_warning("You would never shame a creature so intelligent by not allowing it to choose its own name.")) + return + + if(try_anoint(target_mob, user)) + qdel(src) + else + being_used = FALSE + +/obj/item/anointing_oil/proc/try_anoint(mob/living/target_mob, mob/living/user) + being_used = TRUE + + var/new_name = sanitize_name(tgui_input_text(user, "Speak forth this beast's new name for all the Kin to hear.", "Input a name", target_mob.name, MAX_NAME_LEN)) + + if(!new_name || QDELETED(src) || QDELETED(target_mob) || new_name == target_mob.name || !target_mob.Adjacent(user)) + being_used = FALSE + return FALSE + + target_mob.visible_message(span_notice("[user] leans down and smears twinned streaks of glistening bloodresin upon [target_mob], then straightens up with ritual purpose...")) + user.say("Let the ice know you forevermore as +[new_name]+.") + + user.log_message("used [src] on [target_mob], renaming it to [new_name].", LOG_GAME) + + target_mob.name = new_name + + //give the stupid dog zoomies from getting named + if(istype(target_mob, /mob/living/basic/mining/wolf)) + target_mob.emote("awoo") + target_mob.emote("spin") + + return TRUE + +/obj/item/anointing_oil/examine(mob/user) + . = ..() + if(is_species(user, /datum/species/human/felinid/primitive)) + . += span_info("Using this on the local wildlife will allow you to give them a name.") + +/datum/crafting_recipe/anointing_oil + name = "Anointing Bloodresin" + category = CAT_MISC + //recipe given to icecats as part of their spawner/team setting + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED + + reqs = list( + /datum/reagent/consumable/liquidgibs = 20, + /datum/reagent/blood = 20, + ) + + result = /obj/item/anointing_oil + +// Hearthkin exclusive object to make their special lungs. +/obj/item/frozen_breath + name = "Frozen Breath" + desc = "A strange brew, it smells minty and is extremely cold to the touch. It is rumored that a cold-hearted witch managed to make this, to mend the breath of her kindred." + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi' + icon_state = "frozenbreath" + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + +/obj/item/frozen_breath/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if (!is_species(user, /datum/species/human/felinid/primitive)) + to_chat(user, span_warning("You have no idea how to use this freezing concoction.")) + return + + if(istype(interacting_with, /obj/item/organ/internal/lungs)) + var/obj/item/organ/internal/lungs/target_lungs = interacting_with + if(IS_ROBOTIC_ORGAN(target_lungs)) + user.balloon_alert(user, "The lungs have to be organic!") + return + var/location = get_turf(target_lungs) + playsound(location, 'sound/effects/slosh.ogg', 25, TRUE) + user.visible_message(span_notice("[user] pours a strange blue liquid over the set of lungs. The flesh starts glistening in a strange cyan light, transforming before your very eyes!"), + span_notice("Recalling the instructions for the lung transfiguration ritual, you pour the liquid over the flesh of the organ. Soon, the lungs glow in a mute cyan light, before they turn dim and change form before your very eyes!")) + var/obj/item/organ/internal/lungs/icebox_adapted/new_lungs = new(location) + new_lungs.damage = target_lungs.damage + qdel(target_lungs) + qdel(src) + +/obj/item/frozen_breath/examine(mob/user) + . = ..() + if(is_species(user, /datum/species/human/felinid/primitive)) + . += span_info("Using this on a pair of organic lungs transforms them into hardy lungs. This will remove any other special features from the old lungs, if there were any.") + +/datum/crafting_recipe/frozen_breath + name = "Frozen Breath" + category = CAT_MISC + //recipe given to icecats as part of their spawner/team setting + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED + + reqs = list( + /datum/reagent/consumable/frostoil = 50, + /datum/reagent/medicine/c2/synthflesh = 50, + ) + + result = /obj/item/frozen_breath diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/organs.dm b/modular_doppler/hearthkin/primitive_catgirls/code/organs.dm new file mode 100644 index 0000000000000..acc10d2153c63 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/organs.dm @@ -0,0 +1,71 @@ +#define GAS_TOLERANCE 5 + +// Lungs + +/obj/item/organ/internal/lungs/icebox_adapted + name = "hardy lungs" + desc = "Lungs adapted to frozen environments that would be otherwise inhospitable to most races. Feels cold." + icon_state = "hardylungs" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/organs.dmi' + +/obj/item/organ/internal/lungs/icebox_adapted/Initialize(mapload) + . = ..() + + var/datum/gas_mixture/immutable/planetary/mix = SSair.planetary[ICEMOON_DEFAULT_ATMOS] + + if(!mix?.total_moles()) // If there is no icemoon atmos then we have problems, return now + return + + // Take a "breath" of the air + var/datum/gas_mixture/breath = mix.remove(mix.total_moles() * BREATH_PERCENTAGE) + + var/list/breath_gases = breath.gases + + breath.assert_gases( + /datum/gas/oxygen, + /datum/gas/plasma, + /datum/gas/carbon_dioxide, + /datum/gas/nitrogen, + /datum/gas/bz, + /datum/gas/miasma, + ) + + var/oxygen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES]) + var/nitrogen_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) + var/plasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) + var/carbon_dioxide_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) + var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) + var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) + + safe_oxygen_min = max(0, oxygen_pp - GAS_TOLERANCE) + safe_nitro_min = max(0, nitrogen_pp - GAS_TOLERANCE) + safe_plasma_min = max(0, plasma_pp - GAS_TOLERANCE) + + // Increase plasma tolerance based on amount in base air + safe_plasma_max += plasma_pp + + // CO2 is always a waste gas, so none is required, but ashwalkers + // tolerate the base amount plus tolerance*2 (humans tolerate only 10 pp) + + safe_co2_max = carbon_dioxide_pp + GAS_TOLERANCE * 2 + + // The lung tolerance against BZ is also increased the amount of BZ in the base air + BZ_trip_balls_min += bz_pp + BZ_brain_damage_min += bz_pp + + // Lungs adapted to a high miasma atmosphere do not process it, and breathe it back out + if(miasma_pp) + suffers_miasma = FALSE + +// Eyes + +/obj/item/organ/internal/eyes/low_light_adapted + color_cutoffs = list(30, 15, 15) + + +// Tongue +/obj/item/organ/internal/tongue/cat/primitive + liked_foodtypes = SEAFOOD | MEAT | GORE + + +#undef GAS_TOLERANCE diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/pet_commands.dm b/modular_doppler/hearthkin/primitive_catgirls/code/pet_commands.dm new file mode 100644 index 0000000000000..35f0c0573b43e --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/pet_commands.dm @@ -0,0 +1,29 @@ +/datum/component/obeys_commands/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examine_more)) + +/datum/component/obeys_commands/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE) + +/datum/component/obeys_commands/on_examine(mob/living/source, mob/user, list/examine_list) + . = ..() + examine_list += span_italics("You can alt+click [source.p_them()] when adjacent to see available commands.") + examine_list += span_italics("You can also examine [source.p_them()] closely to check on [source.p_their()] wounds. Many companions can be healed with sutures or creams!") + +/datum/component/obeys_commands/proc/on_examine_more(mob/living/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if (IS_DEAD_OR_INCAP(source)) + return + if (!(user in source.ai_controller?.blackboard[BB_FRIENDS_LIST])) + return + + if (source.health < source.maxHealth*0.2) + examine_list += span_bolddanger("[source.p_They()] look[source.p_s()] severely injured.") + else if (source.health < source.maxHealth*0.5) + examine_list += span_danger("[source.p_They()] look[source.p_s()] moderately injured.") + else if (source.health < source.maxHealth*0.8) + examine_list += span_warning("[source.p_They()] look[source.p_s()] slightly injured.") + else + examine_list += span_notice("[source.p_They()] look[source.p_s()] to be in good condition.") diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/smelling_salts.dm b/modular_doppler/hearthkin/primitive_catgirls/code/smelling_salts.dm new file mode 100644 index 0000000000000..a91337b514a28 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/smelling_salts.dm @@ -0,0 +1,69 @@ +/obj/item/smelling_salts + name = "smelling salts" + desc = "A small pile of a salt-like substance that smells absolutely repulsive. Rumor has it that the smell is so pungent that even the dead will come back to life to escape it." + icon_state = "smelling_salts" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/salts.dmi' + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + item_flags = NOBLUDGEON + +/obj/item/smelling_salts/attack(mob/living/mob_attacked, mob/user) + . = ..() + if(!iscarbon(mob_attacked)) + to_chat(user, span_warning("On second thought, maybe [src] won't work on [mob_attacked].")) + return + + if(mob_attacked == user) + to_chat(user, span_warning("You can't bring yourself to get [src] anywhere near your face.")) + return + + if(mob_attacked.stat != DEAD) + to_chat(user, span_warning("On second thought, maybe you shouldn't use this on [mob_attacked] if they're not dead.")) + return + + try_revive(mob_attacked, user) + +/// If the right conditions are present (basically could this person be defibrilated), revives the target +/obj/item/smelling_salts/proc/try_revive(mob/living/carbon/carbon_target, mob/user) + carbon_target.notify_revival("You are being brought back to life!") + carbon_target.grab_ghost() + + user.balloon_alert_to_viewers("trying to revive [carbon_target]") + + if(!do_after(user, 3 SECONDS, carbon_target)) + user.balloon_alert(user, "stopped reviving [carbon_target]") + return + + if(carbon_target.stat != DEAD) + to_chat(user, span_warning("Wait, [carbon_target] isn't actually dead!")) + return + + var/defib_result = carbon_target.can_defib() + var/fail_reason + + switch (defib_result) + if (DEFIB_FAIL_SUICIDE, DEFIB_FAIL_BLACKLISTED, DEFIB_FAIL_NO_INTELLIGENCE) + fail_reason = "[carbon_target] doesn't respond at all... You don't think they're coming back." + if (DEFIB_FAIL_NO_HEART, DEFIB_FAIL_FAILING_HEART, DEFIB_FAIL_FAILING_BRAIN) + fail_reason = "[carbon_target] seems to respond just a little, but something you can't see must be wrong about them..." + if (DEFIB_FAIL_TISSUE_DAMAGE, DEFIB_FAIL_HUSK) + fail_reason = "[carbon_target]'s body seems way too damaged for this to work..." + if (DEFIB_FAIL_NO_BRAIN) + fail_reason = "[carbon_target]'s head looks like it's missing something important." + + if(carbon_target.health <= HEALTH_THRESHOLD_FULLCRIT) + fail_reason = "[carbon_target]'s body seems just a little too damaged for this to work..." + + if(fail_reason) + to_chat(user, span_boldwarning("[fail_reason]")) + return + + carbon_target.adjustOxyLoss(amount = 60, updating_health = TRUE) + playsound(src, 'modular_doppler/emotes/sound/female_sniff.ogg', 50, FALSE) + carbon_target.set_heartattack(FALSE) + + if(defib_result == DEFIB_POSSIBLE) + carbon_target.grab_ghost() + + carbon_target.revive() + log_combat(user, carbon_target, "revived", src) diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/spawner.dm b/modular_doppler/hearthkin/primitive_catgirls/code/spawner.dm new file mode 100644 index 0000000000000..d10dfa45615c9 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/spawner.dm @@ -0,0 +1,303 @@ +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl + name = "hole in the ground" + desc = "A clearly hand dug hole in the ground that appears to lead into a small cave of some kind? It's pretty dark in there." + prompt_name = "icemoon dweller" + icon = 'icons/mob/simple/lavaland/nest.dmi' + icon_state = "hole" + mob_species = /datum/species/human/felinid/primitive + outfit = /datum/outfit/primitive_catgirl + density = FALSE + you_are_text = "You are an icemoon dweller." + flavour_text = "For as long as you can remember, the icemoon has been your home. \ + It's been the home of your ancestors, and their ancestors, and the ones before them. \ + Currently, you and your kin live in uneasy tension with your nearby human-and-otherwise \ + neighbors. Keep your village and your Kin safe, but bringing death on their heads from \ + being reckless with the outsiders will not have the Gods be so kind." + spawner_job_path = /datum/job/primitive_catgirl + interaction_flags_mouse_drop = NEED_DEXTERITY + allow_loadout = FALSE + + /// The team the spawner will assign players to and use to keep track of people that have already used the spawner + var/datum/team/primitive_catgirls/team + + restricted_species = list(/datum/species/human/felinid/primitive) + infinite_use = TRUE + deletes_on_zero_uses_left = FALSE + + /// The list of real names of those that have gone back into the hole. + /// Should get modified automatically by `create()` and `put_back_in()`. + var/list/went_back_to_sleep = list() + /// The cached string to display for additional info on who joined and who left. + /// Nulled every time someone joins or leaves to ensure it gets re-generated. + var/join_and_leave_log_cache = null + /// The minimum time someone needs to be SSD before they can be put back in. Shares config "cryo_min_ssd_time" with cryopod + var/ssd_time = 15 MINUTES +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/Initialize(mapload) + . = ..() + team = new /datum/team/primitive_catgirls() + ssd_time = CONFIG_GET(number/cryo_min_ssd_time) + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/Destroy() + team = null + return ..() + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/examine(mob/user) + . = ..() + + if(isprimitivedemihuman(user) || isobserver(user)) + . += span_notice("You could examine it more thoroughly...") + + return . + + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/examine_more(mob/user) + . = ..() + + if(!isprimitivedemihuman(user) && !isobserver(user)) + return + + . += get_joined_and_left_log() + + +/** + * Returns the `join_and_leave_log_cache` string if it already exists, otherwise + * generates and returns it. + */ +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/proc/get_joined_and_left_log() + if(join_and_leave_log_cache) + return join_and_leave_log_cache + + var/list/joined_player_names = list() + + for(var/datum/mind/joined_mind in team.members) + joined_player_names += joined_mind.name + + if(!length(joined_player_names) && !length(went_back_to_sleep)) + join_and_leave_log_cache = span_notice("Everyone still seems to be sleeping peacefully in the hole.") + return join_and_leave_log_cache + + var/nobody_joined = !length(joined_player_names) + var/nobody_returned = !length(went_back_to_sleep) + var/should_add_newline = !nobody_returned && !nobody_joined // if we have both missing kin and kin who went back to sleep, add a newline + + join_and_leave_log_cache = span_notice( \ + "[nobody_joined ? "" : "You smell that the following kin are missing from the hole:\n\ + [joined_player_names.Join(", ")]"]\ + [should_add_newline ? "\n\n" : ""]\ + [nobody_returned ? "" : "You catch the scent of the following kin having recently went back to sleep:\n\ + [went_back_to_sleep.Join(", ")]"]" \ + ) + + return join_and_leave_log_cache + + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/allow_spawn(mob/user, silent = FALSE) + if(!(user.key in team.players_spawned)) // One spawn per person + return TRUE + if(!silent) + to_chat(user, span_warning("It'd be weird if there were multiple of you in that cave, wouldn't it?")) + return FALSE + + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/create(mob/mob_possessor, newname) + . = ..() + + // We remove their name from there if they come back. + went_back_to_sleep -= newname + join_and_leave_log_cache = null + + +// This stuff is put on equip because it turns out /special sometimes just don't get called because Nova +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/equip(mob/living/carbon/human/spawned_human) + . = ..() + + spawned_human.mind.add_antag_datum(/datum/antagonist/primitive_catgirl, team) + + team.players_spawned += (spawned_human.key) + + +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/mouse_drop_receive(mob/living/carbon/human/target, mob/user, params) + if(!istype(target)) + return + if(!isprimitivedemihuman(target) || target.buckled) + return + + if(target.stat == DEAD) + to_chat(user, span_danger("Dead kin cannot be put back to sleep.")) + return + + if(target.key && target != user) + if(!target.get_organ_by_type(/obj/item/organ/internal/brain) || (target.mind && !target.ssd_indicator)) + to_chat(user, span_danger("Awake kin cannot be put back to sleep against their will.")) + return + + if(target.lastclienttime + ssd_time >= world.time) + to_chat(user, span_userdanger("You can't put [target] into [src] for another [round(((ssd_time - (world.time - target.lastclienttime)) / (1 MINUTES)), 1)] minutes.")) + log_admin("[key_name(user)] has attempted to put [key_name(target)] back into [src], but they were only disconnected for [round(((world.time - target.lastclienttime) / (1 MINUTES)), 1)] minutes.") + message_admins("[key_name(user)] has attempted to put [key_name(target)] back into [src]. [ADMIN_JMP(src)]") + return + + else if(tgui_alert(user, "Would you like to place [target] into [src]?", "Put back to sleep?", list("Yes", "No")) == "Yes") + + visible_message(span_infoplain("[user] starts putting [target] into [src]...")) + + if(!do_after(user, 3 SECONDS, target)) + balloon_alert(user, "cancelled transfer!") + return + + to_chat(user, span_danger("You put [target] into [src].")) + log_admin("[key_name(user)] has put [key_name(target)] back into [src].") + message_admins("[key_name(user)] has put [key_name(target)] back into [src]. [ADMIN_JMP(src)]") + + if(target == user) + if(tgui_alert(target, "Would you like to go back to sleep?", "Go back to sleep?", list("Yes", "No")) != "Yes") + return + + visible_message(span_infoplain("[user] starts climbing down into [src]...")) + + if(!do_after(user, 3 SECONDS, target)) + balloon_alert(user, "cancelled transfer!") + return + + if(LAZYLEN(target.buckled_mobs) > 0) + if(target == user) + to_chat(user, span_danger("You can't fit into [src] while someone is buckled to you.")) + else + to_chat(user, span_danger("You can't fit [target] into [src] while someone is buckled to them.")) + + return + + // Just in case something happened in-between, to make sure it doesn't do unexpected behaviors. + if(!isprimitivedemihuman(target) || !can_interact(user) || !target.Adjacent(user) || target.buckled || target.stat == DEAD) + return + + if(target == user) + visible_message(span_infoplain("[user] climbs down into [src].")) + else + visible_message(span_infoplain("[user] puts [target] into [src].")) + + log_admin("[key_name(target)] returned to [src].") + message_admins("[key_name_admin(target)] returned to [src]. [ADMIN_JMP(src)]") + add_fingerprint(target) + put_back_in(target) + + +/** + * Puts the target back into the spawner, effectively qdel'ing them after + * stripping them of all their items, and finishes by adding back a use to the + * spawner. + */ +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/proc/put_back_in(mob/living/carbon/human/target) + if(!istype(target)) + return + + // We don't want to constantly drop stuff that they spawn with. + var/static/list/item_drop_blacklist + if(!item_drop_blacklist) + item_drop_blacklist = generate_item_drop_blacklist() + + for(var/obj/item/item in target) + if(item_drop_blacklist[item.type] || (item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP)) + continue + + target.dropItemToGround(item, FALSE) + + // We make sure people can come back in again, if they needed to fix prefs + // or whatever. + team.players_spawned -= (target.key) + team.remove_member(target.mind) + went_back_to_sleep += target.real_name + join_and_leave_log_cache = null + + for(var/list/record in GLOB.ghost_records) + if(record["name"] == target.real_name) + GLOB.ghost_records.Remove(list(record)) + break + + // Just so the target's ghost ends up above the hole. + target.forceMove(loc) + target.ghostize(FALSE) + + qdel(target) + + +/** + * Simple helper to generate the item drop blacklist based on the spawner's + * outfit, only taking the used slots into account. + */ +/obj/effect/mob_spawn/ghost_role/human/primitive_catgirl/proc/generate_item_drop_blacklist() + PROTECTED_PROC(TRUE) + + var/list/blacklist = list() + + blacklist[initial(outfit.uniform)] = TRUE + blacklist[initial(outfit.shoes)] = TRUE + blacklist[initial(outfit.gloves)] = TRUE + blacklist[initial(outfit.suit)] = TRUE + blacklist[initial(outfit.neck)] = TRUE + blacklist[initial(outfit.back)] = TRUE + + return blacklist + + +/datum/job/primitive_catgirl + title = "Icemoon Dweller" + +// Antag and team datums + +/datum/team/primitive_catgirls + name = "Icewalkers" + member_name = "Icewalker" + show_roundend_report = FALSE + +/datum/team/primitive_catgirls/roundend_report() + var/list/report = list() + + report += span_header("An Ice Walker Tribe inhabited the wastes...
") + if(length(members)) + report += "The [member_name]s were:" + report += printplayerlist(members) + else + report += "But none of its members woke up!" + + return "
[report.Join("
")]
" + +// Antagonist datum + +/datum/antagonist/primitive_catgirl + name = "\improper Icewalker" + job_rank = ROLE_LAVALAND // If you're ashwalker banned you should also not be playing this, other way around as well + show_in_antagpanel = FALSE + show_to_ghosts = TRUE + prevent_roundtype_conversion = FALSE + antagpanel_category = "Icemoon Dwellers" + count_against_dynamic_roll_chance = FALSE + show_in_roundend = FALSE + + /// Tracks the antag datum's 'team' for showing in the ghost orbit menu + var/datum/team/primitive_catgirls/feline_team + + antag_recipes = list( + /datum/crafting_recipe/boneaxe, + /datum/crafting_recipe/bonespear, + /datum/crafting_recipe/bonedagger, + /datum/crafting_recipe/anointing_oil, + /datum/crafting_recipe/handcrafted_hearthkin_armor, + /datum/crafting_recipe/black_pelt_bed, + /datum/crafting_recipe/white_pelt_bed, + /datum/crafting_recipe/frozen_breath, + ) + +/datum/antagonist/primitive_catgirl/Destroy() + feline_team = null + return ..() + +/datum/antagonist/primitive_catgirl/create_team(datum/team/team) + if(team) + feline_team = team + objectives |= feline_team.objectives + else + feline_team = new + +/datum/antagonist/primitive_catgirl/get_team() + return feline_team diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/special_metals.dm b/modular_doppler/hearthkin/primitive_catgirls/code/special_metals.dm new file mode 100644 index 0000000000000..ae969d27ca8df --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/special_metals.dm @@ -0,0 +1,117 @@ +// Completely made up materials to be sold in bar form by jarnsmiour in cargo, *should* be unobtainable otherwise + +// Darkish blue kinda material + +/datum/material/cobolterium + name = "cobolterium" + desc = "Cobolterium" + color = list(0.2,0.5,0.7,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) + greyscale_colors = "#264d61" + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) + sheet_type = /obj/item/stack/sheet/cobolterium + +/datum/material/cobolterium/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) + victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5) + return TRUE + +/obj/item/stack/sheet/cobolterium + name = "cobolterium bars" + desc = "Cobalt-blue metal that might actually just be cobalt." + singular_name = "cobolterium bar" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi' + icon_state = "precious-metals" + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + mats_per_unit = list(/datum/material/cobolterium = SHEET_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/sheet/cobolterium + material_type = /datum/material/cobolterium + material_modifier = 1 + +/obj/item/stack/sheet/cobolterium/three + amount = 3 + +// More copper colored material + +/datum/material/copporcitite + name = "copporcitite" + desc = "Copporcitite" + color = list(0.8,0.35,0.1,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) + greyscale_colors = "#c55a1d" + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) + sheet_type = /obj/item/stack/sheet/copporcitite + +/datum/material/copporcitite/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) + victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5) + return TRUE + +/obj/item/stack/sheet/copporcitite + name = "copporcitite bars" + desc = "Copper colored metal that might actually just be copper." + singular_name = "copporcitite bar" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi' + icon_state = "precious-metals" + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + mats_per_unit = list(/datum/material/copporcitite = SHEET_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/sheet/copporcitite + material_type = /datum/material/copporcitite + material_modifier = 1 + +/obj/item/stack/sheet/copporcitite/three + amount = 3 + +// Super blued-silver color + +/datum/material/tinumium + name = "tinumium" + desc = "Tinumium" + color = list(0.45,0.5,0.6,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) + greyscale_colors = "#717e97" + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) + sheet_type = /obj/item/stack/sheet/tinumium + +/datum/material/tinumium/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) + victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5) + return TRUE + +/obj/item/stack/sheet/tinumium + name = "tinumium bars" + desc = "Heavily blued, silver colored metal." + singular_name = "tinumium bar" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi' + icon_state = "precious-metals" + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + mats_per_unit = list(/datum/material/tinumium = SHEET_MATERIAL_AMOUNT ) + merge_type = /obj/item/stack/sheet/tinumium + material_type = /datum/material/tinumium + material_modifier = 1 + +/obj/item/stack/sheet/tinumium/three + amount = 3 + +// Brassy yellow color + +/datum/material/brussite + name = "brussite" + desc = "Brussite" + color = list(0.9,0.75,0.4,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) + greyscale_colors = "#E1C16E" + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE) + sheet_type = /obj/item/stack/sheet/brussite + +/datum/material/brussite/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) + victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5) + return TRUE + +/obj/item/stack/sheet/brussite + name = "brussite bars" + desc = "Brassy-yellow metal that might actually just be brass." + singular_name = "brussite bar" + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi' + icon_state = "precious-metals" + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + mats_per_unit = list(/datum/material/brussite = SHEET_MATERIAL_AMOUNT ) + merge_type = /obj/item/stack/sheet/brussite + material_type = /datum/material/brussite + material_modifier = 1 + +/obj/item/stack/sheet/brussite/three + amount = 3 diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/species.dm b/modular_doppler/hearthkin/primitive_catgirls/code/species.dm new file mode 100644 index 0000000000000..577d5adb6cc47 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/species.dm @@ -0,0 +1,128 @@ +/mob/living/carbon/human/species/felinid/primitive + race = /datum/species/human/felinid/primitive + +/datum/language_holder/primitive_felinid + understood_languages = list( + /datum/language/primitive_catgirl = list(LANGUAGE_ATOM), + /datum/language/uncommon = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/primitive_catgirl = list(LANGUAGE_ATOM), + /datum/language/uncommon = list(LANGUAGE_ATOM), + ) + selected_language = /datum/language/primitive_catgirl + +/datum/species/human/felinid/primitive + name = "Primitive Demihuman" + id = SPECIES_FELINE_PRIMITIVE + + mutantlungs = /obj/item/organ/internal/lungs/icebox_adapted + mutanteyes = /obj/item/organ/internal/eyes/low_light_adapted + mutanttongue = /obj/item/organ/internal/tongue/cat/primitive + + species_language_holder = /datum/language_holder/primitive_felinid + // language_prefs_whitelist = list(/datum/language/primitive_catgirl) //this needs a dedicated module for language + // always_customizable = TRUE //this needs a dedicated module for species customization + + bodytemp_normal = 270 // If a normal human gets hugged by one it's gonna feel cold + bodytemp_heat_damage_limit = 283 // To them normal station atmos would be sweltering + bodytemp_cold_damage_limit = 213 // Man up bro it's not even that cold out here + + inherent_traits = list( + TRAIT_CATLIKE_GRACE, + TRAIT_VIRUSIMMUNE, + TRAIT_RESISTCOLD, + TRAIT_USES_SKINTONES, + ) + +/datum/species/human/felinid/primitive/on_species_gain(mob/living/carbon/new_primitive, datum/species/old_species, pref_load) + . = ..() + var/mob/living/carbon/human/hearthkin = new_primitive + if(!istype(hearthkin)) + return + hearthkin.dna.add_mutation(/datum/mutation/human/olfaction, MUT_NORMAL) + hearthkin.dna.activate_mutation(/datum/mutation/human/olfaction) + + // >mfw I take mutadone and my nose clogs + var/datum/mutation/human/olfaction/mutation = locate() in hearthkin.dna.mutations + mutation.mutadone_proof = TRUE + mutation.instability = 0 + +/datum/species/human/felinid/primitive/on_species_loss(mob/living/carbon/former_primitive, datum/species/new_species, pref_load) + . = ..() + var/mob/living/carbon/human/hearthkin = former_primitive + if(!istype(hearthkin)) + return + hearthkin.dna.remove_mutation(/datum/mutation/human/olfaction) + +/datum/species/human/felinid/primitive/prepare_human_for_preview(mob/living/carbon/human/human_for_preview) + human_for_preview.set_haircolor("#323442", update = FALSE) + human_for_preview.set_hairstyle("Blunt Bangs Alt", update = TRUE) + human_for_preview.skin_tone = "mediterranean" + + var/obj/item/organ/internal/ears/cat/cat_ears = human_for_preview.get_organ_by_type(/obj/item/organ/internal/ears/cat) + var/obj/item/organ/external/tail/cat/cat_tail = human_for_preview.get_organ_by_type(/obj/item/organ/external/tail/cat) + if (cat_ears) + cat_ears.color = human_for_preview.hair_color + if (cat_tail) + cat_tail.color = human_for_preview.hair_color + if (cat_ears || cat_tail) + human_for_preview.update_body() + + human_for_preview.update_body(is_creating = TRUE) + +/datum/species/human/felinid/primitive/get_species_description() + return list( + "Genetically modified humanoids believed to be descendants of a now centuries old colony \ + ship from the pre-bluespace travel era. Still having at least some human traits, they \ + are most comparable to today's felinids with most sporting features likely spliced from \ + the icemoon's many fauna." + ) + +/datum/species/human/felinid/primitive/get_species_lore() + return list( + "The Hearthkin are a culture of disparate Scandinavian groups all sharing a common origin \ + as descendents from demihuman genemodders aboard the good ship Stjarndrakkr, or Star Dragon; \ + an enormous colony ship almost 40km tall. This ship first reached the orbit of its last \ + resting place three hundred years ago, before the advent of bluespace travel; coming from \ + a world known to the Hearthkin as 'Asgard.' When it reached the atmosphere of the ice moon, \ + or 'Niflheim' as they consider it, the vessel detonated in low orbit for unknown reasons. \ + Large sections of the Star Dragon broke up and sealed themselves, \ + coming to a rest all over the moon itself.", + + "At first, life was incredibly difficult for the would-be colonists. Generations were very short, \ + and most of the personnel able to even fix the vessel had died either on impact, or later on. \ + While their genetic modifications and pre-existing comfort in frozen climates somewhat helped them, \ + the Ancestors were said to have made one last desperate move to put all their resources together to \ + fully modify and adapt themselves to the climes of Niflheim; forever.", + + "Nowadays, the Hearthkin are removed from the original culture of the Ancestors, building one all of their own. \ + Many of the original, largest segments of the Star Dragon are buried under ice and snow, and the Hearthkin have \ + created a culture of building separate dwellings to keep them secret. Dwelling in longhouses and sleeping in the \ + warm undergrounds of Niflheim, and hunting native creatures and those coming from portals to the moon's planet; \ + Muspelheim. Their pagan faith has strengthened over the centuries, from occasional prayers for a blizzard to end \ + soon, to now full-on worship and sacrifices to their various Gods. Hearthkin still hold immense reverence for their \ + Ancestors, but tend to have varying opinions and speculation on what exactly they were like, and why they came to \ + Niflheim in the first place.", + + "Their names are two-part; a birth name, and a title. Their birth names still hold resemblance to 'Asgardian' culture, \ + typically a Nordic name such as 'Solveig Helgasdottir,' or 'Bjorn Lukasson.' However, their last name is then exchanged \ + for a 'Title' when the Hearthkin is no longer 'Unproven.' These are a two-parter, based on either great deeds, \ + embarrassing moments, or aspects of the person's personality. Some examples would be 'Soul-Drowner' after the night of \ + a Hearthkin drinking herself half-dead, or one might be known as 'Glacier-Shaped' for being abnormally large. \ + These titles are always given by ones' kin.", + + "The Hearth itself is an area that the kindred hold incredibly sacred, primarily hating Outsiders for more \ + practical reasons. They think themselves as having been there first, many of them knowing they were 'promised' \ + Niflheim by the Ancestors. Unlike the Ashwalkers of Muspelheim, the Hearthkin are a more technologically \ + advanced society; having use for not only metal, but gold and silver for accessory. They are known to employ \ + artifacts thought to be of either the planet their moon orbits, or leftovers from their Ancestors; however, for \ + a variety of reasons from Kin to Kin, they tend to shy away from using modern human technology.", + + "Physically, the Hearthkin always come in the form of demihumans; appearing similar to normal Earthlings, \ + but with the tails, ears, and sometimes limbs of various arctic animals; wolves, bears, and felines to only name a few. \ + They seem perfectly adapted to their lands of ice and mist, but find even the mild controlled temperatures of \ + NanoTrasen stations to be swelteringly hot. Their view of 'station' genemodders is that of 'halflings': \ + Ancestral bodies, but with the blood and spirit of the humans of Midgard, \ + tending to look down on them even more than other aliens.", + ) diff --git a/modular_doppler/hearthkin/primitive_catgirls/code/translator.dm b/modular_doppler/hearthkin/primitive_catgirls/code/translator.dm new file mode 100644 index 0000000000000..4963f7160ab99 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/code/translator.dm @@ -0,0 +1,73 @@ +//TRANSLATOR NECKLACE// +#define LANGUAGE_TRANSLATOR "translator" + +/obj/item/clothing/neck/necklace/translator/ + name = "antique necklace" + desc = "A necklace with a old, strange device as its pendant. Symbols \ + constantly seem to appear on its screen, as noises happen around it, \ + but its purpose is not immediately apparent." + icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/translator.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_catgirls/icons/translator_worn.dmi' + icon_state = "translator" + slot_flags = ITEM_SLOT_NECK | ITEM_SLOT_OCLOTHING + w_class = WEIGHT_CLASS_SMALL //allows this to fit inside of pockets. + /// The language granted by this necklace + var/datum/language/language_granted = /datum/language/uncommon + + +/obj/item/clothing/neck/necklace/translator/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ITEM_EQUIPPED, PROC_REF(on_necklace_equip)) + + +/// Handles giving the language to the equipper when equipped. +/obj/item/clothing/neck/necklace/translator/proc/on_necklace_equip(datum/source, mob/living/carbon/human/equipper, slot) + SIGNAL_HANDLER + + if(!(slot_flags & slot)) + return + + if(!istype(equipper)) + return + + equipper.grant_language(language_granted, source = LANGUAGE_TRANSLATOR) + RegisterSignal(src, COMSIG_ITEM_DROPPED, PROC_REF(on_necklace_unequip)) + + equip_feedback(equipper) + + +/// Handles sending text feedback to the equipper. Override to change the text. +/obj/item/clothing/neck/necklace/translator/proc/equip_feedback(mob/living/carbon/human/equipper) + to_chat(equipper, span_notice( \ + "Slipping the necklace on, you notice a slight buzzing in your ears, \ + and that any word in [initial(language_granted.name)] said in your \ + general vicinity is immediately translated to your native language, \ + directly in your ears. Not only that, but you find yourself able to \ + speak your mind in such a way that the pendant translates your words \ + back in [initial(language_granted.name)]." \ + )) + + +/// Handles removing the language from the unequipper when unequipped. +/obj/item/clothing/neck/necklace/translator/proc/on_necklace_unequip(obj/item/source, mob/living/carbon/human/unequipper) + SIGNAL_HANDLER + + if(!istype(unequipper)) + return + + unequipper.remove_language(language_granted, source = LANGUAGE_TRANSLATOR) + UnregisterSignal(source, COMSIG_ITEM_DROPPED) + + unequip_feedback(unequipper) + + +/// Handles sending text feedback to the unequipper. Override to change the text. +/obj/item/clothing/neck/necklace/translator/proc/unequip_feedback(mob/living/carbon/human/unequipper) + to_chat(unequipper, span_boldnotice( \ + "\The [src]'s constant buzzing suddenly stops. Peace, at last. \ + You also lose your artificial grasp on [initial(language_granted.name)], \ + unfortunately. Such is the price for peace and quiet." \ + )) + + +#undef LANGUAGE_TRANSLATOR diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi new file mode 100644 index 0000000000000..4599c4404da66 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/clothing_greyscale.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/gods_statue.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/gods_statue.dmi new file mode 100644 index 0000000000000..5a61f36148914 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/gods_statue.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/language_icon.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/language_icon.dmi new file mode 100644 index 0000000000000..02cec3342ba77 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/language_icon.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi new file mode 100644 index 0000000000000..a409593f875d5 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/loincloth.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi new file mode 100644 index 0000000000000..2382f1e7d0808 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/objects.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/organs.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/organs.dmi new file mode 100644 index 0000000000000..dd5a091866b39 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/organs.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/pelt.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt.dmi new file mode 100644 index 0000000000000..8015469b93fd1 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_big.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_big.dmi new file mode 100644 index 0000000000000..53c897995a49c Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_big.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_worn.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_worn.dmi new file mode 100644 index 0000000000000..c1a46d0c01379 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/pelt_worn.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/salts.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/salts.dmi new file mode 100644 index 0000000000000..dfb65f36f1977 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/salts.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi new file mode 100644 index 0000000000000..8a7ff309998e7 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/special_metals_stack.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/translator.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/translator.dmi new file mode 100644 index 0000000000000..d0a29cf363cbe Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/translator.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/icons/translator_worn.dmi b/modular_doppler/hearthkin/primitive_catgirls/icons/translator_worn.dmi new file mode 100644 index 0000000000000..2934348e8eec9 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_catgirls/icons/translator_worn.dmi differ diff --git a/modular_doppler/hearthkin/primitive_catgirls/readme.md b/modular_doppler/hearthkin/primitive_catgirls/readme.md new file mode 100644 index 0000000000000..b2556a429e167 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_catgirls/readme.md @@ -0,0 +1,29 @@ +## Title: Primitive Catgirls + +MODULE ID: PRIMITIVE_CATGIRLS + +### Description: + +Main module of the Hearthkin. It adds species, unique language, special organs and map items as well as unique objects and clothing. +Special metals, as well as the pet commands functionality have been added too. + +The species ID `primitive_felinid` was added in the configuration file `config\doppler\config_doppler.txt` as a round start species + +### TG Proc Changes: + +N/A + +### Defines: + +- `code\__DEFINES\~doppler_defines\is_helpers.dm` is_type identificator for species +- `code\__DEFINES\~doppler_defines\species.dm` species id + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/big_mortar.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/big_mortar.dm new file mode 100644 index 0000000000000..76e8176ffb87e --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/big_mortar.dm @@ -0,0 +1,235 @@ +/obj/structure/large_mortar + name = "large mortar" + desc = "A large bowl perfect for grinding or juicing a large number of things at once." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/cooking_structures.dmi' + icon_state = "big_mortar" + density = TRUE + anchored = TRUE + max_integrity = 100 + pass_flags = PASSTABLE + resistance_flags = FLAMMABLE + custom_materials = list( + /datum/material/wood = SHEET_MATERIAL_AMOUNT * 10, + ) + /// The maximum number of items this structure can store + var/maximum_contained_items = 10 + var/in_use = FALSE + +/obj/structure/large_mortar/Initialize(mapload) + . = ..() + create_reagents(200, OPENCONTAINER) + + AddElement(/datum/element/falling_hazard, damage = 20, wound_bonus = 5, hardhat_safety = TRUE, crushes = FALSE) + +/obj/structure/large_mortar/examine(mob/user) + . = ..() + . += span_notice("It currently contains [length(contents)]/[maximum_contained_items] items.") + . += span_notice("It can be (un)secured with Right Click") + . += span_notice("You can empty all of the items out of it with Alt Click") + +/obj/structure/large_mortar/Destroy() + drop_everything_contained() + return ..() + +/obj/structure/large_mortar/click_alt(mob/user) + if(in_use) // If the big mortar is currently in use by someone then we cannot use it + balloon_alert(user, "big mortar busy") + return CLICK_ACTION_BLOCKING + + if(!length(contents)) + balloon_alert(user, "nothing inside") + return CLICK_ACTION_BLOCKING + + drop_everything_contained() + balloon_alert(user, "removed all items") + return CLICK_ACTION_SUCCESS + +/// Drops all contents at the mortar +/obj/structure/large_mortar/proc/drop_everything_contained() + if(!length(contents)) + return + + for(var/obj/target_item as anything in contents) + target_item.forceMove(get_turf(src)) + +/obj/structure/large_mortar/attack_hand_secondary(mob/user, list/modifiers) + if(in_use) // If the big mortar is currently in use by someone then we cannot use it + balloon_alert(user, "big mortar busy") + return + + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + if(!can_interact(user) || !user.can_perform_action(src)) + return + + set_anchored(!anchored) + balloon_alert_to_viewers(anchored ? "secured" : "unsecured") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/structure/large_mortar/attackby(obj/item/attacking_item, mob/living/carbon/human/user) + if(in_use) // If the big mortar is currently in use by someone then we cannot use it + balloon_alert(user, "big mortar busy") + return + + if(attacking_item.is_refillable()) + return + +/obj/structure/large_mortar/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + if(in_use) // If the big mortar is currently in use by someone then we cannot use it + balloon_alert(user, "big mortar busy") + return ITEM_INTERACT_BLOCKING + + . = ..() + if(. || user.combat_mode || tool.is_refillable()) + return . + if(istype(tool, /obj/item/storage/bag)) + if(length(contents) >= maximum_contained_items) + balloon_alert(user, "already full!") + return ITEM_INTERACT_BLOCKING + + if(!length(tool.contents)) + balloon_alert(user, "nothing to transfer!") + return ITEM_INTERACT_BLOCKING + + for(var/obj/item/target_item in tool.contents) + if(length(contents) >= maximum_contained_items) + break + + if(target_item.juice_typepath || target_item.grind_results) + target_item.forceMove(src) + + if (length(contents) >= maximum_contained_items) + balloon_alert(user, "filled") + else + balloon_alert(user, "transferred") + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/pestle)) + if(!anchored) + balloon_alert(user, "not secured!") + return ITEM_INTERACT_BLOCKING + + if(!length(contents) && reagents.total_volume == 0) + balloon_alert(user, "mortar empty!") + return ITEM_INTERACT_BLOCKING + + var/list/choose_options = list( + "Grind" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_grind"), + "Juice" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_juice"), + "Mix" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix"), + ) + var/picked_option = show_radial_menu(user, src, choose_options, radius = 38, require_near = TRUE) + + if(!in_range(src, user) || !user.is_holding(tool) || !picked_option) + return ITEM_INTERACT_BLOCKING + var/act_verb = LOWER_TEXT(picked_option) + var/act_verb_ing + if(act_verb == "juice") + act_verb_ing = "juicing" + else + act_verb_ing = "[act_verb]ing" + + var/has_resource + if(picked_option == "Mix") + has_resource = reagents.total_volume > 0 + else + has_resource = length(contents) > 0 + + if(!has_resource) + balloon_alert(user, "nothing to [act_verb]!") + return ITEM_INTERACT_BLOCKING + + balloon_alert_to_viewers("[act_verb_ing]...") + + in_use = TRUE + + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert_to_viewers("stopped [act_verb_ing]") + in_use = FALSE + return ITEM_INTERACT_BLOCKING + + switch(picked_option) + if("Juice") + for(var/obj/item/target_item as anything in contents) + if (reagents.total_volume >= reagents.maximum_volume) + balloon_alert(user, "overflowing!") + break + if(target_item.juice_typepath) + juice_target_item(target_item, user) + else + grind_target_item(target_item, user) + + if("Grind") + for(var/obj/item/target_item as anything in contents) + if (reagents.total_volume >= reagents.maximum_volume) + balloon_alert(user, "overflowing!") + break + if(target_item.grind_results) + grind_target_item(target_item, user) + else + juice_target_item(target_item, user) + if("Mix") + mix() + + in_use = FALSE + return ITEM_INTERACT_SUCCESS + + if(!tool.grind_results && !tool.juice_typepath) + balloon_alert(user, "can't grind this!") + return ITEM_INTERACT_BLOCKING + + if(length(contents) >= maximum_contained_items) + balloon_alert(user, "already full!") + return ITEM_INTERACT_BLOCKING + + tool.forceMove(src) + return ITEM_INTERACT_SUCCESS + +///Juices the passed target item, and transfers any contained chems to the mortar as well +/obj/structure/large_mortar/proc/juice_target_item(obj/item/to_be_juiced, mob/living/carbon/human/user) + if(to_be_juiced.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to juice [to_be_juiced], but it fades away!")) + qdel(to_be_juiced) + return + + if(!to_be_juiced.juice(src.reagents, user)) + to_chat(user, span_danger("You fail to juice [to_be_juiced].")) + + to_chat(user, span_notice("You juice [to_be_juiced] into a liquid.")) + QDEL_NULL(to_be_juiced) + +///Grinds the passed target item, and transfers any contained chems to the mortar as well +/obj/structure/large_mortar/proc/grind_target_item(obj/item/to_be_ground, mob/living/carbon/human/user) + if(to_be_ground.flags_1 & HOLOGRAM_1) + to_chat(user, span_notice("You try to grind [to_be_ground], but it fades away!")) + qdel(to_be_ground) + return + + if(!to_be_ground.grind(src.reagents, user)) + if(isstack(to_be_ground)) + to_chat(user, span_notice("[src] attempts to grind as many pieces of [to_be_ground] as possible.")) + else + to_chat(user, span_danger("You fail to grind [to_be_ground].")) + + to_chat(user, span_notice("You break [to_be_ground] into a fine powder.")) + QDEL_NULL(to_be_ground) + +///Mixes contained reagents, creating butter/mayo/whipped cream +/obj/structure/large_mortar/proc/mix() + //Recipe to make Butter + var/butter_amt = FLOOR(reagents.get_reagent_amount(/datum/reagent/consumable/milk) / MILK_TO_BUTTER_COEFF, 1) + var/purity = reagents.get_reagent_purity(/datum/reagent/consumable/milk) + reagents.remove_reagent(/datum/reagent/consumable/milk, MILK_TO_BUTTER_COEFF * butter_amt) + for(var/i in 1 to butter_amt) + var/obj/item/food/butter/tasty_butter = new(drop_location()) + tasty_butter.reagents.set_all_reagents_purity(purity) + + //Recipe to make Mayonnaise + if (reagents.has_reagent(/datum/reagent/consumable/eggyolk)) + reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise) + + //Recipe to make whipped cream + if (reagents.has_reagent(/datum/reagent/consumable/cream)) + reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream) diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/cauldron.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/cauldron.dm new file mode 100644 index 0000000000000..911beb129215f --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/cauldron.dm @@ -0,0 +1,339 @@ +/obj/machinery/cauldron + name = "stone cauldron" + desc = "Cooks and boils stuff the old fashioned way." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi' + icon_state = "cauldron_back_off" + density = TRUE + pass_flags_self = PASSMACHINE | PASSTABLE| LETPASSTHROW // It's roughly the height of a table. + layer = BELOW_OBJ_LAYER + use_power = FALSE + circuit = null + resistance_flags = FIRE_PROOF + /// Whether it's currently cooking + var/operating + /// Lid position + var/open + /// Cauldron max capacity + var/max_n_of_items = 10 + /// Ingredients - may only contain /atom/movables + var/list/ingredients = list() + /// When this is the nth ingredient, whats its pixel_x? + var/list/ingredient_shifts_x = list( + -1, + 1, + -2, + 2, + -3, + 0, + ) + /// When this is the nth ingredient, whats its pixel_y? + var/list/ingredient_shifts_y = list( + 7, + 6, + 5, + ) + + /// Radial list icons + var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject") + var/static/radial_cook = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_cook") + + /// Radial list options + var/static/list/radial_options = list("eject" = radial_eject, "cook" = radial_cook) + +/obj/machinery/cauldron/Initialize(mapload) + . = ..() + register_context() + update_appearance(UPDATE_ICON) + +/obj/machinery/cauldron/examine(mob/user) + . = ..() + + . += span_notice("It can be taken apart with a crowbar.") + + if(!in_range(user, src) && !isobserver(user)) + . += span_warning("You're too far away to examine [src]'s contents!") + return + + if(length(ingredients)) + . += span_notice("\The [src] contains:") + var/list/items_counts = new + for(var/i in ingredients) + if(isstack(i)) + var/obj/item/stack/item_stack = i + items_counts[item_stack.name] += item_stack.amount + else + var/atom/movable/single_item = i + items_counts[single_item.name]++ + for(var/item in items_counts) + . += span_notice("- [items_counts[item]]x [item].") + else + . += span_notice("\The [src] is empty.") + +/obj/machinery/cauldron/Exited(atom/movable/gone, direction) + if(gone in ingredients) + ingredients -= gone + if(!QDELING(gone) && ingredients.len && isitem(gone)) + var/obj/item/itemized_ingredient = gone + if(!(itemized_ingredient.item_flags & NO_PIXEL_RANDOM_DROP)) + itemized_ingredient.pixel_x = itemized_ingredient.base_pixel_x + rand(-6, 6) + itemized_ingredient.pixel_y = itemized_ingredient.base_pixel_y + rand(-5, 6) + return ..() + +/obj/machinery/cauldron/on_deconstruction(disassembled) + eject() + return ..() + +/obj/machinery/cauldron/Destroy() + QDEL_LIST(ingredients) + return ..() + +/obj/machinery/cauldron/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(held_item?.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unsecure" : "Secure"]" + return CONTEXTUAL_SCREENTIP_SET + + if(held_item?.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + + context[SCREENTIP_CONTEXT_LMB] = "Show menu" + + if(length(ingredients) != 0) + context[SCREENTIP_CONTEXT_RMB] = "Start cooking" + + return CONTEXTUAL_SCREENTIP_SET + +#define CAULDRON_INGREDIENT_OVERLAY_SIZE 14 + +/obj/machinery/cauldron/update_overlays() + . = ..() + + var/ingredient_count = 0 + + for(var/atom/movable/ingredient as anything in ingredients) + var/image/ingredient_overlay = image(ingredient, src) + + var/list/icon_dimensions = get_icon_dimensions(ingredient.icon) + ingredient_overlay.transform = ingredient_overlay.transform.Scale( + CAULDRON_INGREDIENT_OVERLAY_SIZE / icon_dimensions["width"], + CAULDRON_INGREDIENT_OVERLAY_SIZE / icon_dimensions["height"], + ) + + ingredient_overlay.pixel_x = ingredient_shifts_x[(ingredient_count % ingredient_shifts_x.len) + 1] + ingredient_overlay.pixel_y = ingredient_shifts_y[(ingredient_count % ingredient_shifts_y.len) + 1] + ingredient_overlay.layer = FLOAT_LAYER + ingredient_overlay.plane = FLOAT_PLANE + ingredient_overlay.blend_mode = BLEND_INSET_OVERLAY + + ingredient_count += 1 + + . += ingredient_overlay + + var/base_icon_state = "cauldron_front" + var/lid_icon_state + + if(open) + lid_icon_state = "cauldron_lid_open" + else + lid_icon_state = "cauldron_lid_closed" + + + . += mutable_appearance( + icon, + lid_icon_state, + ) + + . += base_icon_state + +#undef CAULDRON_INGREDIENT_OVERLAY_SIZE + +/obj/machinery/cauldron/update_icon_state() + if(operating) + icon_state = "cauldron_back_cooking" + else + icon_state = "cauldron_back_off" + + return ..() + +/obj/machinery/cauldron/wrench_act(mob/living/user, obj/item/tool) + if(default_unfasten_wrench(user, tool)) + update_appearance() + return ITEM_INTERACT_SUCCESS + +/obj/machinery/cauldron/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + new /obj/item/stack/sheet/mineral/stone(drop_location(), 5) // Made with stone instead of iron so that it doesn't outbalance microwaves on station + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/cauldron/attackby(obj/item/item, mob/living/user, params) + if(operating) + return + + if(!anchored) + if(IS_EDIBLE(item)) + balloon_alert(user, "not secured!") + return TRUE + return ..() + + if(istype(item, /obj/item/storage)) + var/obj/item/storage/tray = item + var/loaded = 0 + + if(!istype(item, /obj/item/storage/bag/tray)) + // Non-tray dumping requires a do_after + to_chat(user, span_notice("You start dumping out the contents of [item] into [src]...")) + if(!do_after(user, 2 SECONDS, target = tray)) + return + + for(var/obj/tray_item in tray.contents) + if(!IS_EDIBLE(tray_item)) + continue + if(ingredients.len >= max_n_of_items) + balloon_alert(user, "it's full!") + return TRUE + if(tray.atom_storage.attempt_remove(tray_item, src)) + loaded++ + ingredients += tray_item + if(loaded) + open() + to_chat(user, span_notice("You insert [loaded] items into \the [src].")) + update_appearance() + return + + if(item.w_class <= WEIGHT_CLASS_NORMAL && !istype(item, /obj/item/storage) && !user.combat_mode) + if(ingredients.len >= max_n_of_items) + balloon_alert(user, "it's full!") + return TRUE + if(!user.transferItemToLoc(item, src)) + balloon_alert(user, "it's stuck to your hand!") + return FALSE + + ingredients += item + open() + user.visible_message(span_notice("[user] adds \a [item] to \the [src]."), span_notice("You add [item] to \the [src].")) + update_appearance() + return + + return ..() + +/obj/machinery/cauldron/attack_hand_secondary(mob/user, list/modifiers) + if(user.can_perform_action(src)) + if(!length(ingredients)) + balloon_alert(user, "it's empty!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + cook(user) + + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/cauldron/ui_interact(mob/user) + . = ..() + + if(!anchored) + balloon_alert(user, "not secured!") + return + if(operating || !user.can_perform_action(src)) + return + + if(!length(ingredients)) + if(isAI(user)) + examine(user) + else + balloon_alert(user, "it's empty!") + return + + var/choice = show_radial_menu(user, src, radial_options, require_near = TRUE) + + // post choice verification + if(!anchored) + balloon_alert(user, "not secured!") + return + if(operating || !user.can_perform_action(src)) + return + + switch(choice) + if("eject") + eject() + if("cook") + cook(user) + if("examine") + examine(user) + +/** + * Ejects all the ingredients currently stored in the cauldron. + * Called by deconstruction, finishing cooking, or user selecting the eject option. + */ +/obj/machinery/cauldron/proc/eject() + var/atom/drop_loc = drop_location() + for(var/atom/movable/movable_ingredient as anything in ingredients) + movable_ingredient.forceMove(drop_loc) + open() + +/** + * Begins the process of cooking the included ingredients. + * + * * cooker - The mob that initiated the cook cycle + */ +/obj/machinery/cauldron/proc/cook(mob/cooker) + if(operating || !anchored) + return + + start(cooker) + +/** + * The start of the cook loop + * + * * cooker - The mob that initiated the cook cycle + */ +/obj/machinery/cauldron/proc/start(mob/cooker) + visible_message(span_notice("\The [src] turns on."), null, span_hear("You hear bubbling as the cauldron ignites.")) + operating = TRUE + update_appearance() + cook_loop(cycles = 10, cooker = cooker) + +/** + * The actual cook loop started via [proc/start] + * * time - how many loops are left, base case for recursion + * * wait - deciseconds between loops + * * cooker - The mob that initiated the cook cycle + */ +/obj/machinery/cauldron/proc/cook_loop(cycles, wait = max(12, 2), mob/cooker) + if(cycles <= 0 || !length(ingredients)) + loop_finish(cooker) + return + cycles-- + addtimer(CALLBACK(src, PROC_REF(cook_loop), cycles, wait, cooker), wait) + +/** + * Called when the cook_loop is done successfully + * + * * cooker - The mob that initiated the cook cycle + */ +/obj/machinery/cauldron/proc/loop_finish(mob/cooker) + operating = FALSE + + for(var/obj/item/cooked_item in ingredients) + cooked_item.microwave_act(src, cooker, randomize_pixel_offset = ingredients.len) + + eject() + +/** + * Temporary opens the cauldron, called when ingredients are added or ejected + * + * autoclose - how long it stays open before calling the close() proc + */ +/obj/machinery/cauldron/proc/open(autoclose = 0.6 SECONDS) + open = TRUE + update_appearance() + addtimer(CALLBACK(src, PROC_REF(close)), autoclose) + +/** + * Closes the cauldron, called by the open() proc after some delay + */ +/obj/machinery/cauldron/proc/close() + open = FALSE + update_appearance() diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/cookware.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/cookware.dm new file mode 100644 index 0000000000000..2c92a4ba45011 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/cookware.dm @@ -0,0 +1,33 @@ +/obj/item/reagent_containers/cup/soup_pot/material + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/cookware.dmi' + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + +// A few random preset types as well + +/obj/item/reagent_containers/cup/soup_pot/material/fake_copper + custom_materials = list(/datum/material/copporcitite=SHEET_MATERIAL_AMOUNT) + +/obj/item/reagent_containers/cup/soup_pot/material/fake_brass + custom_materials = list(/datum/material/brussite=SHEET_MATERIAL_AMOUNT) + +/obj/item/reagent_containers/cup/soup_pot/material/fake_tin + custom_materials = list(/datum/material/tinumium=SHEET_MATERIAL_AMOUNT) + +// Oven Trays +/obj/item/plate/oven_tray/material + desc = "Time to bake hardtack!" + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/cookware.dmi' + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + fragile = FALSE + +// A few random preset types as well + +/obj/item/plate/oven_tray/material/fake_copper + custom_materials = list(/datum/material/copporcitite=SHEET_MATERIAL_AMOUNT) + +/obj/item/plate/oven_tray/material/fake_brass + custom_materials = list(/datum/material/brussite=SHEET_MATERIAL_AMOUNT) + +/obj/item/plate/oven_tray/material/fake_tin + custom_materials = list(/datum/material/tinumium=SHEET_MATERIAL_AMOUNT) + diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/cutting_board.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/cutting_board.dm new file mode 100644 index 0000000000000..69a1f26e06334 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/cutting_board.dm @@ -0,0 +1,146 @@ +#define GET_RECIPE(input_thing) LAZYACCESS(processor_inputs[/obj/machinery/processor], input_thing.type) + +/obj/item/cutting_board + name = "cutting board" + desc = "Processing food before electricity was cool, because you can just do your regular cutting on the table next to this right?" + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/cooking_structures.dmi' + icon_state = "cutting_board" + force = 5 + throwforce = 7 //Imagine someone just throws the entire fucking cutting board at you + w_class = WEIGHT_CLASS_NORMAL + pass_flags = PASSTABLE + layer = BELOW_OBJ_LAYER //So newly spawned food appears on top of the board rather than under it + resistance_flags = FLAMMABLE + ///List containg list of possible inputs and resulting recipe items, taken from processor.dm and processor_recipes.dm + var/static/list/processor_inputs + +/obj/item/cutting_board/Initialize(mapload) + . = ..() + if(processor_inputs) + return + + processor_inputs = list() + for(var/datum/food_processor_process/recipe as anything in subtypesof(/datum/food_processor_process)) //this is how tg food processors do it just in case this is digusting + if(!initial(recipe.input)) + continue + + recipe = new recipe + var/list/typecache = list() + var/list/bad_types + + for(var/bad_type in recipe.blacklist) + LAZYADD(bad_types, typesof(bad_type)) + + for(var/input_type in typesof(recipe.input) - bad_types) + typecache[input_type] = recipe + + for(var/machine_type in typesof(recipe.required_machine)) + LAZYADD(processor_inputs[machine_type], typecache) + +/obj/item/cutting_board/update_appearance() + . = ..() + cut_overlays() + if(!length(contents)) + return + var/image/overlayed_item = image(icon = contents[1].icon, icon_state = contents[1].icon_state, pixel_y = 2) + add_overlay(overlayed_item) + +/obj/item/cutting_board/examine(mob/user) + . = ..() + . += span_notice("You can process food similar to a food processor by putting food on this and using a knife on it.") + . += span_notice("It can be (un)secured with Right Click") + . += span_notice("You can make it drop its item with Alt Click") + if(length(contents)) + . += span_notice("It has [contents[1]] sitting on it.") + +/obj/item/cutting_board/Destroy() + drop_everything_contained() + return ..() + +/obj/item/cutting_board/click_alt(mob/user) + if(!length(contents)) + balloon_alert(user, "nothing on board") + return CLICK_ACTION_BLOCKING + + drop_everything_contained() + balloon_alert(user, "cleared board") + return CLICK_ACTION_SUCCESS + +///Drops all contents at the turf of the item +/obj/item/cutting_board/proc/drop_everything_contained() + if(!length(contents)) + return + + for(var/obj/target_item as anything in contents) + target_item.forceMove(get_turf(src)) + +/obj/item/cutting_board/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + if(!can_interact(user) || !user.can_perform_action(src)) + return + + set_anchored(!anchored) + balloon_alert_to_viewers(anchored ? "secured" : "unsecured") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +///Takes the given obj (processed thing) and gets its results from the recipe list, spawning the results and deleting the original obj +/obj/item/cutting_board/proc/process_food(datum/food_processor_process/recipe, obj/processed_thing) + if(!recipe.output || !loc || QDELETED(src)) + return + + var/food_multiplier = recipe.food_multiplier + for(var/i in 1 to food_multiplier) + var/obj/new_food_item = new recipe.output(drop_location()) + new_food_item.pixel_x = rand(-6, 6) + new_food_item.pixel_y = rand(-6, 6) + + if(!processed_thing.reagents) //backup in case we really fuck up + continue + + processed_thing.reagents.copy_to(new_food_item, processed_thing.reagents.total_volume, multiplier = 1 / food_multiplier) + + qdel(processed_thing) + update_appearance() + +/obj/item/cutting_board/attackby(obj/item/attacking_item, mob/living/user, params) + if(user.combat_mode) + return ..() + + if(attacking_item.tool_behaviour == TOOL_KNIFE) + if(!length(contents)) + balloon_alert(user, "nothing to process") + return + + var/datum/food_processor_process/item_process_recipe = GET_RECIPE(contents[1]) + if(!item_process_recipe) + log_admin("DEBUG: [src] (cutting board item) just tried to process [contents[1]] but wasn't able to get a recipe somehow, this should not be able to happen.") + return + + playsound(src, 'sound/effects/butcher.ogg', 50, TRUE) + balloon_alert_to_viewers("cutting...") + if(!do_after(user, 3 SECONDS, target = src)) + balloon_alert_to_viewers("stopped cutting") + return + + process_food(item_process_recipe, contents[1]) + return + + var/datum/food_processor_process/gotten_recipe = GET_RECIPE(attacking_item) + if(gotten_recipe) + if(length(contents)) + balloon_alert(user, "board is full") + return + + attacking_item.forceMove(src) + balloon_alert(user, "placed [attacking_item] on board") + update_appearance() + return + + if(IS_EDIBLE(attacking_item)) //We may have failed but the user wants some feedback on why they can't put x food item on the board + balloon_alert(user, "[attacking_item] can't be processed") + return ..() + +#undef GET_RECIPE diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/millstone.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/millstone.dm new file mode 100644 index 0000000000000..e251c3f629f17 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/millstone.dm @@ -0,0 +1,173 @@ +/obj/structure/millstone + name = "millstone" + desc = "Two big disks of something heavy and tough. Put a plant between them and spin, and you'll end up with seeds and a really ground up plant." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/millstone.dmi' + icon_state = "millstone" + density = TRUE + anchored = TRUE + max_integrity = 200 + pass_flags = PASSTABLE + custom_materials = list( + /datum/material/stone = SHEET_MATERIAL_AMOUNT * 6, + ) + drag_slowdown = 2 + /// The maximum number of items this structure can store + var/maximum_contained_items = 10 + /// Is the millstone processing plants? If true, prevents most interactions with the millstone + var/in_use = FALSE + +/obj/structure/millstone/examine(mob/user) + . = ..() + + . += span_notice("It currently contains [length(contents)]/[maximum_contained_items] items.") + . += span_notice("You can process [src]'s contents with Right Click") + . += span_notice("You can empty all of the items out of it with Alt Click") + + if(length(contents)) + . += span_notice("Inside, you can see:") + var/list/stuff_inside = list() + for(var/obj/thing as anything in contents) + stuff_inside[thing.type] += 1 + + for(var/obj/thing as anything in stuff_inside) + . += span_notice("• [stuff_inside[thing]] [initial(thing.name)]\s") + + . += span_notice("And it can fit [maximum_contained_items - length(contents)] more items in it.") + else + . += span_notice("It can hold [maximum_contained_items] items, and there is nothing in it presently.") + + . += span_notice("You can [anchored ? "un" : ""]secure [src] with CTRL-Shift-Click.") + . += span_notice("With a prying tool of some sort, you could take [src] apart.") + +/obj/structure/millstone/Destroy() + drop_everything_contained() + return ..() + +/obj/structure/millstone/atom_deconstruct(disassembled) + var/obj/item/stack/sheet/mineral/stone/stone = new(drop_location(), 6) + transfer_fingerprints_to(stone) + return ..() + +/obj/structure/millstone/click_alt(mob/user) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return CLICK_ACTION_BLOCKING + + if(!length(contents)) + balloon_alert(user, "nothing inside!") + return CLICK_ACTION_BLOCKING + + drop_everything_contained() + balloon_alert(user, "removed all items") + return CLICK_ACTION_SUCCESS + +/obj/structure/millstone/click_ctrl_shift(mob/user) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return + set_anchored(!anchored) + balloon_alert(user, "[anchored ? "secured" : "unsecured"]") + +/// Drops all contents at the mortar +/obj/structure/millstone/proc/drop_everything_contained() + if(!length(contents)) + return + + for(var/obj/target_item as anything in contents) + target_item.forceMove(get_turf(src)) + +/obj/structure/millstone/attack_hand_secondary(mob/user, list/modifiers) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return + + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + if(!can_interact(user) || !user.can_perform_action(src)) + return + + mill_it_up(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/structure/millstone/crowbar_act(mob/living/user, obj/item/tool) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return + + . = ..() + balloon_alert_to_viewers("disassembling...") + if(!do_after(user, 2 SECONDS, src)) + return + deconstruct(TRUE) + +/obj/structure/millstone/attackby(obj/item/attacking_item, mob/user) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return + + if(istype(attacking_item, /obj/item/storage/bag)) + if(length(contents) >= maximum_contained_items) + balloon_alert(user, "already full") + return TRUE + + if(!length(attacking_item.contents)) + balloon_alert(user, "nothing to transfer!") + return TRUE + + for(var/obj/item/food/grown/target_item in attacking_item.contents) + if(length(contents) >= maximum_contained_items) + break + + target_item.forceMove(src) + + if (length(contents) >= maximum_contained_items) + balloon_alert(user, "filled!") + else + balloon_alert(user, "transferred") + + return TRUE + + if(!(istype(attacking_item, /obj/item/food/grown) || istype(attacking_item, /obj/item/grown))) + balloon_alert(user, "can only mill plants") + return ..() + + if(length(contents) >= maximum_contained_items) + balloon_alert(user, "already full") + return + + attacking_item.forceMove(src) + balloon_alert(user, "transferred [attacking_item]") + return TRUE + +/// Takes the content's seeds and spits them out on the turf, as well as grinding whatever the contents may be +/obj/structure/millstone/proc/mill_it_up(mob/living/carbon/human/user) + if(in_use) // If the millstone is currently in use by someone then we cannot use it + balloon_alert(user, "millstone busy") + return + + if(!length(contents)) + balloon_alert(user, "nothing to mill") + return + + if(!length(contents) || !in_range(src, user)) + return + + balloon_alert_to_viewers("grinding...") + + in_use = TRUE + + flick("millstone_spin", src) + playsound(src, 'sound/effects/stonedoor_openclose.ogg', 50, TRUE) + + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert_to_viewers("stopped grinding") + in_use = FALSE + return + + in_use = FALSE + for(var/target_item as anything in contents) + seedify(target_item, t_max = 1) + + balloon_alert_to_viewers("finished grinding") diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/plant_bag.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/plant_bag.dm new file mode 100644 index 0000000000000..9ceadd7057980 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/plant_bag.dm @@ -0,0 +1,69 @@ +#define RESKIN_LINEN "Linen" + +/obj/item/storage/bag/plants + uses_advanced_reskins = TRUE + unique_reskin = list( + "Original" = list( + RESKIN_ICON = 'icons/obj/service/hydroponics/equipment.dmi', + RESKIN_ICON_STATE = "plantbag", + RESKIN_WORN_ICON = 'icons/mob/clothing/belt.dmi', + RESKIN_WORN_ICON_STATE = "plantbag", + ), + RESKIN_LINEN = list( + RESKIN_ICON = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag.dmi', + RESKIN_ICON_STATE = "plantbag_primitive", + RESKIN_WORN_ICON = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn.dmi', + RESKIN_WORN_ICON_STATE = "plantbag_primitive", + ), + ) + + +// This is so the linen reskin shows properly in the suit storage. +/obj/item/storage/bag/plants/build_worn_icon(default_layer, default_icon_file, isinhands, female_uniform, override_state, override_file, mutant_styles) + if(default_layer == SUIT_STORE_LAYER && current_skin == RESKIN_LINEN) + override_file = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn_mirror.dmi' + + return ..() + + +/// Simple helper to reskin this item into its primitive variant. +/obj/item/storage/bag/plants/proc/make_primitive() + current_skin = RESKIN_LINEN + + icon = unique_reskin[current_skin][RESKIN_ICON] + icon_state = unique_reskin[current_skin][RESKIN_ICON_STATE] + worn_icon = unique_reskin[current_skin][RESKIN_WORN_ICON] + worn_icon_state = unique_reskin[current_skin][RESKIN_WORN_ICON_STATE] + + update_appearance() + + +/// A helper for the primitive variant, for mappers. +/obj/item/storage/bag/plants/primitive + current_skin = RESKIN_LINEN // Just so it displays properly when in suit storage + uses_advanced_reskins = FALSE + unique_reskin = null + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag.dmi' + icon_state = "plantbag_primitive" + worn_icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn.dmi' + worn_icon_state = "plantbag_primitive" + + +/obj/item/stack/sheet/cloth/on_item_crafted(mob/builder, atom/created) + if(!istype(created, /obj/item/storage/bag/plants)) + return + + if(!isprimitivedemihuman(builder)) + return + + var/obj/item/storage/bag/plants/bag = created + + bag.make_primitive() + + +/obj/item/storage/bag/plants/portaseeder + uses_advanced_reskins = FALSE + unique_reskin = null + + +#undef RESKIN_LINEN diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_griddle.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_griddle.dm new file mode 100644 index 0000000000000..a78d5ef140574 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_griddle.dm @@ -0,0 +1,32 @@ +/obj/machinery/griddle/stone + name = "stone griddle" + desc = "You could probably cook an egg on this... the griddle slab looks very unsanitary." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi' + icon_state = "griddle1_off" + density = TRUE + pass_flags_self = PASSMACHINE | PASSTABLE| LETPASSTHROW // It's roughly the height of a table. + layer = BELOW_OBJ_LAYER + use_power = FALSE + circuit = null + resistance_flags = FIRE_PROOF + processing_flags = START_PROCESSING_MANUALLY + variant = 1 + +/obj/machinery/griddle/Initialize(mapload) + . = ..() + grill_loop = new(src, FALSE) + if(isnum(variant)) + variant = 1 + +/obj/machinery/griddle/stone/examine(mob/user) + . = ..() + + . += span_notice("It can be taken apart with a crowbar.") + +/obj/machinery/griddle/stone/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + new /obj/item/stack/sheet/mineral/stone(drop_location(), 5) + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_oven.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_oven.dm new file mode 100644 index 0000000000000..676c49ee06d2f --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_oven.dm @@ -0,0 +1,74 @@ +#define OVEN_TRAY_Y_OFFSET -12 + +/obj/machinery/oven/stone + name = "stone oven" + desc = "Sorry buddy, all this stone used up the budget that would have normally gone to garfield comic jokes." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi' + circuit = null + use_power = FALSE + + /// A list of the different oven trays we can spawn with + var/static/list/random_oven_tray_types = list( + /obj/item/plate/oven_tray/material/fake_copper, + /obj/item/plate/oven_tray/material/fake_brass, + /obj/item/plate/oven_tray/material/fake_tin, + ) + +/obj/machinery/oven/stone/Initialize(mapload) + . = ..() + + if(!mapload) + return + + if(used_tray) // We have to get rid of normal generic tray that normal ovens spawn with + QDEL_NULL(used_tray) + + var/new_tray_type_to_use = pick(random_oven_tray_types) + add_tray_to_oven(new new_tray_type_to_use(src)) + +/obj/machinery/oven/stone/examine(mob/user) + . = ..() + + . += span_notice("It can be taken apart with a crowbar.") + +// formerly NO_DECONSTRUCTION +/obj/machinery/oven/stone/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) + return NONE + +/obj/machinery/oven/stone/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel, custom_deconstruct) + return NONE + +/obj/machinery/oven/stone/default_pry_open(obj/item/crowbar, close_after_pry, open_density, closed_density) + return NONE + +/obj/machinery/oven/stone/add_tray_to_oven(obj/item/plate/oven_tray, mob/baker) + used_tray = oven_tray + + if(!open) + oven_tray.vis_flags |= VIS_HIDE + vis_contents += oven_tray + oven_tray.flags_1 |= IS_ONTOP_1 + oven_tray.vis_flags |= VIS_INHERIT_PLANE + oven_tray.pixel_y = OVEN_TRAY_Y_OFFSET + + RegisterSignal(used_tray, COMSIG_MOVABLE_MOVED, PROC_REF(on_tray_moved)) + update_baking_audio() + update_appearance() + +/obj/machinery/oven/stone/set_smoke_state(new_state) + . = ..() + + if(particles) + particles.position = list(0, 10, 0) + +/obj/machinery/oven/stone/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/oven/stone/on_deconstruction(disassembled) + new /obj/item/stack/sheet/mineral/stone(drop_location(), 5) + +#undef OVEN_TRAY_Y_OFFSET diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_stove.dm b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_stove.dm new file mode 100644 index 0000000000000..b7ea86609802e --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/code/stone_stove.dm @@ -0,0 +1,70 @@ +/obj/machinery/primitive_stove + name = "stone stove" + desc = "You think you'll stick to just putting pots on this, the grill part looks very unsanitary." + icon = 'modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi' + icon_state = "stove_off" + base_icon_state = "stove" + density = TRUE + pass_flags_self = PASSMACHINE | LETPASSTHROW + layer = BELOW_OBJ_LAYER + use_power = FALSE + circuit = null + resistance_flags = FIRE_PROOF + +/obj/machinery/primitive_stove/Initialize(mapload) + . = ..() + var/obj/item/reagent_containers/cup/soup_pot/mapload_container + if(mapload) + mapload_container = new(loc) + + AddComponent(/datum/component/stove/primitive, container_x = -7, container_y = 7, spawn_container = mapload_container) + +/obj/machinery/primitive_stove/examine(mob/user) + . = ..() + + . += span_notice("It can be taken apart with a crowbar.") + +// formerly NO_DECONSTRUCTION +/obj/machinery/primitive_stove/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) + return NONE + +/obj/machinery/primitive_stove/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel, custom_deconstruct) + return NONE + +/obj/machinery/primitive_stove/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/primitive_stove/on_deconstruction(disassembled) + new /obj/item/stack/sheet/mineral/stone(drop_location(), 5) + +/// Stove component subtype with changed visuals and not much else +/datum/component/stove/primitive + flame_color = "#ff9900" + +/datum/component/stove/primitive/on_overlay_update(obj/machinery/source, list/overlays) + update_smoke() + + var/obj/real_parent = parent + + if(!on) + real_parent.icon_state = "[real_parent.base_icon_state]_off" // Not an overlay but do you really want me to override a second proc? I don't + real_parent.set_light(0, 0) + return + + real_parent.icon_state = "[real_parent.base_icon_state]_on" + real_parent.set_light(3, 1, LIGHT_COLOR_FIRE) + + overlays += emissive_appearance(real_parent.icon, "[real_parent.base_icon_state]_on_fire_emissive", real_parent, alpha = real_parent.alpha) + + if(!container) + overlays += emissive_appearance(real_parent.icon, "[real_parent.base_icon_state]_on_hole_emissive", real_parent, alpha = real_parent.alpha) + + // Flames around the pot + var/mutable_appearance/flames = mutable_appearance(real_parent.icon, "[real_parent.base_icon_state]_on_flame", alpha = real_parent.alpha) + flames.color = flame_color + overlays += flames + overlays += emissive_appearance(real_parent.icon, "[real_parent.base_icon_state]_on_flame", real_parent, alpha = real_parent.alpha) diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/cooking_structures.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/cooking_structures.dmi new file mode 100644 index 0000000000000..296899c42af56 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/cooking_structures.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/cookware.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/cookware.dmi new file mode 100644 index 0000000000000..0ed9501bfb554 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/cookware.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/millstone.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/millstone.dmi new file mode 100644 index 0000000000000..ba7d14985cb3a Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/millstone.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag.dmi new file mode 100644 index 0000000000000..aa949961a41da Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn.dmi new file mode 100644 index 0000000000000..5dc6e5fae2923 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn_mirror.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn_mirror.dmi new file mode 100644 index 0000000000000..5e3b03c8fe3ac Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/plant_bag_worn_mirror.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi b/modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi new file mode 100644 index 0000000000000..5453f17f1ebc4 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_cooking_additions/icons/stone_kitchen_machines.dmi differ diff --git a/modular_doppler/hearthkin/primitive_cooking_additions/readme.md b/modular_doppler/hearthkin/primitive_cooking_additions/readme.md new file mode 100644 index 0000000000000..e65eeba2b3d88 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_cooking_additions/readme.md @@ -0,0 +1,26 @@ +## Title: Primitive Cooking Additions + +MODULE ID: PRIMITIVE_COOKING_ADDITIONS + +### Description: + +Adds several ways to cook and prepare foods without the use of powered machines + +### TG Proc/File Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `modular_doppler\modular_crafting\code\sheet_types.dm` crafting recipes +- `modular_doppler\stone\code\stone.dm` crafting recipes + +### Credits: diff --git a/modular_doppler/hearthkin/primitive_production/code/antfarm.dm b/modular_doppler/hearthkin/primitive_production/code/antfarm.dm new file mode 100644 index 0000000000000..5eac7c79cb26a --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/antfarm.dm @@ -0,0 +1,98 @@ +/obj/structure/antfarm + name = "ant farm" + desc = "Though it may look natural, this was not made by ants." + icon = 'modular_doppler/hearthkin/primitive_production/icons/structures.dmi' + icon_state = "anthill" + density = TRUE + anchored = TRUE + /// If the farm is occupied by ants + var/has_ants = FALSE + /// the chance for the farm to get ants + var/ant_chance = 0 + /// the list of ore-y stuff that ants can drag up from deep within their nest + var/list/ore_list = list( + /obj/item/stack/ore/iron = 20, + /obj/item/stack/ore/glass/basalt = 20, + /obj/item/stack/ore/plasma = 14, + /obj/item/stack/ore/silver = 8, + /obj/item/xenoarch/strange_rock = 8, + /obj/item/stack/stone = 8, + /obj/item/stack/sheet/mineral/coal = 8, + /obj/item/stack/ore/titanium = 8, + /obj/item/stack/ore/uranium = 3, + /obj/item/stack/ore/gold = 3, + ) + // The cooldown between each worm "breeding" + COOLDOWN_DECLARE(ant_timer) + +/obj/structure/antfarm/Initialize(mapload) + . = ..() + var/turf/src_turf = get_turf(src) + if(!src_turf.GetComponent(/datum/component/simple_farm)) + src_turf.balloon_alert_to_viewers("must be on farmable surface") + return INITIALIZE_HINT_QDEL + + for(var/obj/structure/antfarm/found_farm in range(2, get_turf(src))) + if(found_farm == src) + continue + + src_turf.balloon_alert_to_viewers("too close to another farm") + return INITIALIZE_HINT_QDEL + + START_PROCESSING(SSobj, src) + COOLDOWN_START(src, ant_timer, 30 SECONDS) + +/obj/structure/antfarm/Destroy() + STOP_PROCESSING(SSobj, src) + new /obj/item/stack/ore/glass(get_turf(src), 20) + return ..() + +/obj/structure/antfarm/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, ant_timer)) + return + + COOLDOWN_START(src, ant_timer, 30 SECONDS) + + if(!has_ants) + if(prob(ant_chance)) + balloon_alert_to_viewers("ants have appeared!") + has_ants = TRUE + + return + + var/spawned_ore = pick_weight(ore_list) + new spawned_ore(get_turf(src)) + +/obj/structure/antfarm/examine(mob/user) + . = ..() + . += span_notice("
There are currently [has_ants ? "" : "no "]ants in the farm.") + if(!has_ants) + . += span_notice("To add ants, feed the farm some food.") + +/obj/structure/antfarm/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/food)) + if(has_ants) + balloon_alert(user, "ants block the way!") + return + + qdel(attacking_item) + balloon_alert(user, "food has been placed") + ant_chance++ + return + + if(istype(attacking_item, /obj/item/storage/bag/plants)) + if(has_ants) + balloon_alert(user, "ants block the way!") + return + + balloon_alert(user, "feeding the ants") + for(var/obj/item/food/selected_food in attacking_item.contents) + if(has_ants || !do_after(user, 0.5 SECONDS, src)) + return + + qdel(selected_food) + ant_chance++ + + return + + return ..() diff --git a/modular_doppler/hearthkin/primitive_production/code/ceramics.dm b/modular_doppler/hearthkin/primitive_production/code/ceramics.dm new file mode 100644 index 0000000000000..a7105a0ca4359 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/ceramics.dm @@ -0,0 +1,265 @@ +#define DEFAULT_SPIN (4 SECONDS) + +/* + * Clay Bricks + */ + +/obj/item/stack/sheet/mineral/clay + name = "clay brick" + desc = "A heavy clay brick." + singular_name = "clay brick" + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "sheet-clay" + inhand_icon_state = null + throw_speed = 3 + throw_range = 5 + merge_type = /obj/item/stack/sheet/mineral/clay + drop_sound = SFX_BRICK_DROP + pickup_sound = SFX_BRICK_PICKUP + +GLOBAL_LIST_INIT(clay_recipes, list ( \ + new/datum/stack_recipe("clay range", /obj/machinery/primitive_stove, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_MISC), \ + new/datum/stack_recipe("clay oven", /obj/machinery/oven/stone, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_MISC) \ + )) + +/obj/item/stack/sheet/mineral/clay/get_main_recipes() + . = ..() + . += GLOB.clay_recipes + +/obj/structure/water_source/puddle/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/glass_item = O + if(!glass_item.use(1)) + return + new /obj/item/stack/clay(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 1) + return + return ..() + +/turf/open/water/attackby(obj/item/C, mob/user, params) + if(istype(C, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/glass_item = C + if(!glass_item.use(1)) + return + new /obj/item/stack/clay(src) + user.mind.adjust_experience(/datum/skill/production, 1) + return + return ..() + +/obj/structure/sink/attackby(obj/item/O, mob/living/user, params) + if(istype(O, /obj/item/stack/ore/glass)) + if(dispensedreagent != /datum/reagent/water) + return + if(reagents.total_volume <= 0) + return + var/obj/item/stack/ore/glass/glass_item = O + if(!glass_item.use(1)) + return + new /obj/item/stack/clay(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 1) + return + return ..() + +/obj/item/ceramic + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + var/forge_item + +/obj/item/ceramic/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/crayon_item = attacking_item + if(!forge_item || !crayon_item.paint_color) + return + color = crayon_item.paint_color + to_chat(user, span_notice("You color [src] with [crayon_item]...")) + return + return ..() + +/obj/item/stack/clay + name = "clay" + desc = "A pile of clay that can be used to create ceramic artwork." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "clay" + merge_type = /obj/item/stack/clay + singular_name = "glob of clay" + +/datum/export/ceramics + cost = CARGO_CRATE_VALUE * 2 + unit_name = "ceramic product" + export_types = list( + /obj/item/plate/ceramic, + /obj/item/plate/oven_tray/material/ceramic, + /obj/item/reagent_containers/cup/bowl/ceramic, + /obj/item/reagent_containers/cup/beaker/large/ceramic, + ) + +/datum/export/ceramics/sell_object(obj/O, datum/export_report/report, dry_run, apply_elastic = FALSE) //I really dont want them to feel gimped + . = ..() + +/datum/export/ceramics_unfinished + cost = CARGO_CRATE_VALUE * 0.5 + unit_name = "unfinished ceramic product" + export_types = list(/obj/item/ceramic/plate, + /obj/item/ceramic/bowl, + /obj/item/ceramic/tray, + /obj/item/ceramic/cup) + +/datum/export/ceramics_unfinished/sell_object(obj/O, datum/export_report/report, dry_run, apply_elastic = FALSE) //I really dont want them to feel gimped + . = ..() + +/obj/item/ceramic/plate + name = "ceramic plate" + desc = "A piece of clay that is flat, in the shape of a plate." + icon_state = "clay_plate" + forge_item = /obj/item/plate/ceramic + +/obj/item/plate/ceramic + name = "ceramic plate" + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "clay_plate" + +/obj/item/ceramic/tray + name = "ceramic tray" + desc = "A piece of clay that is flat, in the shape of a tray." + icon_state = "clay_tray" + forge_item = /obj/item/plate/oven_tray/material/ceramic + +/obj/item/plate/oven_tray/material/ceramic + name = "ceramic oven tray" + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "clay_tray" + +/obj/item/ceramic/bowl + name = "ceramic bowl" + desc = "A piece of clay with a raised lip, in the shape of a bowl." + icon_state = "clay_bowl" + forge_item = /obj/item/reagent_containers/cup/bowl/ceramic + +/obj/item/reagent_containers/cup/bowl/ceramic + name = "ceramic bowl" + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "clay_bowl" + custom_materials = null + +/obj/item/ceramic/cup + name = "ceramic cup" + desc = "A piece of clay with high walls, in the shape of a cup. It can hold 120 units." + icon_state = "clay_cup" + forge_item = /obj/item/reagent_containers/cup/beaker/large/ceramic + +/obj/item/reagent_containers/cup/beaker/large/ceramic + name = "ceramic cup" + desc = "A cup that is made from ceramic." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "clay_cup" + custom_materials = null + +/obj/item/ceramic/brick + name = "ceramic brick" + desc = "A dense block of clay, ready to be fired into a brick!" + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "sheet-clay" + forge_item = /obj/item/stack/sheet/mineral/clay + +/obj/structure/throwing_wheel + name = "throwing wheel" + desc = "A machine that allows you to throw clay." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "throw_wheel_empty" + density = TRUE + anchored = TRUE + ///if the structure has clay + var/has_clay = FALSE + //if the structure is in use or not + var/in_use = FALSE + ///the list of messages that are sent whilst "working" the clay + var/static/list/given_message = list( + "You slowly start spinning the throwing wheel...", + "You place your hands on the clay, slowly shaping it...", + "You start becoming satisfied with what you have made...", + "You stop the throwing wheel, admiring your new creation...", + ) + +/obj/structure/throwing_wheel/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/stack/clay)) + if(has_clay) + return + var/obj/item/stack/stack_item = attacking_item + if(!stack_item.use(1)) + return + has_clay = TRUE + icon_state = "throw_wheel_full" + return + return ..() + +/obj/structure/throwing_wheel/crowbar_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + new /obj/item/stack/sheet/iron/ten(get_turf(src)) + if(has_clay) + new /obj/item/stack/clay(get_turf(src)) + qdel(src) + +/obj/structure/throwing_wheel/wrench_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + anchored = !anchored + +/obj/structure/throwing_wheel/proc/use_clay(spawn_type, mob/user) + var/spinning_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * DEFAULT_SPIN + for(var/loop_try in 1 to length(given_message)) + if(!do_after(user, spinning_speed, target = src)) + in_use = FALSE + return + to_chat(user, span_notice(given_message[loop_try])) + new spawn_type(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 50) + has_clay = FALSE + icon_state = "throw_wheel_empty" + +/obj/structure/throwing_wheel/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(in_use) + return + use(user) + in_use = FALSE + +/** + * Prompts user for how they wish to use the throwing wheel + * + * To make sure in_use var always gets set back to FALSE no matter what happens, do the actual 'using' in its own proc and do the setting to FALSE in attack_hand + * + * Arguments: + * * user - the mob who is using the throwing wheel + */ +/obj/structure/throwing_wheel/proc/use(mob/living/user) + in_use = TRUE + var/spinning_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * DEFAULT_SPIN + if(!has_clay) + balloon_alert(user, "there is no clay!") + return + var/user_input = tgui_alert(user, "What would you like to do?", "Choice Selection", list("Create", "Remove")) + if(!user_input) + return + switch(user_input) + if("Create") + var/creation_choice = tgui_input_list(user, "What you like to create?", "Creation Choice", list("Cup", "Plate", "Bowl", "Tray", "Brick")) + if(!creation_choice) + return + switch(creation_choice) + if("Cup") + use_clay(/obj/item/ceramic/cup, user) + if("Plate") + use_clay(/obj/item/ceramic/plate, user) + if("Bowl") + use_clay(/obj/item/ceramic/bowl, user) + if("Tray") + use_clay(/obj/item/ceramic/tray, user) + if("Brick") + use_clay(/obj/item/ceramic/brick, user) + if("Remove") + if(!do_after(user, spinning_speed, target = src)) + return + var/atom/movable/new_clay = new /obj/item/stack/clay(get_turf(src)) + user.put_in_active_hand(new_clay) + has_clay = FALSE + icon_state = "throw_wheel_empty" + +#undef DEFAULT_SPIN diff --git a/modular_doppler/hearthkin/primitive_production/code/farming.dm b/modular_doppler/hearthkin/primitive_production/code/farming.dm new file mode 100644 index 0000000000000..f6ccf6c8a8c47 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/farming.dm @@ -0,0 +1,296 @@ +/datum/component/simple_farm + ///whether we limit the amount of plants you can have per turf + var/one_per_turf = TRUE + ///the reference to the movable parent the component is attached to + var/atom/atom_parent + ///the amount of pixels shifted (x,y) + var/list/pixel_shift = 0 + +/datum/component/simple_farm/Initialize(set_plant = FALSE, set_turf_limit = TRUE, list/set_shift = list(0, 0)) + //we really need to check if its movable + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + atom_parent = parent + //important to allow people to just straight up set allowing to plant + one_per_turf = set_turf_limit + pixel_shift = set_shift + //now lets register the signals + RegisterSignal(atom_parent, COMSIG_ATOM_ATTACKBY, PROC_REF(check_attack)) + RegisterSignal(atom_parent, COMSIG_ATOM_EXAMINE, PROC_REF(check_examine)) + RegisterSignal(atom_parent, COMSIG_QDELETING, PROC_REF(delete_farm)) + +/datum/component/simple_farm/Destroy(force) + //lets not hard del + UnregisterSignal(atom_parent, list(COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EXAMINE, COMSIG_QDELETING)) + atom_parent = null + return ..() + +/** + * check_attack is meant to listen for the COMSIG_ATOM_ATTACKBY signal, where it essentially functions like the attackby proc + */ +/datum/component/simple_farm/proc/check_attack(datum/source, obj/item/attacking_item, mob/user) + SIGNAL_HANDLER + + //if its a seed, lets try to plant + if(istype(attacking_item, /obj/item/seeds)) + var/obj/structure/simple_farm/locate_farm = locate() in get_turf(atom_parent) + + if(one_per_turf && locate_farm) + atom_parent.balloon_alert_to_viewers("cannot plant more seeds here!") + return + + locate_farm = new(get_turf(atom_parent)) + locate_farm.pixel_x = pixel_shift[1] + locate_farm.pixel_y = pixel_shift[2] + locate_farm.layer = atom_parent.layer + 0.1 + if(ismovable(atom_parent)) + var/atom/movable/movable_parent = atom_parent + locate_farm.glide_size = movable_parent.glide_size + attacking_item.forceMove(locate_farm) + locate_farm.planted_seed = attacking_item + locate_farm.attached_atom = atom_parent + atom_parent.balloon_alert_to_viewers("seed has been planted!") + locate_farm.update_appearance() + locate_farm.late_setup() + +/** + * check_examine is meant to listen for the COMSIG_ATOM_EXAMINE signal, where it will put additional information in the examine + */ +/datum/component/simple_farm/proc/check_examine(datum/source, mob/user, list/examine_list) + examine_list += span_notice("
You are able to plant seeds here!") + +/** + * delete_farm is meant to be called when the parent of this component has been deleted-- thus deleting the ability to grow the simple farm + * it will delete the farm that can be found on the turf of the parent of this component + */ +/datum/component/simple_farm/proc/delete_farm() + SIGNAL_HANDLER + + var/obj/structure/simple_farm/locate_farm = locate() in get_turf(atom_parent) + if(locate_farm) + qdel(locate_farm) + +/obj/structure/simple_farm + name = "simple farm" + desc = "A small little plant that has adapted to the surrounding environment." + //it needs to be able to be walked through + density = FALSE + //it should not be pulled by anything + anchored = TRUE + ///the atom the farm is attached to + var/atom/attached_atom + ///the seed that is held within + var/obj/item/seeds/planted_seed + ///the max amount harvested from the plants + var/max_harvest = 3 + ///the cooldown amount between each harvest + var/harvest_cooldown = 1 MINUTES + //the cooldown between each harvest + COOLDOWN_DECLARE(harvest_timer) + +/obj/structure/simple_farm/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + COOLDOWN_START(src, harvest_timer, harvest_cooldown) + +/obj/structure/simple_farm/Destroy() + STOP_PROCESSING(SSobj, src) + + if(planted_seed) + planted_seed.forceMove(get_turf(src)) + planted_seed = null + + if(attached_atom) + if(ismovable(attached_atom)) + UnregisterSignal(attached_atom, COMSIG_MOVABLE_MOVED) + + attached_atom = null + + return ..() + +/obj/structure/simple_farm/examine(mob/user) + . = ..() + . += span_notice("
[src] will be ready for harvest in [DisplayTimeText(COOLDOWN_TIMELEFT(src, harvest_timer))]") + if(max_harvest < 6) + . += span_notice("You can use goliath hides or worm fertilizer to increase the amount dropped per harvest!") + if(harvest_cooldown > 30 SECONDS) + . += span_notice("
You can use sinew or worm fertilizer to lower the time between each harvest!") + +/obj/structure/simple_farm/process(seconds_per_tick) + update_appearance() + +/obj/structure/simple_farm/update_appearance(updates) + if(!planted_seed) + return + + icon = planted_seed.growing_icon + + if(COOLDOWN_FINISHED(src, harvest_timer)) + if(planted_seed.icon_harvest) + icon_state = planted_seed.icon_harvest + + else + icon_state = "[planted_seed.icon_grow][planted_seed.growthstages]" + + name = LOWER_TEXT(planted_seed.plantname) + + else + icon_state = "[planted_seed.icon_grow]1" + name = LOWER_TEXT("harvested [planted_seed.plantname]") + + return ..() + +/obj/structure/simple_farm/attack_hand(mob/living/user, list/modifiers) + if(!COOLDOWN_FINISHED(src, harvest_timer)) + balloon_alert(user, "plant not ready for harvest!") + return + + COOLDOWN_START(src, harvest_timer, harvest_cooldown) + create_harvest() + update_appearance() + return ..() + +/obj/structure/simple_farm/attackby(obj/item/attacking_item, mob/user, params) + //if its a shovel or knife, dismantle + if(attacking_item.tool_behaviour == TOOL_SHOVEL || attacking_item.tool_behaviour == TOOL_KNIFE) + var/turf/src_turf = get_turf(src) + src_turf.balloon_alert_to_viewers("the plant crumbles!") + Destroy() + return + + if(istype(attacking_item, /obj/item/storage/bag/plants)) + if(!COOLDOWN_FINISHED(src, harvest_timer)) + return + + COOLDOWN_START(src, harvest_timer, harvest_cooldown) + create_harvest(attacking_item, user) + update_appearance() + return + + var/obj/item/stack/use_item = attacking_item + if(istype(use_item) && !use_item.tool_use_check(1)) + return + + //if its sinew, lower the cooldown + if(istype(use_item, /obj/item/stack/sheet/sinew)) + if(decrease_cooldown(user)) + use_item.use(1) + return + + //if its goliath hide, increase the amount dropped + if(istype(use_item, /obj/item/stack/sheet/animalhide/goliath_hide)) + if(increase_yield(user)) + use_item.use(1) + return + + if(istype(use_item, /obj/item/stack/worm_fertilizer)) + var/cooldown_improved = decrease_cooldown(user, silent = TRUE) + var/yield_improved = increase_yield(user, silent = TRUE) + if (cooldown_improved || yield_improved) + use_item.use(1) + balloon_alert(user, "fertilized") + else + balloon_alert(user, "already fertilized!") + return + + + return ..() + +/** + * a proc that will increase the amount of items the crop could produce (at a maximum of 6, from base of 3) + */ +/obj/structure/simple_farm/proc/increase_yield(mob/user, var/silent = FALSE) + if(max_harvest >= 6) + if(!silent) + balloon_alert(user, "plant is at maximum yield") + + return FALSE + + max_harvest++ + + if(!silent) + balloon_alert_to_viewers("plant will have increased yield") + + return TRUE + +/** + * a proc that will decrease the amount of time it takes to be ready for harvest (at a maximum of 30 seconds, from a base of 1 minute) + */ +/obj/structure/simple_farm/proc/decrease_cooldown(mob/user, var/silent = FALSE) + if(harvest_cooldown <= 30 SECONDS) + if(!silent) + balloon_alert(user, "already at maximum growth speed!") + + return FALSE + + var/timeleft_percent = COOLDOWN_TIMELEFT(src, harvest_timer) / harvest_cooldown + harvest_cooldown -= 10 SECONDS + COOLDOWN_START(src, harvest_timer, harvest_cooldown * timeleft_percent) + + if(!silent) + balloon_alert_to_viewers("plant will grow faster") + + return TRUE + +/** + * used during the component so that it can move when it's attached atom moves + */ +/obj/structure/simple_farm/proc/late_setup() + if(!ismovable(attached_atom)) + return + RegisterSignal(attached_atom, COMSIG_MOVABLE_MOVED, PROC_REF(move_plant)) + +/** + * a simple proc to forcemove the plant on top of the movable atom its attached to + */ +/obj/structure/simple_farm/proc/move_plant() + forceMove(get_turf(attached_atom)) + +/** + * will create a harvest of the seeds product, with a chance to create a mutated version + */ +/obj/structure/simple_farm/proc/create_harvest(var/obj/item/storage/bag/plants/plant_bag, var/mob/user) + if(!planted_seed) + return + + for(var/i in 1 to rand(1, max_harvest)) + var/obj/creating_obj + + if(prob(15) && length(planted_seed.mutatelist)) + var/obj/item/seeds/choose_seed = pick(planted_seed.mutatelist) + creating_obj = initial(choose_seed.product) + + if(!creating_obj) + creating_obj = choose_seed + + var/created_special = new creating_obj(get_turf(src)) + + plant_bag?.atom_storage?.attempt_insert(created_special, user, TRUE) + + balloon_alert_to_viewers("something special drops!") + continue + + creating_obj = planted_seed.product + + if(!creating_obj) + creating_obj = planted_seed.type + + var/created_harvest = new creating_obj(get_turf(src)) + + plant_bag?.atom_storage?.attempt_insert(created_harvest, user, TRUE) + +/turf/open/misc/asteroid/basalt/getDug() + . = ..() + AddComponent(/datum/component/simple_farm) + +/turf/open/misc/asteroid/basalt/refill_dug() + . = ..() + qdel(GetComponent(/datum/component/simple_farm)) + +/turf/open/misc/asteroid/snow/getDug() + . = ..() + AddComponent(/datum/component/simple_farm) + +/turf/open/misc/asteroid/snow/refill_dug() + . = ..() + qdel(GetComponent(/datum/component/simple_farm)) diff --git a/modular_doppler/hearthkin/primitive_production/code/glassblowing.dm b/modular_doppler/hearthkin/primitive_production/code/glassblowing.dm new file mode 100644 index 0000000000000..261f5ba411eab --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/glassblowing.dm @@ -0,0 +1,540 @@ +#define DEFAULT_TIMED (4 SECONDS) +#define STEP_BLOW "blow" +#define STEP_SPIN "spin" +#define STEP_PADDLE "paddle" +#define STEP_SHEAR "shear" +#define STEP_JACKS "jacks" + +/obj/item/glassblowing + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + +/obj/item/glassblowing/glass_globe + name = "glass globe" + desc = "A glass bowl that is capable of carrying things." + icon_state = "glass_globe" + material_flags = MATERIAL_COLOR + custom_materials = list( + /datum/material/glass = HALF_SHEET_MATERIAL_AMOUNT, + ) + +/datum/export/glassblowing + cost = CARGO_CRATE_VALUE * 5 + unit_name = "glassblowing product" + export_types = list( + /obj/item/glassblowing/glass_lens, + /obj/item/glassblowing/glass_globe, + /obj/item/reagent_containers/cup/bowl/blowing_glass, + /obj/item/reagent_containers/cup/beaker/large/blowing_glass, + /obj/item/plate/blowing_glass + ) + +/datum/export/glassblowing/sell_object(obj/O, datum/export_report/report, dry_run, apply_elastic = FALSE) //I really dont want them to feel gimped + return ..() + +/obj/item/glassblowing/glass_lens + name = "glass lens" + desc = "A convex glass lens that would make an excellent magnifying glass if it were attached to a handle." + icon_state = "glass_lens" + +/obj/item/reagent_containers/cup/bowl/blowing_glass + name = "glass bowl" + desc = "A glass bowl that is capable of carrying things." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "glass_bowl" + custom_materials = list(/datum/material/glass=SHEET_MATERIAL_AMOUNT) + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + +/obj/item/reagent_containers/cup/beaker/large/blowing_glass + name = "glass cup" + desc = "A glass cup that is capable of carrying liquids." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "glass_cup" + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + +/obj/item/plate/blowing_glass + name = "glass plate" + desc = "A glass plate that is capable of carrying things." + icon = 'modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi' + icon_state = "glass_plate" + custom_materials = list(/datum/material/glass=SHEET_MATERIAL_AMOUNT) + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + +/obj/item/glassblowing/molten_glass + name = "molten glass" + desc = "A glob of molten glass, ready to be shaped into art." + icon_state = "molten_glass" + ///the cooldown if it's still molten / requires heating up + COOLDOWN_DECLARE(remaining_heat) + ///the typepath of the item that will be produced when the required actions are met + var/chosen_item + ///the list of steps remaining + var/list/steps_remaining + ///the amount of time this glass will stay heated, updated each time it gets put in the forge based on the user's skill + var/total_time + ///whether this glass's chosen item has completed all its steps. So we don't have to keep checking this a million times once it's done. + var/is_finished = FALSE + +/obj/item/glassblowing/molten_glass/examine(mob/user) + . = ..() + var/message = get_examine_message(src) + if(message) + . += message + +/obj/item/glassblowing/molten_glass/pickup(mob/living/user) + if(!istype(user)) + return ..() + + . = ..() + + try_burn_user(user) + +/** + * Tries to burn the user if the glass is still molten hot. + * + * Arguments: + * * mob/living/user - user to burn + */ +/obj/item/glassblowing/molten_glass/proc/try_burn_user(mob/living/user) + if(!COOLDOWN_FINISHED(src, remaining_heat)) + to_chat(user, span_warning("You burn your hands trying to pick up [src]!")) + user.emote("scream") + user.dropItemToGround(src) + var/obj/item/bodypart/affecting = user.get_active_hand() + user.investigate_log("was burned their hand on [src] for [15] at [AREACOORD(user)]", INVESTIGATE_CRAFTING) + return affecting?.receive_damage(0, 15, wound_bonus = CANT_WOUND) + +/obj/item/glassblowing/blowing_rod + name = "blowing rod" + desc = "A tool that is used to hold the molten glass as well as help shape it." + icon_state = "blow_pipe_empty" + tool_behaviour = TOOL_BLOWROD + /// Whether the rod is in use currently; will try to prevent many other actions on it + var/in_use = FALSE + /// A ref to the glass item being blown + var/datum/weakref/glass_ref + +/obj/item/glassblowing/blowing_rod/examine(mob/user) + . = ..() + var/obj/item/glassblowing/molten_glass/glass = glass_ref.resolve() + if(!glass) + return + var/message = get_examine_message(glass) + if(message) + . += message + + +/** + * Create the examine message and return it. + * + * This will include all the remaining steps and whether the glass has cooled down or not. + * + * Arguments: + * * obj/item/glassblowing/molten_glass/glass - the glass object being examined + * + * Returns the examine message. + */ +/obj/item/glassblowing/proc/get_examine_message(obj/item/glassblowing/molten_glass/glass) + if(COOLDOWN_FINISHED(glass, remaining_heat)) + . += span_warning("The glass has cooled down and will require reheating to modify! ") + if(!length(glass.steps_remaining)) + return + if(glass.steps_remaining[STEP_BLOW]) + . += "The glass requires [glass.steps_remaining[STEP_BLOW]] more blowing actions! " + if(glass.steps_remaining[STEP_SPIN]) + . += "The glass requires [glass.steps_remaining[STEP_SPIN]] more spinning actions! " + if(glass.steps_remaining[STEP_PADDLE]) + . += "The glass requires [glass.steps_remaining[STEP_PADDLE]] more paddling actions! " + if(glass.steps_remaining[STEP_SHEAR]) + . += "The glass requires [glass.steps_remaining[STEP_SHEAR]] more shearing actions! " + if(glass.steps_remaining[STEP_JACKS]) + . += "The glass requires [glass.steps_remaining[STEP_JACKS]] more jacking actions!" + +/obj/item/glassblowing/blowing_rod/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + var/obj/item/glassblowing/molten_glass/attacking_glass = interacting_with + if(!istype(attacking_glass)) + return NONE + + if(glass_ref?.resolve()) + to_chat(user, span_warning("[src] already has some glass on it!")) + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(attacking_glass, src)) + return ITEM_INTERACT_BLOCKING + glass_ref = WEAKREF(attacking_glass) + to_chat(user, span_notice("[src] picks up [attacking_glass].")) + icon_state = "blow_pipe_full" + return ITEM_INTERACT_SUCCESS + +/obj/item/glassblowing/blowing_rod/attackby(obj/item/attacking_item, mob/living/user, params) + var/actioning_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * DEFAULT_TIMED + var/obj/item/glassblowing/molten_glass/glass = glass_ref?.resolve() + + if(istype(attacking_item, /obj/item/glassblowing/molten_glass)) + if(glass) + to_chat(user, span_warning("[src] already has some glass on it still!")) + return + if(!user.transferItemToLoc(attacking_item, src)) + return + glass_ref = WEAKREF(attacking_item) + to_chat(user, span_notice("[src] picks up [attacking_item].")) + icon_state = "blow_pipe_full" + return + + if(istype(attacking_item, /obj/item/glassblowing/paddle)) + do_glass_step(STEP_PADDLE, user, actioning_speed, glass) + return + + if(istype(attacking_item, /obj/item/glassblowing/shears)) + do_glass_step(STEP_SHEAR, user, actioning_speed, glass) + return + + if(istype(attacking_item, /obj/item/glassblowing/jacks)) + do_glass_step(STEP_JACKS, user, actioning_speed, glass) + return + + return ..() + +/obj/item/glassblowing/blowing_rod/attack_self(mob/user, modifiers) + return ..() + +/obj/item/glassblowing/blowing_rod/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "GlassBlowing", name) + ui.open() + +/obj/item/glassblowing/blowing_rod/ui_data() + var/obj/item/glassblowing/molten_glass/glass = glass_ref?.resolve() + + var/data = list() + data["inUse"] = in_use + + if(glass) + data["glass"] = list( + timeLeft = COOLDOWN_TIMELEFT(glass, remaining_heat), + totalTime = glass.total_time, + chosenItem = null, + stepsRemaining = glass.steps_remaining, + isFinished = glass.is_finished + ) + + var/obj/item_path = glass.chosen_item + data["glass"]["chosenItem"] = item_path ? list(name = initial(item_path.name), type = item_path) : null + else + data["glass"] = null + + return data + +/obj/item/glassblowing/blowing_rod/ui_act(action, params) + . = ..() + if(.) + return + if(!Adjacent(usr)) + return + add_fingerprint(usr) + + var/obj/item/glassblowing/molten_glass/glass = glass_ref?.resolve() + var/actioning_speed = usr.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * DEFAULT_TIMED + + if(!glass) + return + + if(action == "Remove") + if(!glass.chosen_item) + remove_glass(usr, glass) + in_use = FALSE + return + + if(glass.is_finished) + create_item(usr, glass) + in_use = FALSE + else + remove_glass(usr, glass) + return + + if(!glass.chosen_item) + switch(action) + if("Plate") + glass.chosen_item = /obj/item/plate/blowing_glass + glass.steps_remaining = list(blow=3,spin=3,paddle=3,shear=0,jacks=0) //blowing, spinning, paddling + if("Bowl") + glass.chosen_item = /obj/item/reagent_containers/cup/bowl/blowing_glass + glass.steps_remaining = list(blow=2,spin=2,paddle=2,shear=0,jacks=3) //blowing, spinning, paddling + if("Globe") + glass.chosen_item = /obj/item/glassblowing/glass_globe + glass.steps_remaining = list(blow=6,spin=3,paddle=0,shear=0,jacks=0) //blowing, spinning + if("Cup") + glass.chosen_item = /obj/item/reagent_containers/cup/beaker/large/blowing_glass + glass.steps_remaining = list(blow=3,spin=3,paddle=3,shear=0,jacks=0) //blowing, spinning, paddling + if("Lens") + glass.chosen_item = /obj/item/glassblowing/glass_lens + glass.steps_remaining = list(blow=0,spin=0,paddle=3,shear=3,jacks=3) //paddling, shearing, jacking + if("Bottle") + glass.chosen_item = /obj/item/reagent_containers/cup/glass/bottle/small + glass.steps_remaining = list(blow=3,spin=2,paddle=3,shear=0,jacks=0) //blowing, spinning, paddling + else + switch(action) + if("Blow") + do_glass_step(STEP_BLOW, usr, actioning_speed, glass) + if("Spin") + do_glass_step(STEP_SPIN, usr, actioning_speed, glass) + if("Paddle") + do_glass_step(STEP_PADDLE, usr, actioning_speed, glass) + if("Shear") + do_glass_step(STEP_SHEAR, usr, actioning_speed, glass) + if("Jacks") + do_glass_step(STEP_JACKS, usr, actioning_speed, glass) + if("Cancel") + glass.chosen_item = null + glass.steps_remaining = null + glass.is_finished = FALSE + to_chat(usr, span_notice("You start over with the [src].")) + + +/** + * Removes the glass object from the rod. + * + * Try to put the glass into the user's hands, or on the floor if that fails. + * + * Arguments: + * * mob/user - the mob doing the removing + * * obj/item/glassblowing/molten_glass/glass - the glass object + * + * Returns TRUE or FALSE. + */ +/obj/item/glassblowing/blowing_rod/proc/remove_glass(mob/user, obj/item/glassblowing/molten_glass/glass) + if(!glass) + return + + in_use = FALSE + user.put_in_hands(glass) + glass.try_burn_user(user) + glass_ref = null + icon_state = "blow_pipe_empty" + +/** + * Creates the finished product and delete the glass object used to make it. + * + * Try to put the finished product into the user's hands + * + * Arguments: + * * mob/user - the user doing the creating + * * obj/item/glassblowing/molten_glass/glass - the glass object + * + * Returns TRUE or FALSE. + */ +/obj/item/glassblowing/blowing_rod/proc/create_item(mob/user, obj/item/glassblowing/molten_glass/glass) + if(!glass) + return + if(in_use) + return + + in_use = TRUE + user.put_in_hands(new glass.chosen_item) + user.mind.adjust_experience(/datum/skill/production, 30) + glass_ref = null + qdel(glass) + icon_state = "blow_pipe_empty" + return + +/** + * Display fail message and reset in_use. + * + * Craft is finished when all steps in steps_remaining are 0. + * + * Arguments: + * * mob/user - mob to display to + * * message - to display + */ +/obj/item/glassblowing/blowing_rod/proc/fail_message(message, mob/user) + to_chat(user, span_warning(message)) + in_use = FALSE + +/** + * Try to do a glassblowing action. + * + * Checks for a table and valid tool if applicable, and updates the steps_remaining on the glass object. + * + * Arguments: + * * step_id - the step id e.g. STEP_BLOW + * * actioning_speed - the speed based on the user's production skill + * * obj/item/glassblowing/molten_glass/glass - the glass object + */ +/obj/item/glassblowing/blowing_rod/proc/do_glass_step(step_id, mob/user, actioning_speed, obj/item/glassblowing/molten_glass/glass) + if(!glass) + return + + if(COOLDOWN_FINISHED(glass, remaining_heat)) + balloon_alert(user, "glass too cool!") + return FALSE + + if(in_use) + return + + in_use = TRUE + + if(!check_valid_table(user)) + fail_message("You must be near a non-flammable table!", user) + return + + var/atom/movable/tool_to_use = check_valid_tool(user, step_id) + if(!tool_to_use) + in_use = FALSE + return FALSE + + to_chat(user, span_notice("You begin to [step_id] [src].")) + if(!do_after(user, actioning_speed, target = src)) + fail_message("You interrupt an action!", user) + REMOVE_TRAIT(tool_to_use, TRAIT_CURRENTLY_GLASSBLOWING, TRAIT_GLASSBLOWING) + return FALSE + + if(glass.steps_remaining) + // We do not want to have negative values here + if(glass.steps_remaining[step_id] > 0) + glass.steps_remaining[step_id]-- + if(check_finished(glass)) + glass.is_finished = TRUE + + REMOVE_TRAIT(tool_to_use, TRAIT_CURRENTLY_GLASSBLOWING, TRAIT_GLASSBLOWING) + in_use = FALSE + + to_chat(user, span_notice("You finish trying to [step_id] [src].")) + user.mind.adjust_experience(/datum/skill/production, 10) + + +/** + * Check if there is a non-flammable table nearby to do the crafting on. + * + * If the user is a master in the production skill, they can skip tables. + * + * Arguments: + * * mob/living/user - the mob doing the action + * + * Returns TRUE or FALSE. + */ +/obj/item/glassblowing/blowing_rod/proc/check_valid_table(mob/living/user) + var/skill_level = user.mind.get_skill_level(/datum/skill/production) + if(skill_level >= SKILL_LEVEL_MASTER) // + return TRUE + for(var/obj/structure/table/check_table in range(1, get_turf(src))) + if(!(check_table.resistance_flags & FLAMMABLE)) + return TRUE + return FALSE + +/** + * Check if user is carrying the proper tool for the step. + * + * Arguments: + * * mob/living/carbon/human/user - the mob doing the action + * * step_id - the step id of the action being done + * + * We check to see if the user is using the right tool and if they are currently glassblowing with it. + * If the correct tool is being used we return the tool. Otherwise we return `FALSE` + */ +/obj/item/glassblowing/blowing_rod/proc/check_valid_tool(mob/living/carbon/human/user, step_id) + if(!istype(user)) + return FALSE + + if(step_id == STEP_BLOW || step_id == STEP_SPIN) + if(HAS_TRAIT(user, TRAIT_CURRENTLY_GLASSBLOWING)) + balloon_alert(user, "already glassblowing!") + return FALSE + + ADD_TRAIT(user, TRAIT_CURRENTLY_GLASSBLOWING, TRAIT_GLASSBLOWING) + return user + + var/obj/item/glassblowing/used_tool + switch(step_id) + if(STEP_PADDLE) + used_tool = user.is_holding_item_of_type(/obj/item/glassblowing/paddle) + if(STEP_SHEAR) + used_tool = user.is_holding_item_of_type(/obj/item/glassblowing/shears) + if(STEP_JACKS) + used_tool = user.is_holding_item_of_type(/obj/item/glassblowing/jacks) + + if(!used_tool) + balloon_alert(user, "need the right tool!") + return FALSE + + if(HAS_TRAIT(used_tool, TRAIT_CURRENTLY_GLASSBLOWING)) + balloon_alert(user, "already in use!") + return FALSE + + ADD_TRAIT(used_tool, TRAIT_CURRENTLY_GLASSBLOWING, TRAIT_GLASSBLOWING) + return used_tool + +/** + * Checks if the glass is ready to craft into its chosen item. + * + * Craft is finished when all steps in steps_remaining are 0. + * + * Arguments: + * * obj/item/glassblowing/molten_glass/glass - the glass object + * + * Returns TRUE or FALSE. + */ +/obj/item/glassblowing/blowing_rod/proc/check_finished(obj/item/glassblowing/molten_glass/glass) + for(var/step_id in glass.steps_remaining) + if(glass.steps_remaining[step_id] != 0) + return FALSE + return TRUE + +/datum/crafting_recipe/glassblowing_recipe + reqs = list(/obj/item/stack/sheet/iron = 5) + category = CAT_MISC + +/datum/crafting_recipe/glassblowing_recipe/glass_blowing_rod + name = "Glass-blowing Blowing Rod" + result = /obj/item/glassblowing/blowing_rod + +/obj/item/glassblowing/jacks + name = "jacks" + desc = "A tool that helps shape glass during the art process." + icon_state = "jacks" + +/datum/crafting_recipe/glassblowing_recipe/glass_jack + name = "Glass-blowing Jacks" + result = /obj/item/glassblowing/jacks + +/obj/item/glassblowing/paddle + name = "paddle" + desc = "A tool that helps shape glass during the art process." + icon_state = "paddle" + +/datum/crafting_recipe/glassblowing_recipe/glass_paddle + name = "Glass-blowing Paddle" + result = /obj/item/glassblowing/paddle + +/obj/item/glassblowing/shears + name = "shears" + desc = "A tool that helps shape glass during the art process." + icon_state = "shears" + +/datum/crafting_recipe/glassblowing_recipe/glass_shears + name = "Glass-blowing Shears" + result = /obj/item/glassblowing/shears + +/obj/item/glassblowing/metal_cup + name = "metal cup" + desc = "A tool that helps shape glass during the art process." + icon_state = "metal_cup_empty" + var/has_sand = FALSE + +/datum/crafting_recipe/glassblowing_recipe/glass_metal_cup + name = "Glass-blowing Metal Cup" + result = /obj/item/glassblowing/metal_cup + +/obj/item/glassblowing/metal_cup/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/glass_obj = I + if(!glass_obj.use(1)) + return + has_sand = TRUE + icon_state = "metal_cup_full" + return ..() + +#undef DEFAULT_TIMED +#undef STEP_BLOW +#undef STEP_SPIN +#undef STEP_PADDLE +#undef STEP_SHEAR +#undef STEP_JACKS diff --git a/modular_doppler/hearthkin/primitive_production/code/misc.dm b/modular_doppler/hearthkin/primitive_production/code/misc.dm new file mode 100644 index 0000000000000..18bcafa8664e9 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/misc.dm @@ -0,0 +1,35 @@ +/obj/item/shard/attackby(obj/item/item, mob/user, params) + //xenoarch hammer, forging hammer, etc. + if(item.tool_behaviour == TOOL_HAMMER) + var/added_color + switch(src.type) + if(/obj/item/shard) + added_color = "#88cdf1" + + if(/obj/item/shard/plasma) + added_color = "#ff80f4" + + if(/obj/item/shard/plastitanium) + added_color = "#5d3369" + + if(/obj/item/shard/titanium) + added_color = "#cfbee0" + + var/obj/colored_item = new /obj/item/stack/ore/glass/zero_cost(get_turf(src)) + colored_item.add_atom_colour(added_color, FIXED_COLOUR_PRIORITY) + new /obj/effect/decal/cleanable/glass(get_turf(src)) + user.balloon_alert(user, "[src] shatters!") + playsound(src, SFX_SHATTER, 30, TRUE) + qdel(src) + return TRUE + + return ..() + +/obj/item/stack/ore/glass/zero_cost + points = 0 + merge_type = /obj/item/stack/ore/glass/zero_cost + +/obj/item/stack/ore/examine(mob/user) + . = ..() + if(points == 0) + . += span_warning("
[src] is worthless and will not reward any mining points!") diff --git a/modular_doppler/hearthkin/primitive_production/code/production_skill.dm b/modular_doppler/hearthkin/primitive_production/code/production_skill.dm new file mode 100644 index 0000000000000..b591706ac86b3 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/production_skill.dm @@ -0,0 +1,18 @@ +/datum/skill/production + name = "Production" + title = "Producer" + desc = "The artist who finds themselves using multiple mediums in which to express their creativity." + modifiers = list( + SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5), + SKILL_PROBS_MODIFIER = list(10, 15, 20, 25, 30, 35, 40) + ) + skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/production + +/obj/item/clothing/neck/cloak/skill_reward/production + name = "legendary producer's cloak" + desc = "Worn by the most skilled producers, this legendary cloak is only attainable by knowing how to create the best products. \ + This status symbol represents a being who has crafted some of the finest glass and ceramic works." + icon = 'modular_doppler/hearthkin/primitive_production/icons/cloaks.dmi' + worn_icon = 'modular_doppler/hearthkin/primitive_production/icons/neck.dmi' + icon_state = "productioncloak" + associated_skill_path = /datum/skill/production diff --git a/modular_doppler/hearthkin/primitive_production/code/wormfarm.dm b/modular_doppler/hearthkin/primitive_production/code/wormfarm.dm new file mode 100644 index 0000000000000..4fa88b9a4a1d5 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/code/wormfarm.dm @@ -0,0 +1,139 @@ +/obj/structure/wormfarm + name = "worm farm" + desc = "A wonderfully dirty barrel where worms can have a happy little life." + icon = 'modular_doppler/hearthkin/primitive_production/icons/structures.dmi' + icon_state = "wormbarrel" + density = TRUE + anchored = FALSE + /// How many worms can the barrel hold + var/max_worm = 10 + /// How many worms the barrel is currently holding + var/current_worm = 0 + /// How much food was inserted into the barrel that needs to be composted + var/current_food = 0 + /// If the barrel is currently being used by someone + var/in_use = FALSE + // The cooldown between each worm "breeding" + COOLDOWN_DECLARE(worm_timer) + +/obj/structure/wormfarm/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + COOLDOWN_START(src, worm_timer, 30 SECONDS) + +/obj/structure/wormfarm/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +//process is currently only used for making more worms +/obj/structure/wormfarm/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, worm_timer)) + return + + COOLDOWN_START(src, worm_timer, 30 SECONDS) + + if(current_worm >= 2 && current_worm < max_worm) + current_worm++ + + if(current_food > 0 && current_worm > 1) + current_food-- + new /obj/item/stack/worm_fertilizer(get_turf(src)) + +/obj/structure/wormfarm/examine(mob/user) + . = ..() + . += span_notice("
There are currently [current_worm]/[max_worm] worms in the barrel.") + if(current_worm < max_worm) + . += span_notice("You can place more worms in the barrel.") + if(current_worm > 0) + . += span_notice("You can get fertilizer by feeding the worms food.") + +/obj/structure/wormfarm/attack_hand(mob/living/user, list/modifiers) + if(in_use) + balloon_alert(user, "currently in use") + return ..() + + balloon_alert(user, "digging up worms") + if(!do_after(user, 2 SECONDS, src)) + balloon_alert(user, "stopped digging") + in_use = FALSE + return ..() + + if(current_worm <= 0) + balloon_alert(user, "no worms available") + in_use = FALSE + return ..() + + new /obj/item/food/bait/worm(get_turf(src)) + current_worm-- + in_use = FALSE + + return ..() + +/obj/structure/wormfarm/attackby(obj/item/attacking_item, mob/user, params) + //we want to check for worms first because they are a type of food as well... + if(istype(attacking_item, /obj/item/food/bait/worm)) + if(current_worm >= max_worm) + balloon_alert(user, "too many worms in the barrel") + return + + qdel(attacking_item) + balloon_alert(user, "worm released into barrel") + current_worm++ + return + + //if it aint a worm, lets check for any other food items + if(istype(attacking_item, /obj/item/food)) + if(in_use) + balloon_alert(user, "currently in use") + return + in_use = TRUE + + balloon_alert(user, "feeding the worms") + if(!do_after(user, 1 SECONDS, src)) + balloon_alert(user, "stopped feeding the worms") + in_use = FALSE + return + + // if someone has built multiple worm farms, I want to make sure they can't just use one singular piece of food for more than one barrel + if(!attacking_item) + in_use = FALSE + return + + qdel(attacking_item) + balloon_alert(user, "feeding complete, check back later") + + current_food++ + + in_use = FALSE + return + + if(istype(attacking_item, /obj/item/storage/bag/plants)) + if(in_use) + balloon_alert(user, "currently in use") + return + in_use = TRUE + + balloon_alert(user, "feeding the worms") + for(var/obj/item/food/selected_food in attacking_item.contents) + if(!do_after(user, 1 SECONDS, src)) + in_use = FALSE + return + + qdel(selected_food) + current_food++ + + in_use = FALSE + return + + //it wasn't a worm, or a piece of food + return ..() + +//produced by feeding worms food and can be ground up for plant nutriment or used directly on ash farming +/obj/item/stack/worm_fertilizer + name = "worm fertilizer" + desc = "When you fed your worms, you should have expected this." + icon = 'modular_doppler/hearthkin/primitive_production/icons/misc.dmi' + icon_state = "fertilizer" + grind_results = list(/datum/reagent/plantnutriment/eznutriment = 3, /datum/reagent/plantnutriment/left4zednutriment = 3, /datum/reagent/plantnutriment/robustharvestnutriment = 3) + singular_name = "fertilizer" + merge_type = /obj/item/stack/worm_fertilizer diff --git a/modular_doppler/hearthkin/primitive_production/icons/cloaks.dmi b/modular_doppler/hearthkin/primitive_production/icons/cloaks.dmi new file mode 100644 index 0000000000000..b4467ca916ddc Binary files /dev/null and b/modular_doppler/hearthkin/primitive_production/icons/cloaks.dmi differ diff --git a/modular_doppler/hearthkin/primitive_production/icons/misc.dmi b/modular_doppler/hearthkin/primitive_production/icons/misc.dmi new file mode 100644 index 0000000000000..b3e5b8812eb3c Binary files /dev/null and b/modular_doppler/hearthkin/primitive_production/icons/misc.dmi differ diff --git a/modular_doppler/hearthkin/primitive_production/icons/neck.dmi b/modular_doppler/hearthkin/primitive_production/icons/neck.dmi new file mode 100644 index 0000000000000..502db224f735d Binary files /dev/null and b/modular_doppler/hearthkin/primitive_production/icons/neck.dmi differ diff --git a/modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi b/modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi new file mode 100644 index 0000000000000..1caf775d6829a Binary files /dev/null and b/modular_doppler/hearthkin/primitive_production/icons/prim_fun.dmi differ diff --git a/modular_doppler/hearthkin/primitive_production/icons/structures.dmi b/modular_doppler/hearthkin/primitive_production/icons/structures.dmi new file mode 100644 index 0000000000000..0682447f8d302 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_production/icons/structures.dmi differ diff --git a/modular_doppler/hearthkin/primitive_production/readme.md b/modular_doppler/hearthkin/primitive_production/readme.md new file mode 100644 index 0000000000000..f105311083886 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_production/readme.md @@ -0,0 +1,30 @@ +## Title: Primitive Production +MODULE ID: PRIMITIVE_PRODUCTION + +### Description: + +Adds a variety of 'primitive' ways to produce items +Antfarming, wormfarming and the normal kind of farming were added as well. + +### TG Proc/File Changes: + +| proc | file | +| --------------------------------------------------------------------- | ----------------------------------- | +| `/atom/proc/tool_act(mob/living/user, obj/item/tool, list/modifiers)` | `code\game\atom\atom_tool_acts.dm` | + +### Defines: + +- `code\__DEFINES\~doppler_defines\reagent_forging_tools.dm` glassblowing tools' define +- `code\__DEFINES\~doppler_defines\traits.dm` trait for glassblowing + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `modular_doppler\modular_crafting\code\sheet_types.dm` crafting recipes + +### Credits: + +Jake Park diff --git a/modular_doppler/hearthkin/primitive_structures/code/fencing.dm b/modular_doppler/hearthkin/primitive_structures/code/fencing.dm new file mode 100644 index 0000000000000..ec6d83cb34b8d --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/fencing.dm @@ -0,0 +1,109 @@ +// Short wooden fences, oh me oh my + +/obj/structure/railing/wooden_fencing + name = "wooden fence" + desc = "A basic wooden fence meant to prevent people like you either in or out of somewhere." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/wooden_fence.dmi' + icon_state = "fence" + resistance_flags = FLAMMABLE + flags_1 = ON_BORDER_1 + /// If we randomize our icon on spawning + var/random_icons = TRUE + +/obj/structure/railing/wooden_fencing/Initialize(mapload) + . = ..() + if(!random_icons) + return + icon_state = pick( + "fence", + "fence_2", + "fence_3", + ) + update_appearance() + +/obj/structure/railing/wooden_fencing/atom_deconstruct(disassembled) + var/obj/plank = new /obj/item/stack/sheet/mineral/wood(drop_location(), 5) + transfer_fingerprints_to(plank) + +// formerly NO_DECONSTRUCTION +/obj/structure/railing/wooden_fencing/wirecutter_act(mob/living/user, obj/item/tool) + return NONE + +/obj/structure/railing/wooden_fencing/crowbar_act(mob/living/user, obj/item/tool) + . = ..() + to_chat(user, span_warning("You pry apart the railing.")) + tool.play_tool_sound(src, 100) + deconstruct() + return TRUE + +// Fence gates for the above mentioned fences + +/obj/structure/railing/wooden_fencing/gate + name = "wooden fence gate" + desc = "A basic wooden gate meant to prevent animals like you escaping." + icon_state = "gate" + random_icons = FALSE + /// Has the gate been opened or not? + var/opened = FALSE + +/obj/structure/railing/wooden_fencing/gate/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) + return + return open_or_close(user) + +/// Proc that checks if the gate is open or not, then closes/opens the gate repsectively +/obj/structure/railing/wooden_fencing/gate/proc/open_or_close(mob/user) + if(!user.can_interact_with(src)) + balloon_alert(user, "can't interact") + return + opened = !opened + set_density(!opened) + icon_state = "[opened ? "gate_open" : "gate"]" + playsound(src, (opened ? 'sound/machines/wooden_closet_open.ogg' : 'sound/machines/wooden_closet_close.ogg'), 100, TRUE) + update_appearance() + +/obj/structure/railing/wooden_fencing/gate/update_icon() + . = ..() + if(!opened) + return + +// Large wooden gate, used for big doors or entrances to camps + +/obj/structure/mineral_door/wood/large_gate + name = "large wooden gate" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/wooden_gate.dmi' + icon_state = "gate" + openSound = 'sound/machines/wooden_closet_open.ogg' + closeSound = 'sound/machines/wooden_closet_close.ogg' + +/obj/structure/mineral_door/wood/large_gate/Open() + playsound(src, openSound, 100, TRUE) + set_opacity(FALSE) + set_density(FALSE) + door_opened = TRUE + layer = OPEN_DOOR_LAYER + air_update_turf(TRUE, FALSE) + update_appearance() + +/obj/structure/mineral_door/wood/large_gate/Close() + if(!door_opened) + return + for(var/mob/living/blocking_mob in get_turf(src)) + return + playsound(src, closeSound, 100, TRUE) + set_density(TRUE) + set_opacity(TRUE) + door_opened = FALSE + layer = initial(layer) + air_update_turf(TRUE, TRUE) + update_appearance() + +/obj/structure/mineral_door/wood/large_gate/update_icon() + . = ..() + if(!door_opened) + return + if(dir == EAST) + layer = ABOVE_MOB_LAYER + else + layer = initial(layer) diff --git a/modular_doppler/hearthkin/primitive_structures/code/fuelwell.dm b/modular_doppler/hearthkin/primitive_structures/code/fuelwell.dm new file mode 100644 index 0000000000000..c16548df576aa --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/fuelwell.dm @@ -0,0 +1,53 @@ +//CODE CREDIT TO JJPARK-KB +//Infinite welding fuel source, lets ashwalkers have infinite fuel without needing high-tech welders. + +/obj/structure/sink/fuel_well + name = "fuel well" + desc = "A bubbling pool of fuel. This would probably be valuable, had bluespace technology not destroyed the need for fossil fuels 200 years ago." + icon = 'icons/obj/watercloset.dmi' + icon_state = "puddle-oil" + dispensedreagent = /datum/reagent/fuel + color = "#742912" //Gives it a weldingfuel hue + +/obj/structure/sink/fuel_well/Initialize(mapload) + .=..() + create_reagents(20) + reagents.add_reagent(dispensedreagent, 20) + +/obj/structure/sink/fuel_well/attack_hand(mob/user, list/modifiers) + flick("puddle-oil-splash",src) + reagents.expose(user, TOUCH, 20) //Covers target in 20u of fuel. + to_chat(user, span_notice("You touch the pool of fuel, only to get fuel all over yourself. It would be wise to wash this off with water.")) + +/obj/structure/sink/fuel_well/attackby(obj/item/O, mob/living/user, params) + flick("puddle-oil-splash",src) + if(O.tool_behaviour == TOOL_SHOVEL) //attempt to deconstruct the puddle with a shovel + to_chat(user, "You fill in the fuel well with soil.") + O.play_tool_sound(src) + deconstruct() + return 1 + if(istype(O, /obj/item/reagent_containers)) //Refilling bottles with oil + var/obj/item/reagent_containers/RG = O + if(RG.is_refillable()) + if(!RG.reagents.holder_full()) + RG.reagents.add_reagent(dispensedreagent, min(RG.volume - RG.reagents.total_volume, RG.amount_per_transfer_from_this)) + to_chat(user, span_notice("You fill [RG] from [src].")) + return TRUE + to_chat(user, span_notice("\The [RG] is full.")) + return FALSE + if(O.tool_behaviour == TOOL_WELDER) + if(!reagents.has_reagent(/datum/reagent/fuel)) + to_chat(user, span_warning("[src] is out of fuel!")) + return + var/obj/item/weldingtool/W = O + if(istype(W) && !W.welding) + if(W.reagents.has_reagent(/datum/reagent/fuel, W.max_fuel)) + to_chat(user, span_warning("Your [W.name] is already full!")) + return + reagents.trans_to(W, W.max_fuel, transferred_by = user) + user.visible_message(span_notice("[user] refills [user.p_their()] [W.name]."), span_notice("You refill [W].")) + playsound(src, 'sound/effects/refill.ogg', 50, TRUE) + W.update_appearance() + return + else + return ..() diff --git a/modular_doppler/hearthkin/primitive_structures/code/furniture.dm b/modular_doppler/hearthkin/primitive_structures/code/furniture.dm new file mode 100644 index 0000000000000..92333fc129b87 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/furniture.dm @@ -0,0 +1,73 @@ +/obj/item/target/archery + name = "archery target" + desc = "A shooting target, specifically for bows." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/items_and_weapons.dmi' + icon_state = "archery_target" + // impact_sound = SFX_BULLET_IMPACT_WOOD + +/datum/crafting_recipe/archery_target + + name = "archery target" + category = CAT_FURNITURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF + + reqs = list( + /obj/item/stack/tile/grass/thatch = 4, + /obj/item/stack/sheet/mineral/wood = 4, + ) + + result = /obj/item/target/archery + +// Hearthkin Exclusive Beds +/obj/structure/bed/double/pelt + name = "white pelts bed" + desc = "A luxurious double bed, made with white wolf pelts." + icon_state = "pelt_bed_white" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/tribal_beds.dmi' + anchored = TRUE + can_buckle = TRUE + buckle_lying = 90 + resistance_flags = FLAMMABLE + max_integrity = 100 + integrity_failure = 0.35 + max_buckled_mobs = 2 + /// What material this bed is made of + build_stack_type = /obj/item/stack/sheet/sinew/wolf + /// How many mats to drop when deconstructed + build_stack_amount = 4 + +/obj/structure/bed/double/pelt/atom_deconstruct(disassembled = TRUE) + . = ..() + new /obj/item/stack/sheet/mineral/wood(loc, build_stack_amount) + +/datum/crafting_recipe/white_pelt_bed + name = "White Pelts Bed" + category = CAT_FURNITURE + //recipe given to icecats as part of their spawner/team setting + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND + + reqs = list( + /obj/item/stack/sheet/sinew/wolf = 4, + /obj/item/stack/sheet/mineral/wood = 4, + ) + + result = /obj/structure/bed/double/pelt + +/obj/structure/bed/double/pelt/black + name = "black pelts bed" + desc = "A luxurious double bed, made with black wolf pelts." + icon_state = "pelt_bed_black" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/tribal_beds.dmi' + +/datum/crafting_recipe/black_pelt_bed + name = "Black Pelts Bed" + category = CAT_FURNITURE + //recipe given to icecats as part of their spawner/team setting + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND + + reqs = list( + /obj/item/stack/sheet/sinew/wolf = 4, + /obj/item/stack/sheet/mineral/wood = 4, + ) + + result = /obj/structure/bed/double/pelt/black diff --git a/modular_doppler/hearthkin/primitive_structures/code/railroad.dm b/modular_doppler/hearthkin/primitive_structures/code/railroad.dm new file mode 100644 index 0000000000000..3ee15cccb85f0 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/railroad.dm @@ -0,0 +1,161 @@ +/obj/item/stack/rail_track + name = "railroad tracks" + singular_name = "railroad track" + desc = "A primitive form of transportation. Place on any floor to start building a railroad." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "rail_item" + merge_type = /obj/item/stack/rail_track + +/obj/item/stack/rail_track/ten + amount = 10 + +/obj/item/stack/rail_track/fifty + amount = 50 + +/obj/item/stack/rail_track/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(!isopenturf(interacting_with)) + return NONE + var/turf/open/target_turf = get_turf(interacting_with) + var/obj/structure/railroad/check_rail = locate() in target_turf + if(check_rail || !use(1)) + return NONE + to_chat(user, span_notice("You place [src] on [target_turf].")) + new /obj/structure/railroad(target_turf) + return ITEM_INTERACT_SUCCESS + +/obj/structure/railroad + name = "railroad track" + desc = "A primitive form of transportation. You may see some rail carts on it." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "rail" + anchored = TRUE + +/obj/structure/railroad/Initialize(mapload) + . = ..() + for(var/obj/structure/railroad/rail in range(1)) + addtimer(CALLBACK(rail, /atom/proc/update_appearance), 5) + +/obj/structure/railroad/Destroy() + . = ..() + for(var/obj/structure/railroad/rail in range(1)) + if(rail == src) + continue + addtimer(CALLBACK(rail, /atom/proc/update_appearance), 5) + +/obj/structure/railroad/update_appearance(updates) + icon_state = "rail" + var/turf/src_turf = get_turf(src) + for(var/direction in GLOB.cardinals) + var/obj/structure/railroad/locate_rail = locate() in get_step(src_turf, direction) + if(!locate_rail) + continue + icon_state = "[icon_state][direction]" + return ..() + +/obj/structure/railroad/crowbar_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + if(!do_after(user, 2 SECONDS, src)) + return + tool.play_tool_sound(src) + new /obj/item/stack/rail_track(get_turf(src)) + qdel(src) + +/obj/vehicle/ridden/rail_cart + name = "rail cart" + desc = "A wonderful form of locomotion. It will only ride while on tracks. It does have storage" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "railcart" + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + /// The mutable appearance used for the overlay over buckled mobs. + var/mutable_appearance/railoverlay + /// whether there is sand in the cart + var/has_sand = FALSE + +/obj/vehicle/ridden/rail_cart/examine(mob/user) + . = ..() + . += span_notice("
Alt-Click to attach a rail cart to this cart.") + . += span_notice("
Filling it with 10 sand will allow it to be used as a planter!") + +/obj/vehicle/ridden/rail_cart/Initialize(mapload) + . = ..() + attach_trailer() + railoverlay = mutable_appearance(icon, "railoverlay", ABOVE_MOB_LAYER, src) + AddElement(/datum/element/ridable, /datum/component/riding/vehicle/rail_cart) + + create_storage(max_total_storage = 21, max_slots = 21) + +/obj/vehicle/ridden/rail_cart/post_buckle_mob(mob/living/M) + . = ..() + update_overlays() + +/obj/vehicle/ridden/rail_cart/post_unbuckle_mob(mob/living/M) + . = ..() + update_overlays() + +/obj/vehicle/ridden/rail_cart/update_overlays() + . = ..() + if(has_buckled_mobs()) + add_overlay(railoverlay) + else + cut_overlay(railoverlay) + +/obj/vehicle/ridden/rail_cart/relaymove(mob/living/user, direction) + var/obj/structure/railroad/locate_rail = locate() in get_step(src, direction) + if(!canmove || !locate_rail) + return FALSE + if(is_driver(user)) + return relaydrive(user, direction) + return FALSE + +/obj/vehicle/ridden/rail_cart/click_alt(mob/user) + attach_trailer() + return CLICK_ACTION_SUCCESS + +/obj/vehicle/ridden/rail_cart/attack_hand(mob/living/user, list/modifiers) + . = ..() + atom_storage?.show_contents(user) + +/obj/vehicle/ridden/rail_cart/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/use_item = attacking_item + if(has_sand || !use_item.use(10)) + return ..() + AddComponent(/datum/component/simple_farm, TRUE, TRUE, list(0, 16)) + has_sand = TRUE + RemoveElement(/datum/element/ridable) + return + + if(attacking_item.tool_behaviour == TOOL_SHOVEL) + var/datum/component/remove_component = GetComponent(/datum/component/simple_farm) + if(!remove_component) + return ..() + qdel(remove_component) + has_sand = FALSE + AddElement(/datum/element/ridable, /datum/component/riding/vehicle/rail_cart) + return + + return ..() + +/// searches the cardinal directions to add this cart to another cart's trailer +/obj/vehicle/ridden/rail_cart/proc/attach_trailer() + if(trailer) + remove_trailer() + return + for(var/direction in GLOB.cardinals) + var/obj/vehicle/ridden/rail_cart/locate_cart = locate() in get_step(src, direction) + if(!locate_cart || locate_cart.trailer == src) + continue + add_trailer(locate_cart) + break + +/datum/component/riding/vehicle/rail_cart + vehicle_move_delay = 0.5 + ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER + +/datum/component/riding/vehicle/rail_cart/handle_specials() + . = ..() + set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 13), TEXT_SOUTH = list(0, 13), TEXT_EAST = list(0, 13), TEXT_WEST = list(0, 13))) + set_vehicle_dir_layer(SOUTH, OBJ_LAYER) + set_vehicle_dir_layer(NORTH, OBJ_LAYER) + set_vehicle_dir_layer(EAST, OBJ_LAYER) + set_vehicle_dir_layer(WEST, OBJ_LAYER) diff --git a/modular_doppler/hearthkin/primitive_structures/code/storage_structures.dm b/modular_doppler/hearthkin/primitive_structures/code/storage_structures.dm new file mode 100644 index 0000000000000..9a8a44939ba68 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/storage_structures.dm @@ -0,0 +1,160 @@ +// Wooden shelves that force items placed on them to be visually placed them + +/obj/structure/rack/wooden + name = "shelf" + icon_state = "shelf_wood" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/storage.dmi' + resistance_flags = FLAMMABLE + interaction_flags_mouse_drop = NEED_DEXTERITY + +/obj/structure/rack/wooden/mouse_drop_receive(atom/dropping, mob/user, params) + if ((!isitem(dropping) || user.get_active_held_item() != dropping)) + return + + if(!user.dropItemToGround(dropping)) + return + + if(dropping.loc != src.loc) + step(dropping, get_dir(dropping, src)) + + var/list/modifiers = params2list(params) + if(!LAZYACCESS(modifiers, ICON_X) || !LAZYACCESS(modifiers, ICON_Y)) + return + + dropping.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size / 3), world.icon_size / 3) + dropping.pixel_y = text2num(LAZYACCESS(modifiers, ICON_Y)) > 16 ? 10 : -4 + +/obj/structure/rack/wooden/wrench_act_secondary(mob/living/user, obj/item/tool) + return NONE + +/obj/structure/rack/wooden/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/rack/wooden/atom_deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) + +// Barrel but it works like a crate + +/obj/structure/closet/crate/wooden/storage_barrel + name = "storage barrel" + desc = "This barrel can't hold liquids, it can just hold things inside of it however!" + icon_state = "barrel" + base_icon_state = "barrel" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/storage.dmi' + resistance_flags = FLAMMABLE + material_drop = /obj/item/stack/sheet/mineral/wood + material_drop_amount = 4 + cutting_tool = /obj/item/crowbar + +/obj/machinery/smartfridge/wooden + name = "debug wooden smartfridge" + desc = "You should not be seeing this!" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/storage.dmi' + resistance_flags = FLAMMABLE + base_build_path = /obj/machinery/smartfridge/wooden + icon_state = "producebin" + base_icon_state = "producebin" + use_power = NO_POWER_USE + light_power = 0 + idle_power_usage = 0 + circuit = null + has_emissive = FALSE + integrity_failure = 0 + can_atmos_pass = ATMOS_PASS_YES + visible_contents = TRUE + can_be_welded_down = FALSE + has_emissive = FALSE + vend_sound = null + +/obj/machinery/smartfridge/wooden/Initialize(mapload) + . = ..() + if(type == /obj/machinery/smartfridge/wooden) // don't even let these prototypes exist + return INITIALIZE_HINT_QDEL + +/obj/machinery/smartfridge/wooden/visible_items() + return contents.len + +// formerly NO_DECONSTRUCTION +/obj/machinery/smartfridge/wooden/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) + return NONE + +/obj/machinery/smartfridge/wooden/default_deconstruction_crowbar(obj/item/crowbar, ignore_panel, custom_deconstruct) + return NONE + +/obj/machinery/smartfridge/wooden/default_pry_open(obj/item/crowbar, close_after_pry, open_density, closed_density) + return NONE + +/obj/machinery/smartfridge/wooden/crowbar_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/smartfridge/wooden/on_deconstruction(disassembled) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) + +/obj/machinery/smartfridge/wooden/structure_examine() + . = span_info("The whole rack can be [EXAMINE_HINT("pried")] apart.") + +/obj/machinery/smartfridge/wooden/produce_bin + name = "produce bin" + desc = "A wooden hamper, used to hold plant products and try to keep them safe from pests." + icon_state = "producebin" + base_icon_state = "producebin" + contents_overlay_icon = "produce" + base_build_path = /obj/machinery/smartfridge/wooden/produce_bin + +/obj/machinery/smartfridge/wooden/produce_bin/accept_check(obj/item/item_to_check) + var/static/list/accepted_items = list( + /obj/item/food/grown, + /obj/item/grown, + /obj/item/graft, + ) + + return is_type_in_list(item_to_check, accepted_items) + +/obj/machinery/smartfridge/wooden/seed_shelf + name = "seed shelf" + desc = "A wooden shelf, used to hold seeds, preventing them from germinating early." + icon_state = "seedshelf" + base_icon_state = "seedshelf" + contents_overlay_icon = "seed" + base_build_path = /obj/machinery/smartfridge/wooden/seed_shelf + +/obj/machinery/smartfridge/wooden/seed_shelf/accept_check(obj/item/item_to_check) + return istype(item_to_check, /obj/item/seeds) + +/obj/machinery/smartfridge/wooden/ration_shelf + name = "ration shelf" + desc = "A wooden shelf, used to store food... Preferably preserved." + icon_state = "rationshelf" + base_icon_state = "rationshelf" + contents_overlay_icon = "ration" + base_build_path = /obj/machinery/smartfridge/wooden/ration_shelf + +/obj/machinery/smartfridge/wooden/ration_shelf/accept_check(obj/item/item_to_check) + return (IS_EDIBLE(item_to_check) || (istype(item_to_check,/obj/item/reagent_containers/cup/bowl) && length(item_to_check.reagents?.reagent_list))) + +/obj/machinery/smartfridge/wooden/produce_display + name = "produce display" + desc = "A wooden table with awning, used to display produce items." + icon_state = "producedisplay" + base_icon_state = "producedisplay" + contents_overlay_icon = "nonfood" + base_build_path = /obj/machinery/smartfridge/wooden/produce_display + +/obj/machinery/smartfridge/wooden/produce_display/accept_check(obj/item/item_to_check) + var/static/list/accepted_items = list( + /obj/item/grown, + /obj/item/bouquet, + /obj/item/clothing/head/costume/garland, + ) + var/fancy_food = istype(item_to_check, /obj/item/food/grown) && item_to_check.slot_flags != NONE // mostly things like flowers + return fancy_food || is_type_in_list(item_to_check, accepted_items) diff --git a/modular_doppler/hearthkin/primitive_structures/code/totally_thatch_roof.dm b/modular_doppler/hearthkin/primitive_structures/code/totally_thatch_roof.dm new file mode 100644 index 0000000000000..5474ca0814259 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/totally_thatch_roof.dm @@ -0,0 +1,68 @@ +/turf/open/misc/grass/roofing + name = "thatched roof" + desc = "A collection of various dried greens, not so green anymore, that makes a passable roof material." + baseturfs = /turf/open/openspace/icemoon + initial_gas_mix = "ICEMOON_ATMOS" + icon_state = "grass-255" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi' + smooth_icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi' + + +/turf/open/floor/grass/thatch + name = "thatch patch" + desc = "A collection of various dried greens, not so green anymore, that makes a passable floor material" + icon_state = "grass-255" + base_icon_state = "grass" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi' + damaged_dmi = 'icons/turf/damaged.dmi' + floor_tile = /obj/item/stack/tile/grass/thatch + bullet_bounce_sound = null + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_GRASS + canSmoothWith = SMOOTH_GROUP_FLOOR_GRASS + SMOOTH_GROUP_CLOSED_TURFS + layer = HIGH_TURF_LAYER + /// Icon used for smoothing + var/smooth_icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi' + + +/turf/open/floor/grass/thatch/Initialize(mapload) + . = ..() + if(smoothing_flags) + var/matrix/translation = new + translation.Translate(-9, -9) + transform = translation + icon = smooth_icon + + +/turf/open/floor/grass/thatch/broken_states() + return list("grass_damaged") + + +/turf/open/floor/grass/thatch/burnt_states() + return list("grass_damaged") + + +/obj/item/stack/tile/grass/thatch + name = "thatch tile" + singular_name = "thatch floor tile" + desc = "A patch of thatch like in those old-school barns." + icon_state = "tile_thatch" + inhand_icon_state = "tile-thatch" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch_obj.dmi' + lefthand_file = 'modular_doppler/hearthkin/primitive_structures/icons/tile_lefthand.dmi' + righthand_file = 'modular_doppler/hearthkin/primitive_structures/icons/tile_righthand.dmi' + resistance_flags = FLAMMABLE + turf_type = /turf/open/floor/grass/thatch + merge_type = /obj/item/stack/tile/grass/thatch + + +/obj/item/food/grown/grass/thatch + name = "thatch" + desc = "Yellow and dry." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/thatch_obj.dmi' + icon_state = "thatch_clump" + stacktype = /obj/item/stack/tile/grass/thatch + + +/obj/item/food/grown/grass/make_dryable() + AddElement(/datum/element/dryable, /obj/item/food/grown/grass/thatch) diff --git a/modular_doppler/hearthkin/primitive_structures/code/wall_torch.dm b/modular_doppler/hearthkin/primitive_structures/code/wall_torch.dm new file mode 100644 index 0000000000000..00a76e9e80917 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/wall_torch.dm @@ -0,0 +1,204 @@ +/obj/structure/wall_torch + name = "mounted torch" + desc = "A simple torch mounted to the wall, for lighting and such." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/lighting.dmi' + icon_state = "walltorch" + base_icon_state = "walltorch" + anchored = TRUE + density = FALSE + light_color = LIGHT_COLOR_FIRE + /// Torch contained by the wall torch, if it was mounted manually. + /// Will be `TRUE` if it was intended to spawn in with a torch, + /// without actually initializing a torch in it to save on memory. + var/obj/item/flashlight/flare/torch/mounted_torch = TRUE + /// is the bonfire lit? + var/burning = FALSE + /// Does this torch spawn pre-lit? + var/spawns_lit = FALSE + /// What this item turns back into when wrenched off the wall. + var/wallmount_item_type = /obj/item/wallframe/torch_mount + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/wall_torch, 28) + +/obj/structure/wall_torch/Initialize(mapload) + . = ..() + if(mounted_torch && spawns_lit) + light_it_up() + + update_appearance(UPDATE_NAME | UPDATE_DESC | UPDATE_ICON_STATE) + find_and_hang_on_wall() + + +/obj/structure/wall_torch/Destroy() + drop_torch() // So it drops on the floor when destroyed. + return ..() + + +/obj/structure/wall_torch/update_icon_state() + icon_state = "[base_icon_state][mounted_torch ? (burning ? "_on" : "") : "_mount"]" + return ..() + + +/obj/structure/wall_torch/update_name(updates) + . = ..() + name = mounted_torch ? "mounted torch" : "torch mount" + + +/obj/structure/wall_torch/update_desc(updates) + . = ..() + desc = mounted_torch ? "A simple torch mounted to the wall, for lighting and such." : "A simple torch mount, torches go here." + + +/obj/structure/wall_torch/attackby(obj/item/used_item, mob/living/user, params) + if(!mounted_torch) + if(!istype(used_item, /obj/item/flashlight/flare/torch)) + return ..() + + mounted_torch = used_item + RegisterSignal(used_item, COMSIG_QDELETING, PROC_REF(remove_torch)) + used_item.forceMove(src) + update_appearance(UPDATE_NAME | UPDATE_DESC) + + if(mounted_torch.light_on) + light_it_up() + else + extinguish() + + mounted_torch.turn_off() + + return + + if(!burning && used_item.get_temperature()) + light_it_up() + else + return ..() + + +/obj/structure/wall_torch/fire_act(exposed_temperature, exposed_volume) + light_it_up() + + +/// Sets the torch's icon to burning and sets the light up +/obj/structure/wall_torch/proc/light_it_up() + burning = TRUE + set_light(4) + update_icon_state() + update_appearance(UPDATE_ICON) + + +/obj/structure/wall_torch/extinguish() + . = ..() + if(!burning) + return + + burning = FALSE + set_light(0) + update_appearance(UPDATE_ICON) + + +/obj/structure/wall_torch/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(.) + return + + remove_torch(user) + + +/** + * Helper proc that handles removing the torch and trying to put it in the user's hand. + */ +/obj/structure/wall_torch/proc/remove_torch(mob/living/user, update_visuals = TRUE) + if(!mounted_torch) + return + + if(!istype(mounted_torch)) + mounted_torch = new(src) + + if(burning) + mounted_torch.toggle_light() + + if(user) + mounted_torch.attempt_pickup(user) + + else + mounted_torch.forceMove(drop_location()) + + UnregisterSignal(mounted_torch, COMSIG_QDELETING) + + mounted_torch = null + burning = FALSE + set_light(0) + update_appearance(UPDATE_ICON | UPDATE_NAME | UPDATE_DESC) + + +/obj/structure/wall_torch/wrench_act(mob/living/user, obj/item/tool) + tool.play_tool_sound(src) + to_chat(user, span_notice("You detach [src] from its place.")) + + remove_torch(user) + + var/obj/item/wallframe/torch_mount/mount_item = new /obj/item/wallframe/torch_mount(drop_location()) + transfer_fingerprints_to(mount_item) + + qdel(src) + return TRUE + + +/// Simple helper to drop the torch upon the mount being qdel'd. +/obj/structure/wall_torch/proc/drop_torch() + if(!mounted_torch) + return + + if(!istype(mounted_torch)) + mounted_torch = new(src) + + if(burning) + mounted_torch.toggle_light() + + mounted_torch.forceMove(drop_location()) + + UnregisterSignal(mounted_torch, COMSIG_QDELETING) + + mounted_torch = null + + +/obj/structure/wall_torch/mount_only + name = "torch mount" + mounted_torch = null + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/wall_torch/mount_only, 28) + + +/obj/structure/wall_torch/spawns_lit + spawns_lit = TRUE + +MAPPING_DIRECTIONAL_HELPERS(/obj/structure/wall_torch/spawns_lit, 28) + + +/obj/item/wallframe/torch_mount + name = "torch mount" + desc = "Used to attach torches to walls." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/lighting.dmi' + icon_state = "walltorch_mount" + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT) + result_path = /obj/structure/wall_torch/mount_only + pixel_shift = 28 + + +/obj/item/wallframe/torch_mount/try_build(turf/on_wall, mob/user) + if(get_dist(on_wall,user) > 1) + balloon_alert(user, "you are too far!") + return + + var/floor_to_wall = get_dir(user, on_wall) + if(!(floor_to_wall in GLOB.cardinals)) + balloon_alert(user, "stand in line with wall!") + return + + var/turf/user_turf = get_turf(user) + + if(check_wall_item(user_turf, floor_to_wall, wall_external)) + balloon_alert(user, "already something here!") + return + + return TRUE diff --git a/modular_doppler/hearthkin/primitive_structures/code/windows.dm b/modular_doppler/hearthkin/primitive_structures/code/windows.dm new file mode 100644 index 0000000000000..c2e38e491813e --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/windows.dm @@ -0,0 +1,20 @@ +/obj/structure/window/green_glass_pane + name = "green glass window" + desc = "A handcrafted green glass window. At least you can still see through it." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/windows.dmi' + icon_state = "green_glass" + flags_1 = NONE + obj_flags = parent_type::obj_flags | NO_DEBRIS_AFTER_DECONSTRUCTION + can_be_unanchored = FALSE + fulltile = TRUE + flags_1 = PREVENT_CLICK_UNDER_1 + +/datum/crafting_recipe/green_glass_pane + name = "green glass window" + result = /obj/structure/window/green_glass_pane + time = 0.2 SECONDS + reqs = list( + /datum/reagent/iron = 5, + /obj/item/stack/sheet/glass = 2, + ) + category = CAT_STRUCTURE diff --git a/modular_doppler/hearthkin/primitive_structures/code/wooden_ladder.dm b/modular_doppler/hearthkin/primitive_structures/code/wooden_ladder.dm new file mode 100644 index 0000000000000..136cc715d64e1 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/code/wooden_ladder.dm @@ -0,0 +1,7 @@ +// Ladder structure, behaves identical to a normal ladder except flammable + +/obj/structure/ladder/wood + name = "wooden ladder" + desc = "Up or down, whatever your mood you sure wood find what you're looking for with this ladder." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/wooden_ladder.dmi' + resistance_flags = FLAMMABLE diff --git a/modular_doppler/hearthkin/primitive_structures/icons/lighting.dmi b/modular_doppler/hearthkin/primitive_structures/icons/lighting.dmi new file mode 100644 index 0000000000000..be2d9f0d956a6 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/lighting.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi b/modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi new file mode 100644 index 0000000000000..2ec8d3ce0e635 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/storage.dmi b/modular_doppler/hearthkin/primitive_structures/icons/storage.dmi new file mode 100644 index 0000000000000..413a49e7eaa18 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/storage.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi b/modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi new file mode 100644 index 0000000000000..53299ab681d04 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/thatch.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/thatch_obj.dmi b/modular_doppler/hearthkin/primitive_structures/icons/thatch_obj.dmi new file mode 100644 index 0000000000000..e6d19554945b3 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/thatch_obj.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/tile_lefthand.dmi b/modular_doppler/hearthkin/primitive_structures/icons/tile_lefthand.dmi new file mode 100644 index 0000000000000..438e943271b82 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/tile_lefthand.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/tile_righthand.dmi b/modular_doppler/hearthkin/primitive_structures/icons/tile_righthand.dmi new file mode 100644 index 0000000000000..3546b59d19ec7 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/tile_righthand.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/tribal_beds.dmi b/modular_doppler/hearthkin/primitive_structures/icons/tribal_beds.dmi new file mode 100644 index 0000000000000..0b84f6d85d13d Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/tribal_beds.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/windows.dmi b/modular_doppler/hearthkin/primitive_structures/icons/windows.dmi new file mode 100644 index 0000000000000..02f84bea3823d Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/windows.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/wooden_fence.dmi b/modular_doppler/hearthkin/primitive_structures/icons/wooden_fence.dmi new file mode 100644 index 0000000000000..6bd24627e3159 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/wooden_fence.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/wooden_gate.dmi b/modular_doppler/hearthkin/primitive_structures/icons/wooden_gate.dmi new file mode 100644 index 0000000000000..81c68631613aa Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/wooden_gate.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/icons/wooden_ladder.dmi b/modular_doppler/hearthkin/primitive_structures/icons/wooden_ladder.dmi new file mode 100644 index 0000000000000..b9a782ea52ae0 Binary files /dev/null and b/modular_doppler/hearthkin/primitive_structures/icons/wooden_ladder.dmi differ diff --git a/modular_doppler/hearthkin/primitive_structures/readme.md b/modular_doppler/hearthkin/primitive_structures/readme.md new file mode 100644 index 0000000000000..c1950a1484aa9 --- /dev/null +++ b/modular_doppler/hearthkin/primitive_structures/readme.md @@ -0,0 +1,26 @@ +## Title: Primitive Structures + +MODULE ID: PRIMITIVE_STRUCTURES + +### Description: + +Contains various items of primitive style for icecats +Fuel wells and railroads were added as well. + +### TG Proc/File Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `modular_doppler\modular_crafting\code\sheet_types.dm` crafting recipes + +### Credits: diff --git a/modular_doppler/hearthkin/readme.md b/modular_doppler/hearthkin/readme.md new file mode 100644 index 0000000000000..14d7d45aeb079 --- /dev/null +++ b/modular_doppler/hearthkin/readme.md @@ -0,0 +1,33 @@ +## Title: Hearthkin + +MODULE ID: HEARTHKIN + +### Description: + +Contains the main sub-modules for the Hearthkin, a tribe of genetically modified humanoids that inhabits the Ice Moon. They have their own primitive means of cooking, farming, production and much more. + +The species ID `primitive_felinid` was added in the configuration file `config\doppler\config_doppler.txt` as a round start species + +### TG Proc Changes: + +| proc | file | +| --------------------------------------------------------------------- | ----------------------------------- | +| `/atom/proc/tool_act(mob/living/user, obj/item/tool, list/modifiers)` | `code\game\atom\atom_tool_acts.dm` | + +### Defines: + +- `code\__DEFINES\~doppler_defines\is_helpers.dm` is_type identificator for species +- `code\__DEFINES\~doppler_defines\reagent_forging_tools.dm` glassblowing tools' define +- `code\__DEFINES\~doppler_defines\species.dm` species id +- `code\__DEFINES\~doppler_defines\traits.dm` trait for glassblowing + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `modular_doppler\modular_crafting\code\sheet_types.dm` crafting recipes +- `modular_doppler\stone\code\stone.dm` crafting recipes + +### Credits: diff --git a/modular_doppler/hearthkin/tribal_extended/code/ammo/caseless/arrow.dm b/modular_doppler/hearthkin/tribal_extended/code/ammo/caseless/arrow.dm new file mode 100644 index 0000000000000..4ad8836986cb5 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/ammo/caseless/arrow.dm @@ -0,0 +1,23 @@ +/obj/item/ammo_casing/arrow/ash + name = "ashen arrow" + desc = "An arrow made from ash and iron. They're cheap, but they fell the beasts of lavaland like none other." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi' + icon_state = "ashenarrow" + base_icon_state = "ashenarrow" + projectile_type = /obj/projectile/bullet/arrow/ash + +/obj/item/ammo_casing/arrow/bone + name = "bone arrow" + desc = "An arrow made of bone and sinew. The tip is sharp and jagged, suitable for digging into flesh." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi' + icon_state = "bonearrow" + base_icon_state = "ashenarrow" + projectile_type = /obj/projectile/bullet/arrow/bone + +/obj/item/ammo_casing/arrow/bronze + name = "bronze arrow" + desc = "An arrow tipped with bronze. Fit for killing gods." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi' + icon_state = "bronzearrow" + base_icon_state = "ashenarrow" + projectile_type = /obj/projectile/bullet/arrow/bronze diff --git a/modular_doppler/hearthkin/tribal_extended/code/ammo/reusable/arrow.dm b/modular_doppler/hearthkin/tribal_extended/code/ammo/reusable/arrow.dm new file mode 100644 index 0000000000000..5fd451bb0f4e4 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/ammo/reusable/arrow.dm @@ -0,0 +1,63 @@ +/obj/projectile/bullet/arrow + var/faction_bonus_force = 0 //Bonus force dealt against certain factions + var/list/nemesis_paths //Any mob with a faction that exists in this list will take bonus damage/effects + +/obj/projectile/bullet/arrow/prehit_pierce(mob/living/target, mob/living/carbon/human/user) + if(isnull(target)) + return ..() + + // check if target is a matching type and apply damage bonus if applicable + for(var/nemesis_path in nemesis_paths) + if(istype(target, nemesis_path)) + damage += faction_bonus_force + break + + return ..() + +/obj/projectile/bullet/arrow/ash + name = "ashen arrow" + desc = "An arrow made of hardened ash." + faction_bonus_force = 60 + damage = 15//lower me to 20 or 15 + nemesis_paths = list( + /mob/living/simple_animal/hostile/asteroid, + /mob/living/basic/mining, + /mob/living/basic/wumborian_fugu, + ) + shrapnel_type = /obj/item/ammo_casing/arrow/ash + +/obj/projectile/bullet/arrow/bone + name = "bone arrow" + desc = "An arrow made from bone and sinew." + faction_bonus_force = 35 + damage = 35 + armour_penetration = 20 + wound_bonus = -30 + nemesis_paths = list( + /mob/living/simple_animal/hostile/asteroid, + /mob/living/basic/mining, + /mob/living/basic/wumborian_fugu, + ) + shrapnel_type = /obj/item/ammo_casing/arrow/bone + embed_type = /datum/embed_data/bone_arrow + +/datum/embed_data/bone_arrow + embed_chance = 33 + fall_chance = 3 + jostle_chance = 4 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.4 + pain_mult = 5 + jostle_pain_mult = 6 + rip_time = 0.5 SECONDS + +/obj/projectile/bullet/arrow/bronze + name = "bronze arrow" + desc = "A bronze-tipped arrow." + faction_bonus_force = 90 + damage = 30 + armour_penetration = 30 + nemesis_paths = list( + /mob/living/simple_animal/hostile/megafauna, + ) + shrapnel_type = /obj/item/ammo_casing/arrow/bronze diff --git a/modular_doppler/hearthkin/tribal_extended/code/crafting.dm b/modular_doppler/hearthkin/tribal_extended/code/crafting.dm new file mode 100644 index 0000000000000..b6705ad928954 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/crafting.dm @@ -0,0 +1,25 @@ +/obj/item/weaponcrafting/silkstring + name = "string" + desc = "A long piece of string that looks like a cable coil." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/crafting.dmi' + icon_state = "silkstring" + +/obj/item/dice/d6/bone + name = "bone die" + desc = "A die carved from a creature's bone. Dried blood marks the indented pits." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/dice.dmi' + icon_state = "db6" + microwave_riggable = FALSE // You can't melt bone in the microwave + +/obj/item/reagent_containers/cup/bowl/wood_bowl + name = "wooden bowl" + desc = "A bowl made out of wood. Primitive, but effective." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/crafting.dmi' + icon_state = "wood_bowl" + fill_icon_state = "fullbowl" + fill_icon = 'icons/obj/mining_zones/ash_flora.dmi' + +/obj/item/reagent_containers/cup/bowl/mushroom_bowl/update_icon_state() + if(!reagents.total_volume) + icon_state = "wood_bowl" + return ..() diff --git a/modular_doppler/hearthkin/tribal_extended/code/recipes.dm b/modular_doppler/hearthkin/tribal_extended/code/recipes.dm new file mode 100644 index 0000000000000..7227afa407141 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/recipes.dm @@ -0,0 +1,109 @@ +/datum/crafting_recipe/silkstring + name = "Silk String" + result = /obj/item/weaponcrafting/silkstring + reqs = list(/obj/item/stack/sheet/cloth = 1) + time = 5 SECONDS + category = CAT_MISC + +/datum/crafting_recipe/pipebow + name = "Pipe Bow" + result = /obj/item/gun/ballistic/bow/tribalbow/pipe + reqs = list( + /obj/item/pipe = 5, + /obj/item/stack/sheet/plastic = 5, + /obj/item/weaponcrafting/silkstring = 2, + ) + time = 45 SECONDS + category = CAT_WEAPON_RANGED + +/datum/crafting_recipe/bone_arrow + name = "Bone Arrow" + result = /obj/item/ammo_casing/arrow/bone + reqs = list( + /obj/item/stack/sheet/bone = 1, + /obj/item/ammo_casing/arrow = 1, + ) + category = CAT_WEAPON_AMMO + non_craftable = TRUE + steps = list("Reinforce the arrow with sinew.") + +/datum/crafting_recipe/ashen_arrow + name = "Ashen Arrow" + result = /obj/item/ammo_casing/arrow/ash + reqs = list( + /obj/item/ammo_casing/arrow = 1, + /obj/item/stack/sheet/sinew = 1, + ) + category = CAT_WEAPON_AMMO + non_craftable = TRUE + steps = list("Reinforce the arrow with sinew.") + +/datum/crafting_recipe/bronze_arrow + name = "Bronze arrow" + result = /obj/item/ammo_casing/arrow/bronze + reqs = list( + /obj/item/ammo_casing/arrow = 1, + /obj/item/stack/tile/bronze = 1, + ) + category = CAT_WEAPON_AMMO + non_craftable = TRUE + steps = list("Reinforce the arrowhead with bronze.") + +/datum/crafting_recipe/goliathshield + name = "Goliath shield" + result = /obj/item/shield/goliath + reqs = list( + /obj/item/stack/sheet/bone = 4, + /obj/item/stack/sheet/animalhide/goliath_hide = 3, + ) + time = 6 SECONDS + category = CAT_EQUIPMENT + +/datum/crafting_recipe/bonesword + name = "Bone Sword" + result = /obj/item/claymore/bone + reqs = list( + /obj/item/stack/sheet/bone = 2, + /obj/item/stack/sheet/sinew = 2, + ) + time = 4 SECONDS + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED + category = CAT_WEAPON_MELEE + +/datum/crafting_recipe/quiver + name = "Quiver" + result = /obj/item/storage/bag/quiver + reqs = list( + /obj/item/stack/sheet/leather = 2, + /obj/item/weaponcrafting/silkstring = 4, + ) + time = 8 SECONDS + category = CAT_WEAPON_AMMO + +/datum/crafting_recipe/bone_bow + name = "Bone Bow" + result = /obj/item/gun/ballistic/bow/tribalbow/ashen + reqs = list( + /obj/item/stack/sheet/bone = 4, + /obj/item/stack/sheet/sinew = 4, + ) + time = 20 SECONDS + category = CAT_WEAPON_RANGED + +/datum/crafting_recipe/torch + name = "Torch" + reqs = list(/obj/item/grown/log = 1) + result = /obj/item/flashlight/flare/torch + category = CAT_MISC + non_craftable = TRUE + steps = list("Use any dried leaf-like plant on a towercap log! (Ambrosia, cannabis, tobacco, etc!)") + +/datum/crafting_recipe/bonedice + name = "Bone Die" + result = /obj/item/dice/d6/bone + time = 5 SECONDS + reqs = list( + /obj/item/stack/sheet/bone = 1, + ) + category = CAT_EQUIPMENT + diff --git a/modular_doppler/hearthkin/tribal_extended/code/weapons/bow.dm b/modular_doppler/hearthkin/tribal_extended/code/weapons/bow.dm new file mode 100644 index 0000000000000..28a602dfbbfc1 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/weapons/bow.dm @@ -0,0 +1,31 @@ +/obj/item/gun/ballistic/bow/tribalbow + icon = 'modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi' + lefthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/bows_lefthand.dmi' + righthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/bows_righthand.dmi' + worn_icon = 'modular_doppler/hearthkin/tribal_extended/icons/back.dmi' + inhand_icon_state = "bow" + icon_state = null + base_icon_state = "bow" + worn_icon_state = "bow" + slot_flags = ITEM_SLOT_BACK + +/obj/item/gun/ballistic/bow/tribalbow/ashen + name = "bone bow" + desc = "Some sort of primitive projectile weapon made of bone and wrapped sinew, oddly robust." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi' + icon_state = "ashenbow" + base_icon_state = "ashenbow" + inhand_icon_state = "ashenbow" + worn_icon_state = "ashenbow" + force = 20 + +/obj/item/gun/ballistic/bow/tribalbow/pipe + name = "pipe bow" + desc = "Portable and sleek, but you'd be better off hitting someone with a pool noodle." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi' + icon_state = "pipebow" + base_icon_state = "pipebow" + inhand_icon_state = "pipebow" + worn_icon_state = "pipebow" + force = 10 + slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_SUITSTORE diff --git a/modular_doppler/hearthkin/tribal_extended/code/weapons/shield.dm b/modular_doppler/hearthkin/tribal_extended/code/weapons/shield.dm new file mode 100644 index 0000000000000..2a6b325182002 --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/weapons/shield.dm @@ -0,0 +1,14 @@ +/obj/item/shield/goliath + name = "goliath shield" + desc = "A shield made from interwoven plates of goliath hide." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/shields.dmi' + icon_state = "goliath_shield" + lefthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/shields_lefthand.dmi' + righthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/shields_righthand.dmi' + worn_icon = 'modular_doppler/hearthkin/tribal_extended/icons/back.dmi' + worn_icon_state = "goliath_shield" + inhand_icon_state = "goliath_shield" + max_integrity = 200 + w_class = WEIGHT_CLASS_BULKY + shield_break_sound = 'sound/effects/bang.ogg' + shield_break_leftover = /obj/item/stack/sheet/animalhide/goliath_hide diff --git a/modular_doppler/hearthkin/tribal_extended/code/weapons/sword.dm b/modular_doppler/hearthkin/tribal_extended/code/weapons/sword.dm new file mode 100644 index 0000000000000..a2c7cff9cdccc --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/code/weapons/sword.dm @@ -0,0 +1,23 @@ +/obj/item/claymore/bone + name = "bone sword" + desc = "Jagged pieces of bone are tied to what looks like a goliaths femur." + icon = 'modular_doppler/hearthkin/tribal_extended/icons/items_and_weapons.dmi' + lefthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/swords_lefthand.dmi' + righthand_file = 'modular_doppler/hearthkin/tribal_extended/icons/swords_righthand.dmi' + worn_icon = 'modular_doppler/hearthkin/tribal_extended/icons/back.dmi' + icon_state = "bone_sword" + inhand_icon_state = "bone_sword" + worn_icon_state = "bone_sword" + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + force = 20 + throwforce = 10 + armour_penetration = 10 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb_continuous = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 0 + armor_type = /datum/armor/claymore_bone + +/datum/armor/claymore_bone + fire = 100 + acid = 50 diff --git a/modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi b/modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi new file mode 100644 index 0000000000000..8fb9d7a695a1f Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/ammo.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/back.dmi b/modular_doppler/hearthkin/tribal_extended/icons/back.dmi new file mode 100644 index 0000000000000..43055d5b5992e Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/back.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/belt.dmi b/modular_doppler/hearthkin/tribal_extended/icons/belt.dmi new file mode 100644 index 0000000000000..019d63ca174d2 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/belt.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/bows_lefthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/bows_lefthand.dmi new file mode 100644 index 0000000000000..5ce0b6afdb417 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/bows_lefthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/bows_righthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/bows_righthand.dmi new file mode 100644 index 0000000000000..6e8651a1df8c1 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/bows_righthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/crafting.dmi b/modular_doppler/hearthkin/tribal_extended/icons/crafting.dmi new file mode 100644 index 0000000000000..f549003f086bd Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/crafting.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/dice.dmi b/modular_doppler/hearthkin/tribal_extended/icons/dice.dmi new file mode 100644 index 0000000000000..f597bc60c02ae Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/dice.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/items_and_weapons.dmi b/modular_doppler/hearthkin/tribal_extended/icons/items_and_weapons.dmi new file mode 100644 index 0000000000000..27c0fa3cf830f Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/items_and_weapons.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi b/modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi new file mode 100644 index 0000000000000..3dfc194ae3022 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/projectile.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/shields.dmi b/modular_doppler/hearthkin/tribal_extended/icons/shields.dmi new file mode 100644 index 0000000000000..dcfda3ef96d88 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/shields.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/shields_lefthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/shields_lefthand.dmi new file mode 100644 index 0000000000000..3dff3dd308fd2 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/shields_lefthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/shields_righthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/shields_righthand.dmi new file mode 100644 index 0000000000000..d8927c7dbfdb5 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/shields_righthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/swords_lefthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/swords_lefthand.dmi new file mode 100644 index 0000000000000..3ee1edbc28200 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/swords_lefthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/icons/swords_righthand.dmi b/modular_doppler/hearthkin/tribal_extended/icons/swords_righthand.dmi new file mode 100644 index 0000000000000..66172bb3262f6 Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/icons/swords_righthand.dmi differ diff --git a/modular_doppler/hearthkin/tribal_extended/readme.md b/modular_doppler/hearthkin/tribal_extended/readme.md new file mode 100644 index 0000000000000..5eb036d18e8fe --- /dev/null +++ b/modular_doppler/hearthkin/tribal_extended/readme.md @@ -0,0 +1,25 @@ +## Title: Tribal Extended + +MODULE ID: TRIBAL_EXTENDED + +### Description: + +Adds different types of bows and arrows as well as other tribal miscellaneous. + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: diff --git a/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowdraw.ogg b/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowdraw.ogg new file mode 100644 index 0000000000000..bba8a87e063ab Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowdraw.ogg differ diff --git a/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowfire.ogg b/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowfire.ogg new file mode 100644 index 0000000000000..c079d43475bca Binary files /dev/null and b/modular_doppler/hearthkin/tribal_extended/sound/sound_weapons_bowfire.ogg differ diff --git a/modular_doppler/indicators/code/combat_indicator.dm b/modular_doppler/indicators/code/combat_indicator.dm new file mode 100644 index 0000000000000..567e1d3a8d39c --- /dev/null +++ b/modular_doppler/indicators/code/combat_indicator.dm @@ -0,0 +1,237 @@ +#define COMBAT_NOTICE_COOLDOWN (10 SECONDS) +GLOBAL_VAR_INIT(combat_indicator_overlay, GenerateCombatOverlay()) + +/proc/GenerateCombatOverlay() + var/mutable_appearance/combat_indicator = mutable_appearance('modular_doppler/indicators/icons/combat_indicator.dmi', "combat", FLY_LAYER) + combat_indicator.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + return combat_indicator + +/mob/living + /// Is combat indicator enabled for this mob? Boolean. + var/combat_indicator = FALSE + /// When is the next time this mob will be able to use flick_emote and put the fluff text in chat? + var/nextcombatpopup = 0 + +/** + * Called whenever a mob inside a vehicle/sealed/ toggles CI status. + * + * Tied to the COMSIG_MOB_CI_TOGGLED signal, said signal is assigned when a mob enters a vehicle and unassigned when the mob exits, and is sent whenever set_combat_indicator is called. + * + * Arguments: + * * source -- The mob in question that toggled CI status. + */ + +/obj/vehicle/sealed/proc/mob_toggled_ci(mob/living/source) + SIGNAL_HANDLER + if ((src.max_occupants > src.max_drivers) && (!(source in return_drivers())) && (src.driver_amount() > 0)) // Only returms true if the mob in question has the driver control flags and/or there are drivers. + return + combat_indicator_vehicle = source.combat_indicator // Sync CI between mob and vehicle. + if (combat_indicator_vehicle) + if(world.time > vehicle_next_combat_popup) // As of the time of writing, COMBAT_NOTICE_COOLDOWN is 10 secs, so this is asking "has 10 secs past between last activation of CI?" + vehicle_next_combat_popup = world.time + COMBAT_NOTICE_COOLDOWN + playsound(src, 'sound/machines/chime.ogg', vol = 10, vary = FALSE, extrarange = -6, falloff_exponent = 4, frequency = null, channel = 0, pressure_affected = FALSE, ignore_walls = FALSE, falloff_distance = 1) + flick_emote_popup_on_obj("combat", 20) + visible_message(span_boldwarning("[src] prepares for combat!")) + combat_indicator_vehicle = TRUE + else + combat_indicator_vehicle = FALSE + update_appearance(UPDATE_ICON|UPDATE_OVERLAYS) + +/mob/living/update_overlays() + . = ..() + if(combat_indicator) + . += GLOB.combat_indicator_overlay + +/obj/vehicle/sealed/update_overlays() + . = ..() + if(combat_indicator_vehicle) + . += GLOB.combat_indicator_overlay + +/** + * Called whenever a mob's stat changes. + * Checks if the mob's stat is greater than SOFT_CRIT, and if it is, it will disable CI. + * + * Arguments: + * * source -- The mob in question that toggled CI status. + * * new_stat -- The new stat of the mob. + */ + +/mob/living/proc/ci_on_stat_change(mob/source, new_stat) + SIGNAL_HANDLER + if(new_stat <= SOFT_CRIT) + return + set_combat_indicator(FALSE, involuntary = TRUE) + +/** + * Called whenever a mob's CI status changes for any reason. + * + * Checks if the mob is dead, if config disallows CI, or if the current CI status is the same as state, and if it is, it will change CI status to state. + * + * Arguments: + * * state -- Boolean. Inherited from the procs that call this, basically it's what that proc wants CI to change to - true or false, on or off. + * * involuntary -- Boolean. If true, the mob is dead or unconscious, and the log will reflect that. + */ + +/mob/living/proc/set_combat_indicator(state, involuntary = FALSE) + if(!CONFIG_GET(flag/combat_indicator)) + return + + if(combat_indicator == state) // If the mob is dead (should not happen) or if the combat_indicator is the same as state (also shouldnt happen) kill the proc. + return + + if(stat == DEAD) + disable_combat_indicator(involuntary) + + combat_indicator = state + + SEND_SIGNAL(src, COMSIG_MOB_CI_TOGGLED) + + if(combat_indicator) + enable_combat_indicator() + else + disable_combat_indicator() + +/** + * Called whenever a mob enables CI. + * + * Plays a sound, sents a message to chat, updates their overlay, and sets the mob's CI status to true. + */ + +/mob/living/proc/enable_combat_indicator() + if(world.time > nextcombatpopup) // As of the time of writing, COMBAT_NOTICE_COOLDOWN is 10 secs, so this is asking "has 10 secs past between last activation of CI?" + nextcombatpopup = world.time + COMBAT_NOTICE_COOLDOWN + playsound(src, 'sound/machines/chime.ogg', vol = 10, vary = FALSE, extrarange = -6, falloff_exponent = 4, frequency = null, channel = 0, pressure_affected = FALSE, ignore_walls = FALSE, falloff_distance = 1) + flick_emote_popup_on_mob("combat", 20) + var/ciweapon + if(get_active_held_item()) + ciweapon = get_active_held_item() + if(istype(ciweapon, /obj/item/gun)) + visible_message(span_boldwarning("[src] raises \the [ciweapon] with their finger on the trigger, ready for combat!")) + else + visible_message(span_boldwarning("[src] readies \the [ciweapon] with a tightened grip and offensive stance, ready for combat!")) + else + if(issilicon(src)) + visible_message(span_boldwarning("[src] shifts its armour plating into a defensive stance, ready for combat!")) + if(ishuman(src)) + visible_message(span_boldwarning("[src] raises [p_their()] fists in an offensive stance, ready for combat!")) + if(isalien(src)) + visible_message(span_boldwarning("[src] hisses in a terrifying stance, claws raised and ready for combat!")) + else + visible_message(span_boldwarning("[src] gets ready for combat!")) + combat_indicator = TRUE + apply_status_effect(/datum/status_effect/grouped/surrender, src) + log_message("[src] has turned ON the combat indicator!", LOG_ATTACK) + RegisterSignal(src, COMSIG_MOB_STATCHANGE , PROC_REF(ci_on_stat_change)) + update_appearance(UPDATE_ICON|UPDATE_OVERLAYS) + +/** + * Called whenever a mob disables CI. Or when they die or fall unconscious. + * + * Arguments: + * * involuntary -- Boolean. If true, the mob is dead or unconscious, and the log will reflect that. + */ + +/mob/living/proc/disable_combat_indicator(involuntary = FALSE) + combat_indicator = FALSE + remove_status_effect(/datum/status_effect/grouped/surrender, src) + if(involuntary) + log_message("[src] has fallen unconsious or has died and lost their combat indicator!", LOG_ATTACK) + else + log_message("[src] has turned OFF the combat indicator!", LOG_ATTACK) + UnregisterSignal(src, COMSIG_MOB_STATCHANGE) + update_appearance(UPDATE_ICON|UPDATE_OVERLAYS) + +/** + * Called whenever the user hits their combat indicator keybind, defaulted to C. + * + * If the user is conscious, it will set CI to be whatever the opposite of what it is currently. + */ + +/mob/living/proc/user_toggle_combat_indicator() + if(stat != CONSCIOUS) + return + set_combat_indicator(!combat_indicator) // Set CI status to whatever is the opposite of the current status. + +/** + * Called whenever a mob enters a vehicle/sealed, after everything else. + * + * Sets the vehicle's CI status to that of the mob if the mob is a driver and there are no other drivers, or if the mob is a passenger and there are no drivers. + * + * Arguments: + * * user -- mob/living, the mob that is entering the vehicle. + */ + +/obj/vehicle/sealed/proc/handle_ci_migration(mob/living/user) + if(!typesof(user.loc, /obj/vehicle/sealed)) //Sanity check: If the mob's location (not the tile they are on) is NOT a type of vehicle/sealed, kill the proc. + return + //If the vehicle can have more passenger seats than driver seats (note: each driver seat counts as a passenger seat) AND both: The mob is not a driver, and the vehicle has a driver, return. + if ((src.max_occupants > src.max_drivers) && ((!(user in return_drivers())) && (src.driver_amount() > 0))) + return + if (user.combat_indicator && !combat_indicator_vehicle) // Finally, if all conditions prior are not met, and the mob has CI enabled and the vehicle doesn't, enable CI. + combat_indicator_vehicle = TRUE + update_appearance(UPDATE_ICON|UPDATE_OVERLAYS) + +/** + * Called whenever a mob exits a vehicle/sealed, after everything else. + * + * Disables the vehicle's CI if it was enabled, and if it was the only occupant (or there was noone else in the mech with CI enabled). + * + * Arguments: + * * user -- mob/living, the mob that is exiting the vehicle. + */ + +/obj/vehicle/sealed/proc/disable_ci(mob/living/user) + // If the vehicle can have more occupants than drivers, and either 1. The mob is not a driver and the vehicle has drivers, or 2. The user IS a driver but there is an occupant (drivers count as occupants), return. + if ((src.max_occupants > src.max_drivers) && ((!(user in return_drivers()) && (src.driver_amount() > 0)) || ((user in return_drivers()) && (src.occupant_amount() > 0)))) + return + // If the preceding conditions are not met, and the vehicle has CI, look at each occupant to see if there is a non-driver with CI enabled. If yes, stop the proc, if no, disable CI. + if (combat_indicator_vehicle) + var/has_occupant_with_ci = FALSE + if (src.occupant_amount() > src.driver_amount()) + for (var/mob/living/vehicle_occupant in return_occupants()) + if (vehicle_occupant in return_drivers()) //this for loop does not account for multiple clowns in clown cars. i will not account for that. fuck that. + continue + if (vehicle_occupant.combat_indicator) + has_occupant_with_ci = TRUE + break + if (!has_occupant_with_ci) + combat_indicator_vehicle = FALSE + update_appearance(UPDATE_ICON|UPDATE_OVERLAYS) + +#undef COMBAT_NOTICE_COOLDOWN + +/datum/keybinding/living/combat_indicator + hotkey_keys = list("C") + name = "combat_indicator" + full_name = "Combat Indicator" + description = "Indicates that you're escalating to mechanics. YOU NEED TO USE THIS" + keybind_signal = COMSIG_KB_LIVING_COMBAT_INDICATOR + +/datum/keybinding/living/combat_indicator/down(client/user) + . = ..() + if(.) + return + var/mob/living/L = user.mob + L.user_toggle_combat_indicator() + +/datum/config_entry/flag/combat_indicator + +// Surrender shit +/atom/movable/screen/alert/status_effect/surrender/ + desc = "You're either in combat or being held up. Click here to surrender and show that you don't wish to fight. You will be incapacitated. (You can also say '*surrender' at any time to do this.)" + +/datum/emote/living/surrender + message = "drops to the floor and raises their hands defensively! They surrender%s!" + stat_allowed = SOFT_CRIT + +/datum/emote/living/surrender/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(. && isliving(user)) + var/mob/living/living_user = user + living_user.set_combat_indicator(FALSE) + +/datum/emote/living/surrender/select_message_type(mob/user, intentional) + var/mob/living/living_mob = user + if(living_mob?.body_position == LYING_DOWN) + return "raises their hands defensively! They surrender%s!" + . = ..() diff --git a/modular_doppler/indicators/code/emote_popup.dm b/modular_doppler/indicators/code/emote_popup.dm new file mode 100644 index 0000000000000..e262c38b57950 --- /dev/null +++ b/modular_doppler/indicators/code/emote_popup.dm @@ -0,0 +1,64 @@ +/obj/effect/overlay/emote_popup + icon = 'modular_doppler/indicators/icons/popup_flicks.dmi' + icon_state = "combat" + layer = FLY_LAYER + plane = GAME_PLANE + appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/** + * A proc type that, when called, causes a image/sprite to appear above whatever entity it is called on. + * + * There are two types: on_mob and on_obj, they can only be called on their respective typepaths. + * + * Arguments: + * * state -- The icon_state of whatever .dmi file you're attempting to use for the sprite, in "" format. Ex. "combat", not combat.dmi. + * * time -- The amount of time the sprite remains before remove_emote_popup_on_mob is called. Is used in the addtimer. + */ +/mob/living/proc/flick_emote_popup_on_mob(state, time) + var/obj/effect/overlay/emote_popup/emote_overlay = new + emote_overlay.icon_state = state + vis_contents += emote_overlay + animate(emote_overlay, alpha = 255, time = 5, easing = BOUNCE_EASING, pixel_y = 10) + addtimer(CALLBACK(src, PROC_REF(remove_emote_popup_on_mob), emote_overlay), time) + +/** + * A proc type that, when called, causes a image/sprite to appear above whatever entity it is called on. + * + * There are two types: on_mob and on_obj, they can only be called on their respective typepaths. + * + * Arguments: + * * state -- The icon_state of whatever .dmi file you're attempting to use for the sprite, in "" format. Ex. "combat", not combat.dmi. + * * time -- The amount of time the sprite remains before remove_emote_popup_on_obj is called. Is used in the addtimer. + */ + +/obj/proc/flick_emote_popup_on_obj(state, time) + var/obj/effect/overlay/emote_popup/emote_overlay = new + emote_overlay.icon_state = state + vis_contents += emote_overlay + animate(emote_overlay, alpha = 255, time = 5, easing = BOUNCE_EASING, pixel_y = 10) + addtimer(CALLBACK(src, PROC_REF(remove_emote_popup_on_obj), emote_overlay), time) + +/** + * A proc that is automatically called whenever flick_emote_popup_on_mob's addtimer expires, and removes the popup. + * + * Arguments: + * * emote_overlay -- Inherits state from the preceding proc. + */ + +/mob/living/proc/remove_emote_popup_on_mob(obj/effect/overlay/emote_popup/emote_overlay) + vis_contents -= emote_overlay + qdel(emote_overlay) + return + +/** + * A proc that is automatically called whenever flick_emote_popup_on_obj's addtimer expires, and removes the popup. + * + * Arguments: + * * emote_overlay -- Inherits state from the preceding proc. + */ + +/obj/proc/remove_emote_popup_on_obj(obj/effect/overlay/emote_popup/emote_overlay) + vis_contents -= emote_overlay + qdel(emote_overlay) + return diff --git a/modular_doppler/indicators/code/sealed.dm b/modular_doppler/indicators/code/sealed.dm new file mode 100644 index 0000000000000..15b1c7f2e15ad --- /dev/null +++ b/modular_doppler/indicators/code/sealed.dm @@ -0,0 +1,17 @@ +/obj/vehicle/sealed + /// Is combat indicator on for this vehicle? Boolean. + var/combat_indicator_vehicle = FALSE + /// When is the next time this vehicle will be able to use flick_emote and put the fluff text in chat? + var/vehicle_next_combat_popup = 0 + +//Register the signal to the mob and mechs will listen for when CI is toggled, then call the parent proc, then turn on CI if the mob had CI on. +/obj/vehicle/sealed/add_occupant(mob/occupant_entering, control_flags) + RegisterSignal(occupant_entering, COMSIG_MOB_CI_TOGGLED, PROC_REF(mob_toggled_ci)) + . = ..() + handle_ci_migration(occupant_entering) + +//Unregister the signal then disable CI if the vehicle has no other drivers within it. +/obj/vehicle/sealed/remove_occupant(mob/occupant_exiting) + UnregisterSignal(occupant_exiting, COMSIG_MOB_CI_TOGGLED) + . = ..() + disable_ci(occupant_exiting) diff --git a/modular_doppler/indicators/code/ssd_indicator.dm b/modular_doppler/indicators/code/ssd_indicator.dm new file mode 100644 index 0000000000000..79bd363de019e --- /dev/null +++ b/modular_doppler/indicators/code/ssd_indicator.dm @@ -0,0 +1,38 @@ +GLOBAL_VAR_INIT(ssd_indicator_overlay, mutable_appearance('modular_doppler/indicators/icons/ssd_indicator.dmi', "default0", FLY_LAYER)) + +/mob/living + var/ssd_indicator = FALSE + var/lastclienttime = 0 + +/mob/living/proc/set_ssd_indicator(state) + if(state == ssd_indicator) + return + ssd_indicator = state + if(ssd_indicator) + add_overlay(GLOB.ssd_indicator_overlay) + log_message("has went SSD and got their indicator!", LOG_ATTACK) + else + cut_overlay(GLOB.ssd_indicator_overlay) + log_message("is no longer SSD and lost their indicator!", LOG_ATTACK) + +/mob/living/Login() + . = ..() + set_ssd_indicator(FALSE) + +/mob/living/Logout() + lastclienttime = world.time + set_ssd_indicator(TRUE) + . = ..() + +//Temporary, look below for the reason +/mob/living/ghostize(can_reenter_corpse = TRUE) + . = ..() + set_ssd_indicator(FALSE) + +/* +//EDIT - TRANSFER CKEY IS NOT A THING ON THE TG CODEBASE, if things break too bad because of it, consider implementing it +//This proc should stop mobs from having the overlay when someone keeps jumping control of mobs, unfortunately it causes Aghosts to have their character without the SSD overlay, I wasn't able to find a better proc unfortunately +/mob/living/transfer_ckey(mob/new_mob, send_signal = TRUE) + ..() + set_ssd_indicator(FALSE) +*/ diff --git a/modular_doppler/indicators/icons/DNR_trait_overlay.dmi b/modular_doppler/indicators/icons/DNR_trait_overlay.dmi new file mode 100644 index 0000000000000..950b5503c00eb Binary files /dev/null and b/modular_doppler/indicators/icons/DNR_trait_overlay.dmi differ diff --git a/modular_doppler/indicators/icons/combat_indicator.dmi b/modular_doppler/indicators/icons/combat_indicator.dmi new file mode 100644 index 0000000000000..f4987e829ba8a Binary files /dev/null and b/modular_doppler/indicators/icons/combat_indicator.dmi differ diff --git a/modular_doppler/indicators/icons/emote_indicator.dmi b/modular_doppler/indicators/icons/emote_indicator.dmi new file mode 100644 index 0000000000000..97defc8388f32 Binary files /dev/null and b/modular_doppler/indicators/icons/emote_indicator.dmi differ diff --git a/modular_doppler/indicators/icons/popup_flicks.dmi b/modular_doppler/indicators/icons/popup_flicks.dmi new file mode 100644 index 0000000000000..4224b98ac6e45 Binary files /dev/null and b/modular_doppler/indicators/icons/popup_flicks.dmi differ diff --git a/modular_doppler/indicators/icons/ssd_indicator.dmi b/modular_doppler/indicators/icons/ssd_indicator.dmi new file mode 100644 index 0000000000000..3f7d100b6c67e Binary files /dev/null and b/modular_doppler/indicators/icons/ssd_indicator.dmi differ diff --git a/modular_doppler/indicators/icons/temporary_flavor_text_indicator.dmi b/modular_doppler/indicators/icons/temporary_flavor_text_indicator.dmi new file mode 100644 index 0000000000000..9083aa431d3b5 Binary files /dev/null and b/modular_doppler/indicators/icons/temporary_flavor_text_indicator.dmi differ diff --git a/modular_doppler/indicators/icons/typing_indicator.dmi b/modular_doppler/indicators/icons/typing_indicator.dmi new file mode 100644 index 0000000000000..cbd441f7d7e86 Binary files /dev/null and b/modular_doppler/indicators/icons/typing_indicator.dmi differ diff --git a/modular_doppler/indicators/readme.md b/modular_doppler/indicators/readme.md new file mode 100644 index 0000000000000..9ec04fab0b92b --- /dev/null +++ b/modular_doppler/indicators/readme.md @@ -0,0 +1,46 @@ +https://github.com/Skyrat-SS13/Skyrat-tg/pull/9922 -- Mech CI PR from Niko + +## Title: Indicators + +MODULE ID: INDICATORS + +### Description: + +The compilation of all player indicators (CI, SSD, Typing) +Combat Indicator - Toggleable by players, declares intent to engage in combat +SSD Indicator - Automatically shows when a player has disconnected +Typing Indicator - Shows when a player is typing +Emote Popup - Added from another module because it was only by Combat Indicator + +`COMBAT_INDICATOR` was added in the configuration file `config\doppler\config_doppler.txt`. Comment out to disable the module. + + +### TG Proc Changes: +Combat Indicator + - ADDITION: code/modules/mob/living/death.dm > /mob/living/death() + - CHANGE: code/datums/keybinding/mob.dm > /datum/keybinding/mob/toggle_move_intent() +Typing Indicator + - APPEND: code/modules/keybindings/setup.dm > /datum/proc/key_down() + - ADDITION: code/onclick/_click.dm > /mob/proc/ClickOn() + - ADDITION: code/modules/mob/mob_say.dm > /mob/verb/say_verb(), /mob/verb/me_verb() + - CHANGE: code\modules\mob\living\living_say.dm > /mob/living/send_speech + - ADDITION: code/modules/mob/living/death.dm > /mob/living/death() +SSD Indicator + ./code/modules/mob/living/carbon/human/examine.dm > /mob/living/carbon/human/examine() + - ADDITION: code/modules/mob/living/death.dm > /mob/living/death() + +### Defines: + +- `code\__DEFINES\~doppler_defines\keybindings.dm` +- `code\__DEFINES\~doppler_defines\signals.dm` + +### Included files: + +N/A + +### Credits: + +Azarak - Porting and OG code for Combat Indicator, Typing Indicator, SSD Indicator +FlamingLily - Consolidation, surrender alert +Niko - making CI work with mechs and stuff +GoldenAlphrex - Being invaluably helpful in telling me (niko) how to do things diff --git a/modular_doppler/modular_antagonist/code/antag_datum.dm b/modular_doppler/modular_antagonist/code/antag_datum.dm new file mode 100644 index 0000000000000..dec9482937530 --- /dev/null +++ b/modular_doppler/modular_antagonist/code/antag_datum.dm @@ -0,0 +1,13 @@ +/datum/antagonist + /// the list of recipes that an antag will learn/unlearn on gain/loss + var/list/antag_recipes = list() + +/datum/antagonist/on_gain() + . = ..() + for(var/recipe_datum in antag_recipes) + owner.teach_crafting_recipe(recipe_datum) + +/datum/antagonist/on_removal() + . = ..() + for(var/recipe_datum in antag_recipes) + owner.unteach_crafting_recipe(recipe_datum) diff --git a/modular_doppler/modular_antagonist/readme.md b/modular_doppler/modular_antagonist/readme.md new file mode 100644 index 0000000000000..4dd62b72c6df3 --- /dev/null +++ b/modular_doppler/modular_antagonist/readme.md @@ -0,0 +1,25 @@ +## Title: Modular Antagonist + +MODULE ID: MODULAR_ANTAGONIST + +### Description: + +Extends the antag_datum with their own recipes, which the acquire when gaining the antag and forget when losing it. + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps.json new file mode 100644 index 0000000000000..09996651c19d3 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps.json @@ -0,0 +1,10 @@ +{ + "armwraps": [ + { + "type": "icon_state", + "icon_state": "armwraps_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps_worn.json new file mode 100644 index 0000000000000..657fb8160d3bb --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/armwraps_worn.json @@ -0,0 +1,10 @@ +{ + "armwraps": [ + { + "type": "icon_state", + "icon_state": "armwraps", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps.json new file mode 100644 index 0000000000000..4b212cd5f0c3b --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps.json @@ -0,0 +1,16 @@ +{ + "wraps": [ + { + "type": "icon_state", + "icon_state": "wraps_under_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "wraps_over_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps_worn.json new file mode 100644 index 0000000000000..dd5c9eac4a363 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/body_wraps_worn.json @@ -0,0 +1,16 @@ +{ + "wraps": [ + { + "type": "icon_state", + "icon_state": "wraps_under", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "wraps_over", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots.json new file mode 100644 index 0000000000000..35b58b8d76c66 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots.json @@ -0,0 +1,16 @@ +{ + "boots": [ + { + "type": "icon_state", + "icon_state": "boots_under_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "boots_cover_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots_worn.json new file mode 100644 index 0000000000000..c211d8e9a6f7d --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/boots_worn.json @@ -0,0 +1,16 @@ +{ + "boots": [ + { + "type": "icon_state", + "icon_state": "boots_under", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "boots_cover", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat.json new file mode 100644 index 0000000000000..7fa17c69efde8 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat.json @@ -0,0 +1,16 @@ +{ + "coat": [ + { + "type": "icon_state", + "icon_state": "coat_body_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "coat_fluff_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat_worn.json new file mode 100644 index 0000000000000..d137985c5f340 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/coat_worn.json @@ -0,0 +1,16 @@ +{ + "coat": [ + { + "type": "icon_state", + "icon_state": "coat_body", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "coat_fluff", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere.json new file mode 100644 index 0000000000000..24235978b872c --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere.json @@ -0,0 +1,16 @@ +{ + "ferroniere": [ + { + "type": "icon_state", + "icon_state": "ferroniere_base_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "ferroniere_gem_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere_worn.json new file mode 100644 index 0000000000000..3865a249892bc --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/ferroniere_worn.json @@ -0,0 +1,16 @@ +{ + "ferroniere": [ + { + "type": "icon_state", + "icon_state": "ferroniere_base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "ferroniere_gem", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets.json new file mode 100644 index 0000000000000..31ef0b2a1b92e --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets.json @@ -0,0 +1,16 @@ +{ + "gauntlets": [ + { + "type": "icon_state", + "icon_state": "gauntlets_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "armwraps_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets_worn.json new file mode 100644 index 0000000000000..9feedd350c676 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/gauntlets_worn.json @@ -0,0 +1,16 @@ +{ + "gauntlets": [ + { + "type": "icon_state", + "icon_state": "armwraps", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "gauntlets", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth.json new file mode 100644 index 0000000000000..3f8755911bb2e --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth.json @@ -0,0 +1,10 @@ +{ + "loincloth": [ + { + "type": "icon_state", + "icon_state": "loincloth_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt.json new file mode 100644 index 0000000000000..5982433da9914 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt.json @@ -0,0 +1,10 @@ +{ + "loincloth_alt": [ + { + "type": "icon_state", + "icon_state": "loincloth_alt_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt_worn.json new file mode 100644 index 0000000000000..b39a1ee881586 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_alt_worn.json @@ -0,0 +1,10 @@ +{ + "loincloth_alt": [ + { + "type": "icon_state", + "icon_state": "loincloth_alt", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_worn.json new file mode 100644 index 0000000000000..4eba937fde5e8 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/loincloth_worn.json @@ -0,0 +1,10 @@ +{ + "loincloth": [ + { + "type": "icon_state", + "icon_state": "loincloth", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress.json new file mode 100644 index 0000000000000..98a097909d11e --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress.json @@ -0,0 +1,16 @@ +{ + "tailored_dress": [ + { + "type": "icon_state", + "icon_state": "tailored_dress_base_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "tailored_dress_laces_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress_worn.json new file mode 100644 index 0000000000000..2a970e9f1b509 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tailored_dress_worn.json @@ -0,0 +1,16 @@ +{ + "tailored_dress": [ + { + "type": "icon_state", + "icon_state": "tailored_dress_base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "tailored_dress_laces", + "blend_mode": "overlay", + "color_ids": [ 2 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic.json new file mode 100644 index 0000000000000..170c172c4655d --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic.json @@ -0,0 +1,22 @@ +{ + "tunic": [ + { + "type": "icon_state", + "icon_state": "tunic_white_obj", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "tunic_dark_obj", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "tunic_belt_obj", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic_worn.json b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic_worn.json new file mode 100644 index 0000000000000..edfdda4535659 --- /dev/null +++ b/modular_doppler/modular_cosmetics/GAGS/json_configs/hearthkin/tunic_worn.json @@ -0,0 +1,22 @@ +{ + "tunic": [ + { + "type": "icon_state", + "icon_state": "tunic_white", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "tunic_dark", + "blend_mode": "overlay", + "color_ids": [ 2 ] + }, + { + "type": "icon_state", + "icon_state": "tunic_belt", + "blend_mode": "overlay", + "color_ids": [ 3 ] + } + ] +} diff --git a/modular_doppler/modular_cosmetics/code/hands/rings.dm b/modular_doppler/modular_cosmetics/code/hands/rings.dm new file mode 100644 index 0000000000000..6e24f4796b70a --- /dev/null +++ b/modular_doppler/modular_cosmetics/code/hands/rings.dm @@ -0,0 +1,37 @@ +/obj/item/clothing/gloves/ring + icon = 'modular_doppler/modular_cosmetics/icons/obj/hands/rings.dmi' + worn_icon = 'modular_doppler/modular_cosmetics/icons/mob/hands/rings.dmi' + lefthand_file = 'modular_doppler/modular_cosmetics/icons/mob/inhands/rings_lefthand.dmi' + righthand_file = 'modular_doppler/modular_cosmetics/icons/mob/inhands/rings_righthand.dmi' + name = "gold ring" + desc = "A tiny gold ring, sized to wrap around a finger." + gender = NEUTER + w_class = WEIGHT_CLASS_TINY + icon_state = "ringgold" + inhand_icon_state = "ringgold" + worn_icon_state = "gring" + body_parts_covered = 0 + strip_delay = 4 SECONDS + clothing_traits = list(TRAIT_FINGERPRINT_PASSTHROUGH) + +/obj/item/clothing/gloves/ring/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("\[user] is putting the [src] in [user.p_their()] mouth! It looks like [user] is trying to choke on the [src]!")) + return OXYLOSS + + +/obj/item/clothing/gloves/ring/diamond + name = "diamond ring" + desc = "An expensive ring, studded with a diamond. Cultures have used these rings in courtship for a millenia." + icon_state = "ringdiamond" + inhand_icon_state = "ringdiamond" + worn_icon_state = "dring" + +/obj/item/clothing/gloves/ring/diamond/attack_self(mob/user) + user.visible_message(span_warning("\The [user] gets down on one knee, presenting \the [src]."),span_warning("You get down on one knee, presenting \the [src].")) + +/obj/item/clothing/gloves/ring/silver + name = "silver ring" + desc = "A tiny silver ring, sized to wrap around a finger." + icon_state = "ringsilver" + inhand_icon_state = "ringsilver" + worn_icon_state = "sring" diff --git a/modular_doppler/modular_cosmetics/code/storage/rings.dm b/modular_doppler/modular_cosmetics/code/storage/rings.dm new file mode 100644 index 0000000000000..8ec270b1fc6a3 --- /dev/null +++ b/modular_doppler/modular_cosmetics/code/storage/rings.dm @@ -0,0 +1,28 @@ +/* + * Ring Box + */ + +/obj/item/storage/fancy/ringbox + name = "ring box" + desc = "A tiny box covered in soft red felt made for holding rings." + icon = 'modular_doppler/modular_cosmetics/icons/obj/storage/rings.dmi' + icon_state = "gold ringbox" + base_icon_state = "gold ringbox" + w_class = WEIGHT_CLASS_TINY + spawn_type = /obj/item/clothing/gloves/ring + spawn_count = 1 + +/obj/item/storage/fancy/ringbox/Initialize(mapload) + . = ..() + atom_storage.max_slots = 1 + atom_storage.can_hold = typecacheof(list(/obj/item/clothing/gloves/ring)) + +/obj/item/storage/fancy/ringbox/diamond + icon_state = "diamond ringbox" + base_icon_state = "diamond ringbox" + spawn_type = /obj/item/clothing/gloves/ring/diamond + +/obj/item/storage/fancy/ringbox/silver + icon_state = "silver ringbox" + base_icon_state = "silver ringbox" + spawn_type = /obj/item/clothing/gloves/ring/silver diff --git a/modular_doppler/modular_cosmetics/icons/mob/hands/rings.dmi b/modular_doppler/modular_cosmetics/icons/mob/hands/rings.dmi new file mode 100644 index 0000000000000..696cc75c32a8f Binary files /dev/null and b/modular_doppler/modular_cosmetics/icons/mob/hands/rings.dmi differ diff --git a/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_lefthand.dmi b/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_lefthand.dmi new file mode 100644 index 0000000000000..58a42f2c7f88f Binary files /dev/null and b/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_lefthand.dmi differ diff --git a/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_righthand.dmi b/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_righthand.dmi new file mode 100644 index 0000000000000..a3e3f09605aa5 Binary files /dev/null and b/modular_doppler/modular_cosmetics/icons/mob/inhands/rings_righthand.dmi differ diff --git a/modular_doppler/modular_cosmetics/icons/obj/hands/rings.dmi b/modular_doppler/modular_cosmetics/icons/obj/hands/rings.dmi new file mode 100644 index 0000000000000..9cd9d5cd81621 Binary files /dev/null and b/modular_doppler/modular_cosmetics/icons/obj/hands/rings.dmi differ diff --git a/modular_doppler/modular_cosmetics/icons/obj/storage/rings.dmi b/modular_doppler/modular_cosmetics/icons/obj/storage/rings.dmi new file mode 100644 index 0000000000000..81b89718e7537 Binary files /dev/null and b/modular_doppler/modular_cosmetics/icons/obj/storage/rings.dmi differ diff --git a/modular_doppler/modular_crafting/code/crafting_extended.dm b/modular_doppler/modular_crafting/code/crafting_extended.dm new file mode 100644 index 0000000000000..f8abb76282073 --- /dev/null +++ b/modular_doppler/modular_crafting/code/crafting_extended.dm @@ -0,0 +1,9 @@ +/** + * The opposite of proc/teach_crafting_recipe + * will attempt to remove the arg "recipe" from the learned recipes + */ +/datum/mind/proc/unteach_crafting_recipe(recipe) + if(!learned_recipes) + return + + learned_recipes &= ~recipe diff --git a/modular_doppler/modular_crafting/code/sheet_types.dm b/modular_doppler/modular_crafting/code/sheet_types.dm new file mode 100644 index 0000000000000..f9e2ef3710918 --- /dev/null +++ b/modular_doppler/modular_crafting/code/sheet_types.dm @@ -0,0 +1,143 @@ +// Add modular crafting recipes here, NOT IN BASE /tg/ CRAFTING LISTS + +/** + * Add a list of recipes to an existing recipe sublist. + * + * Arguments: + * * stack_recipes - the existing list of stack recipes. + * * recipe_list_title - the title for the recipe list we're adding to + * * appent_recipes - Add these recipes to the given recipe list. + */ +/proc/add_recipes_to_sublist(list/stack_recipes, recipe_list_title, list/append_recipes) + for(var/datum/stack_recipe_list/sublist in stack_recipes) + if(sublist.title != recipe_list_title) + continue + + sublist.recipes += append_recipes + return + + CRASH("Could not find recipe sublist [recipe_list_title] to add more recipes!") + +// Iron + +GLOBAL_LIST_INIT(doppler_metal_recipes, list( + new/datum/stack_recipe("anvil", /obj/structure/reagent_anvil, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("forge", /obj/structure/reagent_forge, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("throwing wheel", /obj/structure/throwing_wheel, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), +)) + +GLOBAL_LIST_INIT(doppler_metal_airlock_recipes, list( +)) + +/obj/item/stack/sheet/iron/get_main_recipes() + . = ..() + . += GLOB.doppler_metal_recipes + add_recipes_to_sublist(., "airlock assemblies", GLOB.doppler_metal_airlock_recipes) + +// Plasteel + +GLOBAL_LIST_INIT(doppler_plasteel_recipes, list( +)) + +/obj/item/stack/sheet/plasteel/get_main_recipes() + . = ..() + . += GLOB.doppler_plasteel_recipes + +// Rods + +GLOBAL_LIST_INIT(doppler_rod_recipes, list( + new/datum/stack_recipe("crutch", /obj/item/cane/crutch, 3, time = 1 SECONDS, category = CAT_TOOLS), + new/datum/stack_recipe("torch mount", /obj/item/wallframe/torch_mount, 2, category = CAT_MISC), +)) + +/obj/item/stack/rods/get_main_recipes() + . = ..() + . += GLOB.doppler_rod_recipes + +// Wood + +GLOBAL_LIST_INIT(doppler_wood_recipes, list( + new/datum/stack_recipe("water basin", /obj/structure/reagent_water_basin, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("forging work bench", /obj/structure/reagent_crafting_bench, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("large wooden mortar", /obj/structure/large_mortar, 10, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("wooden cutting board", /obj/item/cutting_board, 5, time = 2 SECONDS, category = CAT_TOOLS), + new/datum/stack_recipe("wooden shelf", /obj/structure/rack/wooden, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("seed shelf", /obj/machinery/smartfridge/wooden/seed_shelf, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("produce bin", /obj/machinery/smartfridge/wooden/produce_bin, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("produce display", /obj/machinery/smartfridge/wooden/produce_display, 10, time = 2 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("ration shelf", /obj/machinery/smartfridge/wooden/ration_shelf, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("storage barrel", /obj/structure/closet/crate/wooden/storage_barrel, 4, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("worm barrel", /obj/structure/wormfarm, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), + new/datum/stack_recipe("gutlunch trough", /obj/structure/ore_container/food_trough/gutlunch_trough, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("sturdy wooden fence", /obj/structure/railing/wooden_fencing, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("sturdy wooden fence gate", /obj/structure/railing/wooden_fencing/gate, 5, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("large wooden gate", /obj/structure/mineral_door/wood/large_gate, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("wooden bowl", /obj/item/reagent_containers/cup/bowl/wood_bowl, 3, time = 2 SECONDS, category = CAT_TOOLS), +)) + + +/obj/item/stack/sheet/mineral/wood/get_main_recipes() + . = ..() + . += GLOB.doppler_wood_recipes + +// Cardboard + +GLOBAL_LIST_INIT(doppler_cardboard_recipes, list( +)) + +/obj/item/stack/sheet/cardboard/get_main_recipes() + . = ..() + . += GLOB.doppler_cardboard_recipes + +// Cloth + +GLOBAL_LIST_INIT(doppler_cloth_recipes, list( + new/datum/stack_recipe("eyepatch", /obj/item/clothing/glasses/eyepatch, 2, category = CAT_CLOTHING), + new/datum/stack_recipe("xenoarch bag", /obj/item/storage/bag/xenoarch, 4, category = CAT_CONTAINERS), +)) + +/obj/item/stack/sheet/cloth/get_main_recipes() + . = ..() + . += GLOB.doppler_cloth_recipes + +// Leather + +GLOBAL_LIST_INIT(doppler_leather_recipes, list( +)) + +GLOBAL_LIST_INIT(doppler_leather_belt_recipes, list( + new/datum/stack_recipe("xenoarch belt", /obj/item/storage/belt/utility/xenoarch, 4, category = CAT_CONTAINERS), +)) + +/obj/item/stack/sheet/leather/get_main_recipes() + . = ..() + . += GLOB.doppler_leather_recipes + add_recipes_to_sublist(., "belts", GLOB.doppler_leather_belt_recipes) + +// Titanium + +GLOBAL_LIST_INIT(doppler_titanium_recipes, list( +)) + +/obj/item/stack/sheet/mineral/titanium/get_main_recipes() + . = ..() + . += GLOB.doppler_titanium_recipes + +// Snow + +GLOBAL_LIST_INIT(doppler_snow_recipes, list( +)) + +/obj/item/stack/sheet/mineral/snow/get_main_recipes() + . = ..() + . += GLOB.doppler_snow_recipes + +// Sand + +GLOBAL_LIST_INIT(doppler_sand_recipes, list( + new/datum/stack_recipe("ant farm", /obj/structure/antfarm, 20, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), +)) + +/obj/item/stack/ore/glass/get_main_recipes() + . = ..() + . += GLOB.doppler_sand_recipes diff --git a/modular_doppler/modular_crafting/readme.md b/modular_doppler/modular_crafting/readme.md new file mode 100644 index 0000000000000..9d12e928291bd --- /dev/null +++ b/modular_doppler/modular_crafting/readme.md @@ -0,0 +1,27 @@ +## Title: Modular Crafting + +MODULE ID: MODULAR_CRAFTING + +### Description: + +Module to add recipes unobtrusively without touching TG code, as well as serving as a place to add anything related to them. + +### TG Proc Changes: + +| proc | file | +| ----------------------------------- | -------------------------------- | +| /proc/make_datum_reference_lists() | code\__HELPERS\global_lists.dm | + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `code\__HELPERS\~doppler_helpers\global_lists.dm` method to initialize recipes + +### Credits: diff --git a/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm b/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm new file mode 100644 index 0000000000000..99b6fc2339b82 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/chemistry_reagents.dm @@ -0,0 +1,101 @@ +/* +/datum/reagent/fuel + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/fuel/oil + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/stable_plasma + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/pax + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/water + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/hellwater + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/carbondioxide + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + +/datum/reagent/iron + chemical_flags_nova = REAGENT_BLOOD_REGENERATING + +/datum/reagent/blood + chemical_flags_nova = REAGENT_BLOOD_REGENERATING // For Hemophages to be able to drink it without any issue. + +/datum/reagent/blood/on_new(list/data) + . = ..() + + if(!src.data["blood_type"]) + src.data["blood_type"] = random_blood_type() // This is so we don't get blood without a blood type spawned from something that doesn't explicitly set the blood type. + + + +/datum/reagent/stable_plasma/on_mob_life(mob/living/carbon/C) + if(C.mob_biotypes & MOB_ROBOTIC) + C.nutrition = min(C.nutrition + 5, NUTRITION_LEVEL_FULL-1) + ..() + +/datum/reagent/fuel/on_mob_life(mob/living/carbon/C) + if(C.mob_biotypes & MOB_ROBOTIC) + C.nutrition = min(C.nutrition + 5, NUTRITION_LEVEL_FULL-1) + ..() + +/datum/reagent/fuel/oil/on_mob_life(mob/living/carbon/C) + if(C.mob_biotypes & MOB_ROBOTIC && C.blood_volume < BLOOD_VOLUME_NORMAL) + C.blood_volume += 0.5 + ..() + +/datum/reagent/carbondioxide/on_mob_life(mob/living/carbon/C) + if(C.mob_biotypes & MOB_ROBOTIC) + C.nutrition = min(C.nutrition + 5, NUTRITION_LEVEL_FULL-1) + ..() +*/ +// Catnip +/datum/reagent/pax/catnip + name = "Catnip" + taste_description = "grass" + description = "A colourless liquid that makes people more peaceful and felines happier." + metabolization_rate = 1.75 * REAGENTS_METABOLISM + +/datum/reagent/pax/catnip/on_mob_life(mob/living/carbon/M) + if(isfelinid(M)) + if(prob(20)) + M.emote("nya") + if(prob(20)) + to_chat(M, span_notice("[pick("Headpats feel nice.", "Backrubs would be nice.", "Mew")]")) + else + to_chat(M, span_notice("[pick("I feel oddly calm.", "I feel relaxed.", "Mew?")]")) + ..() + +/* +#define DERMAGEN_SCAR_FIX_AMOUNT 10 + +/datum/reagent/medicine/dermagen + name = "Dermagen" + description = "Heals scars formed by past physical trauma when applied. Minimum 10u needed, only works when applied topically." + reagent_state = LIQUID + color = "#FFEBEB" + ph = 6 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + +/datum/reagent/medicine/dermagen/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE) + . = ..() + if(!iscarbon(exposed_mob)) + return + if(!(methods & (PATCH|TOUCH|VAPOR))) + return + var/mob/living/carbon/scarred = exposed_mob + if(scarred.stat == DEAD) + show_message = FALSE + if(show_message) + to_chat(scarred, span_danger("The scars on your body start to fade and disappear.")) + if(reac_volume >= DERMAGEN_SCAR_FIX_AMOUNT) + for(var/i in scarred.all_scars) + qdel(i) + +#undef DERMAGEN_SCAR_FIX_AMOUNT +*/ diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm new file mode 100644 index 0000000000000..8e639ef8f63b5 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/alcohol reagents.dm @@ -0,0 +1,900 @@ +/*STUFF WE CAN'T USE YET BECAUSE WE HAVEN'T PORTED THEIR PRECURSORS + +// Modular Booze REAGENTS, see the following file for the mixes: modular_nova\modules\customization\modules\food_and_drinks\recipes\drinks_recipes.dm + +/datum/reagent/consumable/ethanol/whiskey + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC //let's not force the detective to change his alcohol brand + + +/datum/reagent/consumable/ethanol/bloody_mary + chemical_flags_nova = REAGENT_BLOOD_REGENERATING +*/ + +/*SYNTHETIC DRINKS +/datum/reagent/consumable/ethanol/synthanol + name = "Synthanol" + description = "A runny liquid with conductive capacities. Its effects on synthetics are similar to those of alcohol on organics." + color = "#1BB1FF" + process_flags = REAGENT_ORGANIC | REAGENT_SYNTHETIC + boozepwr = 50 + quality = DRINK_NICE + taste_description = "motor oil" + +/datum/glass_style/drinking_glass/synthanol + required_drink_type = /datum/reagent/consumable/ethanol/synthanol + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "synthanolglass" + name = "glass of synthanol" + desc = "The equivalent of alcohol for synthetic crewmembers. They'd find it awful if they had tastebuds too." + +/datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + if(!(affected_mob.mob_biotypes & MOB_ROBOTIC)) + affected_mob.reagents.remove_reagent(type, 3.6 * REM * seconds_per_tick) //gets removed from organics very fast + if(prob(25)) + affected_mob.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 5) + return ..() + +/datum/reagent/consumable/ethanol/synthanol/expose_mob(mob/living/carbon/C, method=TOUCH, volume) + . = ..() + if(C.mob_biotypes & MOB_ROBOTIC) + return + if(method == INGEST) + to_chat(C, pick(span_danger("That was awful!"), span_danger("That was disgusting!"))) + +/datum/reagent/consumable/ethanol/synthanol/robottears + name = "Robot Tears" + description = "An oily substance that an IPC could technically consider a 'drink'." + color = "#363636" + quality = DRINK_GOOD + boozepwr = 25 + taste_description = "existential angst" + +/datum/glass_style/drinking_glass/synthanol/robottears + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/robottears + icon_state = "robottearsglass" + name = "glass of robot tears" + desc = "No robots were hurt in the making of this drink." + +/datum/reagent/consumable/ethanol/synthanol/trinary + name = "Trinary" + description = "A fruit drink meant only for synthetics, however that works." + color = "#ADB21f" + quality = DRINK_GOOD + boozepwr = 20 + taste_description = "modem static" + +/datum/glass_style/drinking_glass/synthanol/trinary + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/trinary + icon_state = "trinaryglass" + name = "glass of trinary" + desc = "Colorful drink made for synthetic crewmembers. It doesn't seem like it would taste well." + +/datum/reagent/consumable/ethanol/synthanol/servo + name = "Servo" + description = "A drink containing some organic ingredients, but meant only for synthetics." + color = "#5B3210" + quality = DRINK_GOOD + boozepwr = 25 + taste_description = "motor oil and cocoa" + +/datum/glass_style/drinking_glass/synthanol/servo + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/servo + icon_state = "servoglass" + name = "glass of servo" + desc = "Chocolate - based drink made for IPCs. Not sure if anyone's actually tried out the recipe." + +/datum/reagent/consumable/ethanol/synthanol/uplink + name = "Uplink" + description = "A potent mix of alcohol and synthanol. Will only work on synthetics." + color = "#E7AE04" + quality = DRINK_GOOD + boozepwr = 15 + taste_description = "a GUI in visual basic" + +/datum/glass_style/drinking_glass/synthanol/uplink + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/uplink + icon_state = "uplinkglass" + name = "glass of uplink" + desc = "An exquisite mix of the finest liquoirs and synthanol. Meant only for synthetics." + +/datum/reagent/consumable/ethanol/synthanol/synthncoke + name = "Synth 'n Coke" + description = "The classic drink adjusted for a robot's tastes." + color = "#7204E7" + quality = DRINK_GOOD + boozepwr = 25 + taste_description = "fizzy motor oil" + +/datum/glass_style/drinking_glass/synthanol/synthncoke + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/synthncoke + icon_state = "synthncokeglass" + name = "glass of synth 'n coke" + desc = "Classic drink altered to fit the tastes of a robot, contains de-rustifying properties. Bad idea to drink if you're made of carbon." + +/datum/reagent/consumable/ethanol/synthanol/synthignon + name = "Synthignon" + description = "Someone mixed wine and alcohol for robots. Hope you're proud of yourself." + color = "#D004E7" + quality = DRINK_GOOD + boozepwr = 25 + taste_description = "fancy motor oil" + +/datum/glass_style/drinking_glass/synthanol/synthignon + required_drink_type = /datum/reagent/consumable/ethanol/synthanol/synthignon + icon_state = "synthignonglass" + name = "glass of synthignon" + desc = "Someone mixed good wine and robot booze. Romantic, but atrocious." +*/ +// Other Booze + +/datum/reagent/consumable/ethanol/hot_toddy + name = "Hot Toddy" + description = "An old fashioned cocktail made of honey, rum, and tea." + color = "#e4830d" + boozepwr = 40 + quality = DRINK_GOOD + taste_description = "sweet spiced tea" + +/datum/glass_style/drinking_glass/hot_toddy + required_drink_type = /datum/reagent/consumable/ethanol/hot_toddy + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "hot_toddy" + name = "hot toddy glass" + desc = "An old fashioned cocktail made of honey, rum, and tea, it tastes like sweet holiday spices." + +/datum/reagent/consumable/ethanol/hellfire + name = "Hellfire" + description = "A nice drink that isn't quite as hot as it looks." + color = "#fb2203" + boozepwr = 60 + quality = DRINK_VERYGOOD + taste_description = "cold flames that lick at the top of your mouth" + +/datum/glass_style/drinking_glass/hellfire + required_drink_type = /datum/reagent/consumable/ethanol/hellfire + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "hellfire" + name = "glass of hellfire" + desc = "An amber colored drink that isn't quite as hot as it looks." + +/datum/reagent/consumable/ethanol/hellfire/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.adjust_bodytemperature(30 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, 0, BODYTEMP_NORMAL + 30) + +/datum/reagent/consumable/ethanol/sins_delight + name = "Sin's Delight" + description = "The drink smells like the seven sins." + color = "#330000" + boozepwr = 66 + quality = DRINK_FANTASTIC + taste_description = "overpowering sweetness with a touch of sourness, followed by iron and the sensation of a warm summer breeze" +// chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING //component drink is demon's blood, thus this drink is made with blood so hemophages can comfortably drink it + +/datum/glass_style/drinking_glass/sins_delight + required_drink_type = /datum/reagent/consumable/ethanol/sins_delight + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "sins_delight" + name = "glass of sin's delight" + desc = "You can smell the seven sins rolling off the top of the glass." + +/datum/reagent/consumable/ethanol/strawberry_daiquiri + name = "Strawberry Daiquiri" + description = "Pink looking alcoholic drink." + boozepwr = 20 + color = "#FF4A74" + quality = DRINK_NICE + taste_description = "sweet strawberry, lime and the ocean breeze" + +/datum/glass_style/drinking_glass/strawberry_daiquiri + required_drink_type = /datum/reagent/consumable/ethanol/strawberry_daiquiri + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "strawberry_daiquiri" + name = "glass of strawberry daiquiri" + desc = "Pink looking drink with flowers and a big straw to sip it. Looks sweet and refreshing, perfect for warm days." + +/datum/reagent/consumable/ethanol/liz_fizz + name = "Liz Fizz" + description = "Triple citrus layered with some ice and cream." + boozepwr = 0 + color = "#D8FF59" + quality = DRINK_NICE + taste_description = "brain freezing sourness" + +/datum/glass_style/drinking_glass/liz_fizz + required_drink_type = /datum/reagent/consumable/ethanol/liz_fizz + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "liz_fizz" + name = "glass of liz fizz" + desc = "Looks like a citrus sherbet seperated in layers? Why would anyone want that is beyond you." + +/datum/reagent/consumable/ethanol/miami_vice + name = "Miami Vice" + description = "A drink layering Pina Colada and Strawberry Daiquiri" + boozepwr = 30 + color = "#D8FF59" + quality = DRINK_FANTASTIC + taste_description = "sweet and refreshing flavor, complemented with strawberries and coconut, and hints of citrus" + +/datum/glass_style/drinking_glass/miami_vice + required_drink_type = /datum/reagent/consumable/ethanol/miami_vice + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "miami_vice" + name = "glass of miami vice" + desc = "Strawberries and coconut, like yin and yang." + +/datum/reagent/consumable/ethanol/malibu_sunset + name = "Malibu Sunset" + description = "A drink consisting of creme de coconut and tropical juices" + boozepwr = 20 + color = "#FF9473" + quality = DRINK_VERYGOOD + taste_description = "coconut, with orange and grenadine accents" + +/datum/glass_style/drinking_glass/malibu_sunset + required_drink_type = /datum/reagent/consumable/ethanol/malibu_sunset + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "malibu_sunset" + name = "glass of malibu sunset" + desc = "Tropical looking drinks, with ice cubes hovering on the surface and grenadine coloring the bottom." + +/datum/reagent/consumable/ethanol/hotlime_miami + name = "Hotlime Miami" + description = "The essence of the 90's, if they were a bloody mess that is." + boozepwr = 40 + color = "#A7FAE8" + quality = DRINK_FANTASTIC + taste_description = "coconut and aesthetic violence" + +/datum/glass_style/drinking_glass/hotlime_miami + required_drink_type = /datum/reagent/consumable/ethanol/hotlime_miami + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "hotlime_miami" + name = "glass of hotlime miami" + desc = "This looks very aesthetically pleasing." + +/datum/reagent/consumable/ethanol/hotlime_miami/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.set_drugginess(1.5 MINUTES * REM * seconds_per_tick) + if(affected_mob.adjustStaminaLoss(-2 * REM * seconds_per_tick, updating_stamina = FALSE)) + return UPDATE_MOB_HEALTH + +/datum/reagent/consumable/ethanol/coggrog + name = "Cog Grog" + description = "Now you can fill yourself with the power of Ratvar!" + color = rgb(255, 201, 49) + boozepwr = 10 + quality = DRINK_FANTASTIC + taste_description = "a brass taste with a hint of oil" + +/datum/glass_style/drinking_glass/coggrog + required_drink_type = /datum/reagent/consumable/ethanol/coggrog + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "coggrog" + name = "glass of cog grog" + desc = "Not even Ratvar's Four Generals could withstand this! Qevax Jryy!" + +/datum/reagent/consumable/ethanol/badtouch + name = "Bad Touch" + description = "A sour and vintage drink. Some say the inventor gets slapped a lot." + color = rgb(31, 181, 99) + boozepwr = 35 + quality = DRINK_GOOD + taste_description = "a slap to the face" + +/datum/glass_style/drinking_glass/badtouch + required_drink_type = /datum/reagent/consumable/ethanol/badtouch + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "badtouch" + name = "glass of bad touch" + desc = "We're nothing but mammals after all." + +/datum/reagent/consumable/ethanol/marsblast + name = "Marsblast" + description = "A spicy and manly drink in honor of the first colonists on Mars." + color = rgb(246, 143, 55) + boozepwr = 70 + quality = DRINK_FANTASTIC + taste_description = "hot red sand" + +/datum/glass_style/drinking_glass/marsblast + required_drink_type = /datum/reagent/consumable/ethanol/marsblast + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "marsblast" + name = "glass of marsblast" + desc = "One of these is enough to leave your face as red as the planet." + +/datum/reagent/consumable/ethanol/mercuryblast + name = "Mercuryblast" + description = "A sour burningly cold drink that's sure to chill the drinker." + color = rgb(29, 148, 213) + boozepwr = 40 + quality = DRINK_VERYGOOD + taste_description = "chills down your spine" + +/datum/glass_style/drinking_glass/mercuryblast + required_drink_type = /datum/reagent/consumable/ethanol/mercuryblast + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "mercuryblast" + name = "glass of mercuryblast" + desc = "No thermometers were harmed in the creation of this drink" + +/datum/reagent/consumable/ethanol/mercuryblast/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + affected_mob.adjust_bodytemperature(-30 * TEMPERATURE_DAMAGE_COEFFICIENT * REM * seconds_per_tick, T0C) + +/datum/reagent/consumable/ethanol/piledriver + name = "Piledriver" + description = "A bright drink that leaves you with a burning sensation." + color = rgb(241, 146, 59) + boozepwr = 45 + quality = DRINK_NICE + taste_description = "a fire in your throat" + +/datum/glass_style/drinking_glass/piledriver + required_drink_type = /datum/reagent/consumable/ethanol/piledriver + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "piledriver" + name = "glass of piledriver" + desc = "Not the only thing to leave your throat sore." + +/datum/reagent/consumable/ethanol/zenstar + name = "Zen Star" + description = "A sour and bland drink, rather disappointing." + color = rgb(51, 87, 203) + boozepwr = 35 + quality = DRINK_NICE + taste_description = "disappointment" + +/datum/glass_style/drinking_glass/zenstar + required_drink_type = /datum/reagent/consumable/ethanol/zenstar + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "zenstar" + name = "glass of zen star" + desc = "You'd think something so balanced would actually taste nice... you'd be dead wrong." + + +/* MORE RACE SPECIFIC STUFF WE DON'T HAVE SUPPORT FOR YET +// RACE SPECIFIC DRINKS + +/datum/reagent/consumable/ethanol/coldscales + name = "Coldscales" + color = "#5AEB52" //(90, 235, 82) + description = "A cold looking drink made for people with scales." + boozepwr = 50 //strong! + taste_description = "dead flies" + +/datum/glass_style/drinking_glass/coldscales + required_drink_type = /datum/reagent/consumable/ethanol/coldscales + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "coldscales" + name = "glass of coldscales" + desc = "A soft green drink that looks inviting!" + +/datum/reagent/consumable/ethanol/coldscales/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(islizard(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/oil_drum + name = "Oil Drum" + color = "#000000" //(0, 0, 0) + description = "Industrial grade oil mixed with some ethanol to make it a drink. Somehow not known to be toxic." + boozepwr = 45 + taste_description = "oil spill" + +/datum/glass_style/drinking_glass/oil_drum + required_drink_type = /datum/reagent/consumable/ethanol/oil_drum + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "oil_drum" + name = "drum of oil" + desc = "A gray can of booze and oil..." + +/datum/reagent/consumable/ethanol/oil_drum/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(MOB_ROBOTIC) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/nord_king + name = "Nord King" + color = "#EB1010" //(235, 16, 16) + description = "Strong mead mixed with more honey and ethanol. Beloved by its human patrons." + boozepwr = 50 //strong! + taste_description = "honey and red wine" + chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING + +/datum/glass_style/drinking_glass/nord_king + required_drink_type = /datum/reagent/consumable/ethanol/nord_king + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "nord_king" + name = "keg of nord king" + desc = "A dripping keg of red mead." + +/datum/reagent/consumable/ethanol/nord_king/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(ishumanbasic(exposed_mob) || isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/velvet_kiss + name = "Velvet Kiss" + color = "#EB1010" //(235, 16, 16) + description = "A bloody drink mixed with wine." + boozepwr = 10 //weak + taste_description = "iron with grapejuice" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING + +/datum/glass_style/drinking_glass/velvet_kiss + required_drink_type = /datum/reagent/consumable/ethanol/velvet_kiss + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "velvet_kiss" + name = "glass of velvet kiss" + desc = "Red and white drink for the upper classes or undead." + +/datum/reagent/consumable/ethanol/velvet_kiss/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(iszombie(exposed_mob) || isvampire(exposed_mob) || isdullahan(exposed_mob) || ishemophage(exposed_mob)) //Rare races! + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/velvet_kiss/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() + if(drinker.blood_volume < BLOOD_VOLUME_NORMAL) + drinker.blood_volume = min(drinker.blood_volume + (1 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL) //Same as Bloody Mary, as it is roughly the same difficulty to make. Gives hemophages a bit more choices to supplant their blood levels. + +/datum/reagent/consumable/ethanol/abduction_fruit + name = "Abduction Fruit" + color = "#DEFACD" //(222, 250, 205) + description = "Mixing of juices to make an alien taste." + boozepwr = 80 //Strong + taste_description = "grass and lime" + +/datum/glass_style/drinking_glass/abduction_fruit + required_drink_type = /datum/reagent/consumable/ethanol/abduction_fruit + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "abduction_fruit" + name = "glass of abduction fruit" + desc = "Mixed fruits that were never meant to be mixed..." + +/datum/reagent/consumable/ethanol/abduction_fruit/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isabductor(exposed_mob) || isxenohybrid(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/bug_zapper + name = "Bug Zapper" + color = "#F5882A" //(222, 250, 205) + description = "Copper and lemon juice. Hardly even a drink." + boozepwr = 5 //No booze really + taste_description = "copper and AC power" + +/datum/glass_style/drinking_glass/bug_zapper + required_drink_type = /datum/reagent/consumable/ethanol/bug_zapper + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "bug_zapper" + name = "glass of bug zapper" + desc = "An odd mix of copper, lemon juice and power meant for non-human consumption." + +/datum/reagent/consumable/ethanol/bug_zapper/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isinsect(exposed_mob) || isflyperson(exposed_mob) || ismoth(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/mush_crush + name = "Mush Crush" + color = "#F5882A" //(222, 250, 205) + description = "Soil in a glass." + boozepwr = 5 //No booze really + taste_description = "dirt and iron" + +/datum/glass_style/drinking_glass/mush_crush + required_drink_type = /datum/reagent/consumable/ethanol/mush_crush + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "mush_crush" + name = "glass of mush crush" + desc = "Popular among people that want to grow their own food rather than drink the soil." + +/datum/reagent/consumable/ethanol/mush_crush/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(ispodperson(exposed_mob) || issnail(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/hollow_bone + name = "Hollow Bone" + color = "#FCF7D4" //(252, 247, 212) + description = "Shockingly none-harmful mix of toxins and milk." + boozepwr = 15 + taste_description = "Milk and salt" + +/datum/glass_style/drinking_glass/hollow_bone + required_drink_type = /datum/reagent/consumable/ethanol/hollow_bone + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "hollow_bone" + name = "skull of hollow bone" + desc = "Mixing of milk and bone hurting juice for enjoyment for rather skinny people." + +/datum/reagent/consumable/ethanol/hollow_bone/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isplasmaman(exposed_mob) || isskeleton(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() + +/datum/reagent/consumable/ethanol/jell_wyrm + name = "Jell Wyrm" + color = "#FF6200" //(255, 98, 0) + description = "Horrible mix of CO2, toxins, and heat. Meant for slime based life." + boozepwr = 40 + taste_description = "tropical sea" + +/datum/glass_style/drinking_glass/jell_wyrm + required_drink_type = /datum/reagent/consumable/ethanol/jell_wyrm + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "jell_wyrm" + name = "glass of jell wyrm" + desc = "A bubbly drink that is rather inviting to those that don't know who it's meant for." + +/datum/reagent/consumable/ethanol/jell_wyrm/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(prob(20)) + if(affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, updating_health = FALSE)) + return UPDATE_MOB_HEALTH + +#define JELLWYRM_DISGUST 25 + +/datum/reagent/consumable/ethanol/jell_wyrm/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isjellyperson(exposed_mob) || isslimeperson(exposed_mob) || isluminescent(exposed_mob)) + quality = RACE_DRINK + else //if youre not a slime, jell wyrm should be GROSS + exposed_mob.adjust_disgust(JELLWYRM_DISGUST) + return ..() + +#undef JELLWYRM_DISGUST + +/datum/reagent/consumable/ethanol/laval_spit //Yes Laval + name = "Laval Spit" + color = "#DE3009" //(222, 48, 9) + description = "Heat minerals and some mauna loa. Meant for rock based life." + boozepwr = 30 + taste_description = "tropical island" + +/datum/glass_style/drinking_glass/laval_spit + required_drink_type = /datum/reagent/consumable/ethanol/laval_spit + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "laval_spit" + name = "glass of laval spit" + desc = "Piping hot drink for those who can stomach the heat of lava." + +/datum/reagent/consumable/ethanol/laval_spit/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isgolem(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..() +*/ + +/datum/reagent/consumable/ethanol/frisky_kitty + name = "Frisky Kitty" + color = "#FCF7D4" //(252, 247, 212) + description = "Warm milk mixed with catnip." + boozepwr = 0 + taste_description = "Warm milk and catnip" + +/datum/glass_style/drinking_glass/frisky_kitty + required_drink_type = /datum/reagent/consumable/ethanol/frisky_kitty + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "frisky_kitty" + name = "cup of frisky kitty" + desc = "Warm milk and some catnip." + +/*/datum/reagent/consumable/ethanol/frisky_kitty/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isfelinid(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..()*/ + +/* +/datum/reagent/consumable/ethanol/bloodshot_base + name = "Bloodshot Base" + description = "The bootleg blend of nutrients and alcohol that goes into making Bloodshots. Doesn't taste too great on its own, for Hemophages at least." + color = "#c29ca1" + boozepwr = 25 // Still more concentrated than in Bloodshot. + taste_description = "nutritious mix with an alcoholic kick to it" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + + +/datum/reagent/consumable/ethanol/bloodshot + name = "Bloodshot" + description = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." + color = "#a30000" + boozepwr = 20 // The only booze in it is Bloody Mary + taste_description = "blood filled to the brim with nutrients of all kinds" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags_skyrat = REAGENT_BLOOD_REGENERATING + + +/datum/glass_style/drinking_glass/bloodshot + required_drink_type = /datum/reagent/consumable/ethanol/bloodshot + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "bloodshot" + name = "glass of bloodshot" + desc = "The history of the 'Bloodshot' is based in a mix of flavor-neutral chems devised to help deliver nutrients to a Hemophage's tumorous organs. Due to the expense of the real thing and the clinical nature of it, this liquor has been designed as a 'improvised' alternative; though, still tasting like a hangover cure. It smells like iron, giving a clue to the key ingredient." + + +#define BLOODSHOT_DISGUST 25 + +/datum/reagent/consumable/ethanol/bloodshot/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(ishemophage(exposed_mob)) + quality = RACE_DRINK + + else if(exposed_mob.blood_volume < exposed_mob.blood_volume_normal) + quality = DRINK_GOOD + + if(!quality) // Basically, you don't have a reason to want to have this in your system, it doesn't taste good to you! + exposed_mob.adjust_disgust(BLOODSHOT_DISGUST) + + return ..() + +#undef BLOODSHOT_DISGUST + +/datum/reagent/consumable/ethanol/bloodshot/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() + if(drinker.blood_volume < drinker.blood_volume_normal) + drinker.blood_volume = max(drinker.blood_volume, min(drinker.blood_volume + (3 * REM * seconds_per_tick), BLOOD_VOLUME_NORMAL)) //Bloodshot quickly restores blood loss. + +/datum/reagent/consumable/ethanol/blizzard_brew + name = "Blizzard Brew" + description = "An ancient recipe. Served best chilled as much as dwarvenly possible." + color = rgb(180, 231, 216) + boozepwr = 25 + metabolization_rate = 1.25 * REAGENTS_METABOLISM + taste_description = "ancient icicles" + overdose_threshold = 25 + var/obj/structure/ice_stasis/cube + var/atom/movable/screen/alert/status_effect/freon/cryostylane_alert + +/datum/glass_style/drinking_glass/blizzard_brew + required_drink_type = /datum/reagent/consumable/ethanol/blizzard_brew + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "blizzard_brew" + name = "glass of Blizzard Brew" + desc = "An ancient recipe. Served best chilled as much as dwarvenly possible." + +/datum/reagent/consumable/ethanol/blizzard_brew/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_NICE + return ..() + +/datum/reagent/consumable/ethanol/blizzard_brew/overdose_start(mob/living/carbon/drinker) + . = ..() + cube = new /obj/structure/ice_stasis(get_turf(drinker)) + cube.color = COLOR_CYAN + cube.set_anchored(TRUE) + drinker.forceMove(cube) + cryostylane_alert = drinker.throw_alert("cryostylane_alert", /atom/movable/screen/alert/status_effect/freon/cryostylane) + cryostylane_alert.attached_effect = src //so the alert can reference us, if it needs to + +/datum/reagent/consumable/ethanol/blizzard_brew/on_mob_delete(mob/living/carbon/drinker, amount) + QDEL_NULL(cube) + drinker.clear_alert("cryostylane_alert") + return ..() + +/datum/reagent/consumable/ethanol/molten_mead + name = "Molten Mead" + description = "Famously known to set beards aflame. Ingest at your own risk!" + color = rgb(224, 78, 16) + boozepwr = 35 + metabolization_rate = 1.25 * REAGENTS_METABOLISM + taste_description = "burning wasps" + overdose_threshold = 25 + +/datum/glass_style/drinking_glass/molten_mead + required_drink_type = /datum/reagent/consumable/ethanol/molten_mead + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "molten_mead" + name = "glass of Molten Mead" + desc = "Famously known to set beards aflame. Ingest at your own risk!" + +/datum/reagent/consumable/ethanol/molten_mead/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_VERYGOOD + return ..() + +/datum/reagent/consumable/ethanol/molten_mead/overdose_start(mob/living/carbon/drinker) + drinker.adjust_fire_stacks(2) + drinker.ignite_mob() + ..() +*/ +/datum/reagent/consumable/ethanol/hippie_hooch + name = "Hippie Hooch" + description = "Peace and love! Under request of the HR department, this drink is sure to sober you up quickly." + color = rgb(77, 138, 34) + boozepwr = -20 + taste_description = "eggy hemp" + var/static/list/status_effects_to_clear = list( + /datum/status_effect/confusion, + /datum/status_effect/dizziness, + /datum/status_effect/drowsiness, + /datum/status_effect/speech/slurring/drunk, + ) + +/datum/glass_style/drinking_glass/hippie_hooch + required_drink_type = /datum/reagent/consumable/ethanol/hippie_hooch + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "hippie_hooch" + name = "glass of Hippie Hooch" + desc = "Peace and love! Under request of the HR department, this drink is sure to sober you up quickly." + +/*/datum/reagent/consumable/ethanol/hippie_hooch/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_FANTASTIC + return ..()*/ + +/datum/reagent/consumable/ethanol/hippie_hooch/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + for(var/effect in status_effects_to_clear) + affected_mob.remove_status_effect(effect) + affected_mob.reagents.remove_reagent(/datum/reagent/consumable/ethanol, 3 * REM * seconds_per_tick, include_subtypes = TRUE) + . = ..() + if(affected_mob.adjustToxLoss(-0.2 * REM * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) + . = UPDATE_MOB_HEALTH + affected_mob.adjust_drunk_effect(-10 * REM * seconds_per_tick) + +/datum/reagent/consumable/ethanol/research_rum + name = "Research Rum" + description = "Cooked up by dwarven scientists, this glowing pink brew is sure to supercharge your thinking. How? Science!" + color = rgb(169, 69, 169) + boozepwr = 50 + taste_description = "slippery grey matter" + +/datum/glass_style/drinking_glass/research_rum + required_drink_type = /datum/reagent/consumable/ethanol/research_rum + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "research_rum" + name = "glass of Research Rum" + desc = "Cooked up by dwarven scientists, this glowing pink brew is sure to supercharge your thinking. How? Science!" + +/*/datum/reagent/consumable/ethanol/research_rum/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_GOOD + return ..()*/ + +/datum/reagent/consumable/ethanol/research_rum/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) + . = ..() + if(prob(5)) + drinker.say(pick_list_replacements(VISTA_FILE, "ballmer_good_msg"), forced = "ballmer") + +/datum/reagent/consumable/ethanol/golden_grog + name = "Golden Grog" + description = "A drink concocted by a dwarven Quartermaster who had too much time and money on his hands. Commonly ordered by influencers looking to flaunt their wealth." + color = rgb(247, 230, 141) + boozepwr = 70 + taste_description = "sweet credit holochips" + +/datum/glass_style/drinking_glass/golden_grog + required_drink_type = /datum/reagent/consumable/ethanol/golden_grog + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "golden_grog" + name = "glass of Golden Grog" + desc = "A drink concocted by a dwarven Quartermaster who had too much time and money on his hands. Commonly ordered by influencers looking to flaunt their wealth." + +/*/datum/reagent/consumable/ethanol/golden_grog/expose_mob(mob/living/exposed_mob, methods, reac_volume) + if(isdwarf(exposed_mob)) + quality = RACE_DRINK + else + quality = DRINK_FANTASTIC + return ..()*/ + +// RACIAL DRINKS END + +/datum/reagent/consumable/ethanol/appletini + name = "Appletini" + color = "#9bd1a9" //(155, 209, 169) + description = "The electric-green appley beverage nobody can turn down!" + boozepwr = 50 + taste_description = "Sweet and green" + quality = DRINK_GOOD + +/datum/glass_style/drinking_glass/appletini + required_drink_type = /datum/reagent/consumable/ethanol/appletini + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "appletini" + name = "glass of appletini" + desc = "An appley beverage in a martini glass" + +/datum/reagent/consumable/ethanol/quadruple_sec/cityofsin //making this a subtype was some REAL JANK, but it saves me a headache, and it looks good! + name = "City of Sin" + color = "#eb9378" //(235, 147, 120) + description = "A smooth, fancy drink for people of ill repute" + boozepwr = 70 + taste_description = "Your own sins" + quality = DRINK_VERYGOOD //takes extra effort + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + +/datum/glass_style/drinking_glass/cityofsin + required_drink_type = /datum/reagent/consumable/ethanol/quadruple_sec/cityofsin + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "cityofsin" + name = "glass of city of sin" + desc = "Looking at it makes you recall every mistake you've made." + +/datum/reagent/consumable/ethanol/shakiri + name = "Shakiri" + description = "A sweet, fragrant red drink made from fermented kiri fruits. It seems to gently sparkle when exposed to light." + boozepwr = 45 + color = "#cf3c3c" + quality = DRINK_GOOD + taste_description = "delicious liquified jelly" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + +/datum/glass_style/drinking_glass/shakiri + required_drink_type = /datum/reagent/consumable/ethanol/shakiri + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "shakiri" + name = "glass of shakiri" + desc = "A sweet, fragrant red drink made from fermented kiri fruits. It seems to gently sparkle when exposed to light." + +/datum/reagent/consumable/ethanol/shakiri_spritz + name = "Shakiri Spritz" + description = "A carbonated cocktail made from shakiri and orange juice with soda water." + color = "#cf863c" + quality = DRINK_GOOD + boozepwr = 45 + taste_description = "tangy, carbonated sweetness" + +/datum/glass_style/drinking_glass/shakiri_spritz + required_drink_type = /datum/reagent/consumable/ethanol/shakiri_spritz + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "shakiri_spritz" + name = "glass of shakiri spritz" + desc = "A carbonated cocktail made from shakiri and orange juice with soda water." + +/datum/reagent/consumable/ethanol/crimson_hurricane + name = "Crimson Hurricane" + description = "A strong, citrusy cocktail of human origin, now made with shakiri and kiri jelly for a delightfully sweet drink." + color = "#b86637" + quality = DRINK_VERYGOOD + boozepwr = 60 + taste_description = "thick, fruity sweetness with a punch" + +/datum/glass_style/drinking_glass/crimson_hurricane + required_drink_type = /datum/reagent/consumable/ethanol/crimson_hurricane + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "crimson_hurricane" + name = "glass of crimson hurricane" + desc = "A strong, citrusy cocktail of human origin, now with shakiri and kiri jelly for a delightfully sweet drink." + +/datum/reagent/consumable/ethanol/shakiri_rogers + name = "Shakiri Rogers" + description = "A take on the classic Roy Rogers, with shakiri instead of grenadine. Sweet and refreshing." + color = "#6F2B1A" + quality = DRINK_GOOD + boozepwr = 45 + taste_description = "fruity, carbonated soda with a slight kick" + +/datum/glass_style/drinking_glass/shakiri_rogers + required_drink_type = /datum/reagent/consumable/ethanol/shakiri_rogers + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "shakiri_rogers" + name = "glass of shakiri rogers" + desc = "A take on the classic Roy Rogers, with shakiri instead of grenadine. Sweet and refreshing." diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drink_reagents.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drink_reagents.dm new file mode 100644 index 0000000000000..062854fc73b3d --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drink_reagents.dm @@ -0,0 +1,79 @@ +/datum/reagent/consumable/pinkmilk + name = "Strawberry Milk" + description = "A drink of a bygone era of milk and artificial sweetener back on a rock." + color = "#f76aeb"//rgb(247, 106, 235) + quality = DRINK_VERYGOOD + taste_description = "sweet strawberry and milk cream" + +/datum/glass_style/drinking_glass/pinkmilk + required_drink_type = /datum/reagent/consumable/pinkmilk + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "pinkmilk" + name = "tall glass of strawberry milk" + desc = "Delicious flavored strawberry syrup mixed with milk." + +/datum/reagent/consumable/pinkmilk/on_mob_life(mob/living/carbon/M) + if(prob(15)) + to_chat(M, span_notice("[pick("You cant help to smile.","You feel nostalgia all of sudden.","You remember to relax.")]")) + ..() + . = 1 + +/datum/reagent/consumable/pinktea + name = "Strawberry Tea" + description = "A timeless classic!" + color = "#f76aeb"//rgb(247, 106, 235) + quality = DRINK_VERYGOOD + taste_description = "sweet tea with a hint of strawberry" + +/datum/glass_style/drinking_glass/pinktea + required_drink_type = /datum/reagent/consumable/pinktea + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "pinktea" + name = "mug of strawberry tea" + desc = "Delicious traditional tea flavored with strawberries." + +/datum/reagent/consumable/pinktea/on_mob_life(mob/living/carbon/M) + if(prob(10)) + to_chat(M, span_notice("[pick("Diamond skies where white deer fly.","Sipping strawberry tea.","Silver raindrops drift through timeless, Neverending June.","Crystal ... pearls free, with love!","Beaming love into me.")]")) + ..() + . = TRUE + +/datum/reagent/consumable/catnip_tea + name = "Catnip Tea" + description = "A sleepy and tasty catnip tea!" + color = "#101000" // rgb: 16, 16, 0 + taste_description = "sugar and catnip" + +/datum/glass_style/drinking_glass/catnip_tea + required_drink_type = /datum/reagent/consumable/catnip_tea + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = "catnip_tea" + name = "glass of catnip tea" + desc = "A purrfect drink for a cat." + +/datum/reagent/consumable/catnip_tea/on_mob_life(mob/living/carbon/M) + M.adjustStaminaLoss(min(50 - M.getStaminaLoss(), 3)) + if(isfelinid(M)) + if(prob(20)) + M.emote("nya") + if(prob(20)) + to_chat(M, span_notice("[pick("Headpats feel nice.", "Backrubs would be nice.", "Mew")]")) + else + to_chat(M, span_notice("[pick("I feel oddly calm.", "I feel relaxed.", "Mew?")]")) + ..() + +/datum/reagent/consumable/ethanol/beerbatter + name = "Beer Batter" + description = "Probably not the greatest idea to drink...sludge." + color = "#f5f4e9" + nutriment_factor = 2 * REAGENTS_METABOLISM + taste_description = "flour and cheap booze" + boozepwr = 8 // beer diluted at about a 1:3 ratio + ph = 6 + +/datum/glass_style/drinking_glass/beerbatter + required_drink_type = /datum/reagent/consumable/ethanol/beerbatter + icon = 'icons/obj/drinks/shakes.dmi' + icon_state = "chocolatepudding" + name = "glass of beer batter" + desc = "Used in cooking, pure cholesterol, Scottish people eat it." diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks.dm new file mode 100644 index 0000000000000..882f440c220c7 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks.dm @@ -0,0 +1,87 @@ +/// How much fizziness is added to the can of soda by throwing it, in percentage points +#define SODA_FIZZINESS_THROWN 15 +/// How much fizziness is added to the can of soda by shaking it, in percentage points +#define SODA_FIZZINESS_SHAKE 5 + +/obj/item/reagent_containers/cup/soda_cans/doppler + icon = 'modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi' + icon_state = null + +/obj/item/reagent_containers/cup/soda_cans/doppler/attack(mob/M, mob/living/user) + if(istype(M, /mob/living/carbon) && !reagents.total_volume && user.combat_mode && user.zone_selected == BODY_ZONE_HEAD) + if(M == user) + user.visible_message(span_warning("[user] crushes the can of [src] on [user.p_their()] forehead!"), span_notice("You crush the can of [src] on your forehead.")) + else + user.visible_message(span_warning("[user] crushes the can of [src] on [M]'s forehead!"), span_notice("You crush the can of [src] on [M]'s forehead.")) + playsound(M,'sound/weapons/pierce.ogg', rand(10,50), TRUE) + var/obj/item/trash/can/doppler/crushed_can = new /obj/item/trash/can/doppler(M.loc) + crushed_can.icon_state = icon_state + qdel(src) + return TRUE + . = ..() + +/obj/item/reagent_containers/cup/soda_cans/doppler/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE) + . = ..() + + if(. != BULLET_ACT_HIT) + return + + if(hitting_projectile.damage > 0 && hitting_projectile.damage_type == BRUTE && !QDELETED(src)) + var/obj/item/trash/can/doppler/crushed_can = new /obj/item/trash/can/doppler(src.loc) + crushed_can.icon_state = icon_state + var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs)) + crushed_can.throw_at(throw_target, rand(1,2), 7) + qdel(src) + return + +/** + * Burst the soda open on someone. Fun! Opens and empties the soda can, but does not crush it. + * + * Arguments: + * * target - Who's getting covered in soda + * * hide_message - Stops the generic fizzing message, so you can do your own + */ + +/obj/item/reagent_containers/cup/soda_cans/doppler/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(. || spillable || !reagents.total_volume) // if it was caught, already opened, or has nothing in it + return + + fizziness += SODA_FIZZINESS_THROWN + if(!prob(fizziness)) + return + + burst_soda(hit_atom, hide_message = TRUE) + visible_message(span_danger("[src]'s impact with [hit_atom] causes it to rupture, spilling everywhere!")) + var/obj/item/trash/can/doppler/crushed_can = new /obj/item/trash/can/doppler(src.loc) + crushed_can.icon_state = icon_state + moveToNullspace() + QDEL_IN(src, 1 SECONDS) // give it a second so it can still be logged for the throw impact + +/obj/item/trash/can/doppler + icon = 'modular_doppler/clutter objects/icons/janitor.dmi' + icon_state = "lemonade" + +/obj/item/reagent_containers/cup/soda_cans/doppler/lubricola + name = "LubriCola" + desc = "The perfect lubricant for your weary gears." + icon_state = "lubricola" + list_reagents = list(/datum/reagent/fuel/oil = 30) + custom_price = PAYCHECK_LOWER * 1.2 + +/obj/item/reagent_containers/cup/soda_cans/doppler/welding_fizz + name = "Welding Fizz" + desc = "More energy than in an IED! Now carbonated. WARNING: Contains toxic and flammable fuels." + icon_state = "welding_fizz" + list_reagents = list(/datum/reagent/fuel = 25, /datum/reagent/carbondioxide = 5) + custom_price = PAYCHECK_LOWER * 1.2 + +/*/obj/item/reagent_containers/cup/soda_cans/doppler/synthanolcan + name = "Silly Cone's Synthanol" + desc = "A recompiling can of synthanol." + icon_state = "synthanolcan" + list_reagents = list(/datum/reagent/consumable/ethanol/synthanol = 30) + custom_price = PAYCHECK_CREW*/ + +#undef SODA_FIZZINESS_THROWN +#undef SODA_FIZZINESS_SHAKE diff --git a/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm new file mode 100644 index 0000000000000..420fea48fb751 --- /dev/null +++ b/modular_doppler/modular_food_drinks_and_chems/food_and_drinks/drinks_recipes.dm @@ -0,0 +1,421 @@ +/*STUFF WE CAN'T USE YET + +/datum/chemical_reaction/drink/synthanol + results = list(/datum/reagent/consumable/ethanol/synthanol = 3) + required_reagents = list( + /datum/reagent/lube = 1, + /datum/reagent/toxin/plasma = 1, + /datum/reagent/fuel = 1, + ) + mix_message = "The chemicals mix to create shiny, blue substance." + +/datum/chemical_reaction/drink/robottears + results = list(/datum/reagent/consumable/ethanol/synthanol/robottears = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 1, + /datum/reagent/fuel/oil = 1, + /datum/reagent/consumable/sodawater = 1, + ) + mix_message = "The ingredients combine into a stiff, dark goo." + +/datum/chemical_reaction/drink/trinary + results = list(/datum/reagent/consumable/ethanol/synthanol/trinary = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 1, + /datum/reagent/consumable/limejuice = 1, + /datum/reagent/consumable/orangejuice = 1, + ) + mix_message = "The ingredients mix into a colorful substance." + +/datum/chemical_reaction/drink/servo + results = list(/datum/reagent/consumable/ethanol/synthanol/servo = 4) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 2, + /datum/reagent/consumable/cream = 1, + /datum/reagent/consumable/hot_coco = 1, + ) + mix_message = "The ingredients mix into a dark brown substance." + +/datum/chemical_reaction/drink/uplink + results = list(/datum/reagent/consumable/ethanol/synthanol/uplink = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 1, + /datum/reagent/consumable/ethanol/rum = 1, + /datum/reagent/consumable/ethanol/vodka = 1, + /datum/reagent/consumable/ethanol/tequila = 1, + /datum/reagent/consumable/ethanol/whiskey = 1, + ) + mix_message = "The chemicals mix to create a shiny, orange substance." + +/datum/chemical_reaction/drink/synthncoke + results = list(/datum/reagent/consumable/ethanol/synthanol/synthncoke = 2) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 1, + /datum/reagent/consumable/space_cola = 1, + ) + mix_message = "The chemicals mix to create a smooth, fizzy substance." + +/datum/chemical_reaction/drink/synthignon + results = list(/datum/reagent/consumable/ethanol/synthanol/synthignon = 2) + required_reagents = list( + /datum/reagent/consumable/ethanol/synthanol = 1, + /datum/reagent/consumable/ethanol/wine = 1, + ) + mix_message = "The chemicals mix to create a fine, red substance." +*/ +// Other Booze + +/datum/chemical_reaction/drink/hot_toddy + results = list(/datum/reagent/consumable/ethanol/hot_toddy = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/rum = 1, + /datum/reagent/consumable/tea = 3, + /datum/reagent/consumable/honey = 1, + ) + mix_message = "A loud popping begins to fill the air as the drink is mixed." + +/datum/chemical_reaction/drink/hellfire + results = list(/datum/reagent/consumable/ethanol/hellfire = 4) + required_reagents = list( + /datum/reagent/consumable/ethanol/rum = 2, + /datum/reagent/consumable/ice = 1, + /datum/reagent/consumable/ethanol/crevice_spike = 1, + ) + mix_message = "The liquid begins to churn as it changes to an amber orange and catches on fire." + +/datum/chemical_reaction/drink/sins_delight + results = list(/datum/reagent/consumable/ethanol/sins_delight = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/demonsblood = 2, + /datum/reagent/consumable/ethanol/triple_sec = 1, + /datum/reagent/consumable/ethanol/martini = 1, + /datum/reagent/consumable/ethanol/changelingsting = 1, + ) + mix_message = "The liquid starts swirling, before forming a pink cloud that dissipates in the air." + +/datum/chemical_reaction/drink/strawberry_daiquiri + results = list(/datum/reagent/consumable/ethanol/strawberry_daiquiri = 7) + required_reagents = list( + /datum/reagent/consumable/ethanol/rum = 2, + /datum/reagent/consumable/limejuice = 1, + /datum/reagent/consumable/sugar = 1, + /datum/reagent/consumable/berryjuice = 2, + /datum/reagent/consumable/ice = 1, + ) + +/datum/chemical_reaction/drink/miami_vice + results = list(/datum/reagent/consumable/ethanol/miami_vice = 2) + required_reagents = list( + /datum/reagent/consumable/ethanol/pina_colada = 1, + /datum/reagent/consumable/ethanol/strawberry_daiquiri = 1, + ) + +/datum/chemical_reaction/drink/malibu_sunset + results = list(/datum/reagent/consumable/ethanol/malibu_sunset = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/painkiller = 2, + /datum/reagent/consumable/grenadine = 1, + /datum/reagent/consumable/orangejuice = 1, + /datum/reagent/consumable/ice = 1, + ) + +/datum/chemical_reaction/drink/liz_fizz + results = list(/datum/reagent/consumable/ethanol/liz_fizz = 5) + required_reagents = list( + /datum/reagent/consumable/triple_citrus = 3, + /datum/reagent/consumable/ice = 1, + /datum/reagent/consumable/cream = 1,) + +/datum/chemical_reaction/drink/hotlime_miami + results = list(/datum/reagent/consumable/ethanol/hotlime_miami = 2) + required_reagents = list( + /datum/reagent/medicine/ephedrine = 1, + /datum/reagent/consumable/ethanol/pina_colada = 1, + ) + +/datum/chemical_reaction/drink/coggrog + results = list(/datum/reagent/consumable/ethanol/coggrog = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol/cognac = 1, + /datum/reagent/fuel = 1, + /datum/reagent/consumable/ethanol/screwdrivercocktail = 1, + ) + mix_message = "You hear faint sounds of gears turning as it mixes." + mix_sound = 'sound/machines/clockcult/steam_whoosh.ogg' + +/datum/chemical_reaction/drink/badtouch + results = list(/datum/reagent/consumable/ethanol/badtouch = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/cognac = 2, + /datum/reagent/consumable/limejuice = 2, + /datum/reagent/consumable/orangejuice=1, + ) + +/datum/chemical_reaction/drink/marsblast + results = list(/datum/reagent/consumable/ethanol/marsblast = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/whiskey = 3, + /datum/reagent/consumable/dr_gibb = 2, + ) + +/datum/chemical_reaction/drink/mercuryblast + results = list(/datum/reagent/consumable/ethanol/mercuryblast = 4) + required_reagents = list( + /datum/reagent/consumable/ethanol/vodka = 2, + /datum/reagent/consumable/spacemountainwind = 1, + /datum/reagent/consumable/ice = 1, + ) + +/datum/chemical_reaction/drink/piledriver + results = list(/datum/reagent/consumable/ethanol/piledriver = 6) + required_reagents = list( + /datum/reagent/consumable/ethanol/screwdrivercocktail = 1, + /datum/reagent/consumable/ethanol/rum_coke = 1, + ) + +/datum/chemical_reaction/drink/zenstar + results = list(/datum/reagent/consumable/ethanol/zenstar = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/triple_sec = 2, + /datum/reagent/consumable/lemonjuice = 2, + /datum/reagent/consumable/grenadine = 1, + ) + +/datum/chemical_reaction/drink/appletini + results = list(/datum/reagent/consumable/ethanol/appletini = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/vodka = 3, + /datum/reagent/consumable/ethanol/hcider = 1, + /datum/reagent/consumable/lemonjuice = 1, + ) + +/datum/chemical_reaction/drink/quadruple_sec/cityofsin + results = list(/datum/reagent/consumable/ethanol/quadruple_sec/cityofsin = 4) + required_reagents = list( + /datum/reagent/consumable/ethanol/vodka = 2, + /datum/reagent/consumable/ethanol/champagne = 1, + /datum/reagent/consumable/berryjuice = 1, + ) + +/*/datum/chemical_reaction/drink/blizzard_brew + results = list(/datum/reagent/consumable/ethanol/blizzard_brew = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol/iced_beer = 1, + /datum/reagent/consumable/ice = 1, + /datum/reagent/inverse/cryostylane = 1, + ) + mix_message = "You hear crackling ice as it mixes." + mix_sound = 'sound/effects/ice_shovel.ogg' + +/datum/chemical_reaction/drink/molten_mead + results = list(/datum/reagent/consumable/ethanol/molten_mead = 3) + required_reagents = list( + /datum/reagent/consumable/condensedcapsaicin = 2, + /datum/reagent/consumable/ethanol/mead = 2, + /datum/reagent/consumable/ethanol/mauna_loa = 1, + ) + mix_message = "You hear sizzling flesh and angry wasps buzzing as it mixes." + mix_sound = 'sound/effects/wounds/sizzle2.ogg'*/ + +/datum/chemical_reaction/drink/hippie_hooch + results = list(/datum/reagent/consumable/ethanol/hippie_hooch = 5) + required_reagents = list( + /datum/reagent/medicine/antihol = 1, + /datum/reagent/consumable/ethanol/crevice_spike = 3, + /datum/reagent/medicine/earthsblood = 1, + ) + mix_message = "You hear wood flutes and nature as it mixes." + mix_sound = 'modular_doppler/emotes/sound/hoot.ogg' + +/datum/chemical_reaction/drink/research_rum + results = list(/datum/reagent/consumable/ethanol/research_rum = 4) + required_reagents = list( + /datum/reagent/consumable/ethanol/bananahonk = 2, + /datum/reagent/inverse/neurine = 1, + /datum/reagent/consumable/ethanol/grog = 1, + ) + mix_message = "You hear gurgling and dinging as it mixes." + mix_sound = 'sound/machines/microwave/microwave-end.ogg' + +/datum/chemical_reaction/drink/golden_grog + results = list(/datum/reagent/consumable/ethanol/golden_grog = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/goldschlager = 10, + /datum/reagent/gold = 1, + /datum/reagent/silver = 1, + /datum/reagent/cellulose = 1, + /datum/reagent/spraytan = 1, + ) + mix_message = "You hear golden coins and snobby rich laughing as it mixes." + mix_sound = 'sound/items/coinflip.ogg' + +/* MORE STUFF WE CAN'T USE YET +// RACE SPECIFIC DRINKS + +/datum/chemical_reaction/drink/coldscales + results = list(/datum/reagent/consumable/ethanol/coldscales = 3) + required_reagents = list( + /datum/reagent/consumable/tea = 1, + /datum/reagent/toxin/slimejelly = 1, + /datum/reagent/consumable/menthol = 1, + ) + +/datum/chemical_reaction/drink/oil_drum + results = list(/datum/reagent/consumable/ethanol/oil_drum = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol = 1, + /datum/reagent/fuel/oil = 1, + /datum/reagent/consumable/ethanol/champagne = 12, + ) + +/datum/chemical_reaction/drink/nord_king + results = list(/datum/reagent/consumable/ethanol/nord_king = 10) + required_reagents = list( + /datum/reagent/consumable/ethanol = 5, + /datum/reagent/consumable/honey = 1, + /datum/reagent/consumable/ethanol/red_mead = 10, + ) + +/datum/chemical_reaction/drink/velvet_kiss + results = list(/datum/reagent/consumable/ethanol/velvet_kiss = 15) //Limited races use this + required_reagents = list( + /datum/reagent/blood = 5, + /datum/reagent/consumable/tea = 1, + /datum/reagent/consumable/ethanol/wine = 10, + ) + +/datum/chemical_reaction/drink/abduction_fruit + results = list(/datum/reagent/consumable/ethanol/abduction_fruit = 3) + required_reagents = list( + /datum/reagent/consumable/limejuice = 10, + /datum/reagent/consumable/berryjuice = 5, + /datum/reagent/consumable/watermelonjuice = 10, + ) + +/datum/chemical_reaction/drink/bug_zapper + results = list(/datum/reagent/consumable/ethanol/bug_zapper = 20) //Harder to make + required_reagents = list( + /datum/reagent/consumable/lemonjuice = 10, + /datum/reagent/teslium = 1, + /datum/reagent/copper = 10, + ) + +/datum/chemical_reaction/drink/mush_crush + results = list(/datum/reagent/consumable/ethanol/mush_crush = 10) + required_reagents = list( + /datum/reagent/iron = 5, + /datum/reagent/ash = 5, + /datum/reagent/toxin/coffeepowder = 10, + ) + +/datum/chemical_reaction/drink/hollow_bone + results = list(/datum/reagent/consumable/ethanol/hollow_bone = 10) + required_reagents = list( + /datum/reagent/toxin/bonehurtingjuice = 10, + /datum/reagent/consumable/soymilk = 15, + ) + +/datum/chemical_reaction/drink/jell_wyrm + results = list(/datum/reagent/consumable/ethanol/jell_wyrm = 2) + required_reagents = list( + /datum/reagent/toxin/slimejelly = 1, + /datum/reagent/toxin/carpotoxin = 1, + /datum/reagent/carbondioxide = 5, + ) + required_temp = 333 // (59.85'C) + +/datum/chemical_reaction/drink/laval_spit + results = list(/datum/reagent/consumable/ethanol/laval_spit = 20) //Limited use + required_reagents = list( + /datum/reagent/iron = 5, + /datum/reagent/consumable/ethanol/mauna_loa = 10, + /datum/reagent/sulfur = 5, + ) + required_temp = 900 // (626.85'C) +*/ +/datum/chemical_reaction/drink/frisky_kitty + results = list(/datum/reagent/consumable/ethanol/frisky_kitty = 2) + required_reagents = list( + /datum/reagent/consumable/catnip_tea = 1, + /datum/reagent/consumable/milk = 1, + ) + required_temp = 296 //Just above room temp (22.85'C) +/* +/datum/chemical_reaction/drink/bloodshot_base + results = list(/datum/reagent/consumable/ethanol/bloodshot_base = 2) + required_reagents = list( + /datum/reagent/consumable/ethanol/bloody_mary = 1, + /datum/reagent/consumable/sugar = 1, + ) + reaction_tags = REACTION_TAG_DRINK | REACTION_TAG_EASY | REACTION_TAG_OTHER + +/datum/chemical_reaction/drink/bloodshot + results = list(/datum/reagent/consumable/ethanol/bloodshot = 5) + required_reagents = list( + /datum/reagent/blood = 3, + /datum/reagent/consumable/ethanol/bloodshot_base = 2, + ) + reaction_tags = REACTION_TAG_DRINK | REACTION_TAG_EASY | REACTION_TAG_OTHER +*/ + +// Non-Booze + +/datum/chemical_reaction/drink/pinkmilk + results = list(/datum/reagent/consumable/pinkmilk = 2) + required_reagents = list( + /datum/reagent/consumable/berryjuice = 1, + /datum/reagent/consumable/milk = 1, + /datum/reagent/consumable/sugar = 1, + ) + +/datum/chemical_reaction/drink/pinktea + results = list(/datum/reagent/consumable/pinktea = 5) + required_reagents = list( + /datum/reagent/consumable/berryjuice = 1, + /datum/reagent/consumable/tea/arnold_palmer = 1, + /datum/reagent/consumable/sugar = 1, + ) + +/datum/chemical_reaction/drink/catnip_tea + results = list(/datum/reagent/consumable/catnip_tea = 10) + required_reagents = list( + /datum/reagent/consumable/tea = 5, + /datum/reagent/pax/catnip = 2, + ) + +/datum/chemical_reaction/drink/beerbatter + results = list(/datum/reagent/consumable/ethanol/beerbatter = 4) + required_reagents = list( + /datum/reagent/consumable/nutriment/fat/oil = 1, + /datum/reagent/consumable/ethanol/beer = 1, + /datum/reagent/consumable/flour = 1, + ) + mix_message = "Sizzling and cracking is heard as you beat the mixture into submission." + +/datum/chemical_reaction/drink/shakiri_spritz + results = list(/datum/reagent/consumable/ethanol/shakiri_spritz = 3) + required_reagents = list( + /datum/reagent/consumable/ethanol/shakiri = 1, + /datum/reagent/consumable/sodawater = 1, + /datum/reagent/consumable/orangejuice = 1, + ) + mix_message = "The liquids combine to create a pleasant orange mixture." + +/datum/chemical_reaction/drink/crimson_hurricane + results = list(/datum/reagent/consumable/ethanol/crimson_hurricane = 5) + required_reagents = list( + /datum/reagent/consumable/ethanol/shakiri = 1, + /datum/reagent/consumable/ethanol/rum = 2, + /datum/reagent/consumable/grenadine = 1, + /datum/reagent/consumable/limejuice = 1, + ) + mix_message = "The mixture develops into a rich red color." + +/datum/chemical_reaction/drink/shakiri_rogers + results = list(/datum/reagent/consumable/ethanol/shakiri_rogers = 10) + required_reagents = list( + /datum/reagent/consumable/ethanol/shakiri = 1, + /datum/reagent/consumable/space_cola = 5, + /datum/reagent/consumable/ice = 2, + ) + mix_message = "Bubbles of carbonation rise and pop at the surface of the dark mixture." diff --git a/modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi b/modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi new file mode 100644 index 0000000000000..0b1eaf31cf571 Binary files /dev/null and b/modular_doppler/modular_food_drinks_and_chems/icons/drinks.dmi differ diff --git a/modular_doppler/modular_mob_spawn/code/mob_spawn.dm b/modular_doppler/modular_mob_spawn/code/mob_spawn.dm new file mode 100644 index 0000000000000..c3a0ca11c061c --- /dev/null +++ b/modular_doppler/modular_mob_spawn/code/mob_spawn.dm @@ -0,0 +1,16 @@ +/obj/effect/mob_spawn/ghost_role + /// List of ghost role restricted species + var/list/restricted_species = list() + +/obj/effect/mob_spawn/ghost_role/create(mob/mob_possessor, newname) + if(restricted_species && !(mob_possessor?.client?.prefs?.read_preference(/datum/preference/choiced/species) in restricted_species)) + var/text = "Current loaded character doesn't match required species: " + var/i = 1 + for(var/datum/species/speciesItem as anything in restricted_species) + text += "[speciesItem.name]" + if(i < restricted_species.len) + text += ", " + i++ + tgui_alert(mob_possessor, text) + return FALSE + return ..() diff --git a/modular_doppler/modular_mob_spawn/readme.md b/modular_doppler/modular_mob_spawn/readme.md new file mode 100644 index 0000000000000..6065edd6db776 --- /dev/null +++ b/modular_doppler/modular_mob_spawn/readme.md @@ -0,0 +1,27 @@ +## Title: Modular Mob Spawn + +MODULE ID: MODULAR_MOB_SPAWN + +### Description: + +Allows ghost role spawners to have species restrictions. + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: + +Kaostico diff --git a/modular_doppler/modular_sounds/code/sounds.dm b/modular_doppler/modular_sounds/code/sounds.dm new file mode 100644 index 0000000000000..928086d6280b6 --- /dev/null +++ b/modular_doppler/modular_sounds/code/sounds.dm @@ -0,0 +1,16 @@ +/proc/get_sfx_doppler(soundin) + if(istext(soundin)) + switch(soundin) + if(SFX_BRICK_DROP) + soundin = pick( + 'modular_doppler/modular_sounds/sound/bricks/brick_drop_1.ogg', + 'modular_doppler/modular_sounds/sound/bricks/brick_drop_2.ogg', + 'modular_doppler/modular_sounds/sound/bricks/brick_drop_3.ogg', + ) + if(SFX_BRICK_PICKUP) + soundin = pick( + 'modular_doppler/modular_sounds/sound/bricks/brick_pick_up_1.ogg', + 'modular_doppler/modular_sounds/sound/bricks/brick_pick_up_2.ogg', + ) + + return soundin diff --git a/modular_doppler/modular_sounds/readme.md b/modular_doppler/modular_sounds/readme.md new file mode 100644 index 0000000000000..ff42c5b4b201e --- /dev/null +++ b/modular_doppler/modular_sounds/readme.md @@ -0,0 +1,23 @@ +## Title: Modular sounds + +MODULE ID: MODULAR_SOUNDS + +### Description: + +Module to add sounds in an unobtrusive way without touching TG code + +### TG Proc Changes: + +| proc | file | +| ------------------------ | -------------------- | +| `/proc/get_sfx(soundin)` | `code\game\sound.dm` | + +### Defines: + +- `code\__DEFINES\~doppler_defines\sound.dm` + +### Included files: + +N/A + +### Credits: diff --git a/modular_doppler/modular_sounds/sound/bricks/attributions.txt b/modular_doppler/modular_sounds/sound/bricks/attributions.txt new file mode 100644 index 0000000000000..73ec0f37d5ff3 --- /dev/null +++ b/modular_doppler/modular_sounds/sound/bricks/attributions.txt @@ -0,0 +1 @@ +All sounds taken from 'Brick pick up and put down close mic' by vintage2005 via Pixabay - https://pixabay.com/sound-effects/brick-pick-up-and-put-down-close-mic-28684/ diff --git a/modular_doppler/modular_sounds/sound/bricks/brick_drop_1.ogg b/modular_doppler/modular_sounds/sound/bricks/brick_drop_1.ogg new file mode 100644 index 0000000000000..94bdeed6333b5 Binary files /dev/null and b/modular_doppler/modular_sounds/sound/bricks/brick_drop_1.ogg differ diff --git a/modular_doppler/modular_sounds/sound/bricks/brick_drop_2.ogg b/modular_doppler/modular_sounds/sound/bricks/brick_drop_2.ogg new file mode 100644 index 0000000000000..cd12a30acfc78 Binary files /dev/null and b/modular_doppler/modular_sounds/sound/bricks/brick_drop_2.ogg differ diff --git a/modular_doppler/modular_sounds/sound/bricks/brick_drop_3.ogg b/modular_doppler/modular_sounds/sound/bricks/brick_drop_3.ogg new file mode 100644 index 0000000000000..1b057e707a6f7 Binary files /dev/null and b/modular_doppler/modular_sounds/sound/bricks/brick_drop_3.ogg differ diff --git a/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_1.ogg b/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_1.ogg new file mode 100644 index 0000000000000..13a16eab89237 Binary files /dev/null and b/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_1.ogg differ diff --git a/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_2.ogg b/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_2.ogg new file mode 100644 index 0000000000000..e72228ca6dd29 Binary files /dev/null and b/modular_doppler/modular_sounds/sound/bricks/brick_pick_up_2.ogg differ diff --git a/modular_doppler/modular_traits/code/neutral.dm b/modular_doppler/modular_traits/code/neutral.dm new file mode 100644 index 0000000000000..1bff3c1539b0f --- /dev/null +++ b/modular_doppler/modular_traits/code/neutral.dm @@ -0,0 +1,22 @@ +/datum/quirk/feline_aspect + name = "Feline Traits" + desc = "You happen to act like a feline, for whatever reason. This will replace most other tongue-based speech quirks." + gain_text = span_notice("Nya could go for some catnip right about now...") + lose_text = span_notice("You feel less attracted to lasers.") + medical_record_text = "Patient seems to possess behavior much like a feline." + mob_trait = TRAIT_FELINE + icon = FA_ICON_CAT + +/datum/quirk/feline_aspect/add_unique(client/client_source) + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/organ/internal/tongue/cat/new_tongue = new(get_turf(human_holder)) + + new_tongue.copy_traits_from(human_holder.get_organ_slot(ORGAN_SLOT_TONGUE)) + new_tongue.Insert(human_holder, special = TRUE, movement_flags = DELETE_IF_REPLACED) + +/datum/quirk/feline_aspect/remove() + var/mob/living/carbon/human/human_holder = quirk_holder + var/obj/item/organ/internal/tongue/new_tongue = new human_holder.dna.species.mutanttongue + + new_tongue.copy_traits_from(human_holder.get_organ_slot(ORGAN_SLOT_TONGUE)) + new_tongue.Insert(human_holder, special = TRUE, movement_flags = DELETE_IF_REPLACED) diff --git a/modular_doppler/modular_traits/code/organs.dm b/modular_doppler/modular_traits/code/organs.dm new file mode 100644 index 0000000000000..8dd357630e4f8 --- /dev/null +++ b/modular_doppler/modular_traits/code/organs.dm @@ -0,0 +1,9 @@ +/// Copy traits from one organ to another - e.g. with custom roundstart organs that should still get species traits applied. +/obj/item/organ/proc/copy_traits_from(obj/item/organ/old_organ, copy_actions = FALSE) + if(isnull(old_organ)) + return + + if(copy_actions) + // for when you want to make sure the organ gets any actions from the old one + for(var/datum/action/action as anything in old_organ.actions) + add_item_action(action.type) diff --git a/modular_doppler/modular_traits/readme.md b/modular_doppler/modular_traits/readme.md new file mode 100644 index 0000000000000..18c230c7fe1d2 --- /dev/null +++ b/modular_doppler/modular_traits/readme.md @@ -0,0 +1,25 @@ +## Title: Modular Traits + +MODULE ID: MODULAR_TRAITS + +### Description: + +Anything related to new traits. All the new quirks need to be added in `code\controllers\subsystem\processing\quirks.dm`, on the `GLOBAL_LIST_INIT_TYPED` for them to be available before the subsystem inits. + +### TG Proc Changes: + +N/A + +### Defines: + +- `code\__DEFINES\~doppler_defines\traits.dm` + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `code\controllers\subsystem\processing\quirks.dm` original TG list of quirks + +### Credits: diff --git a/modular_doppler/obj_flags_doppler/code/objs.dm b/modular_doppler/obj_flags_doppler/code/objs.dm new file mode 100644 index 0000000000000..55b595bb1aefc --- /dev/null +++ b/modular_doppler/obj_flags_doppler/code/objs.dm @@ -0,0 +1,3 @@ +/obj + ///the Doppler Shift version of obj_flags, to prevent any potential future conflict + var/obj_flags_doppler = null diff --git a/modular_doppler/obj_flags_doppler/readme.md b/modular_doppler/obj_flags_doppler/readme.md new file mode 100644 index 0000000000000..0444237141c93 --- /dev/null +++ b/modular_doppler/obj_flags_doppler/readme.md @@ -0,0 +1,25 @@ +## Title: Obj Flags Doppler + +MODULE ID: OBJ_FLAGS_DOPPLER + +### Description: + +Currently, it's only function is to extend the `/obj` with `obj_flags_doppler`. Basically Doppler Shift version of `obj_flags`, to prevent any potential future conflict. Honestly, I didn't know where to put this. + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `code\_globalvars\~doppler_globalvars\bitfields.dm` + +### Credits: diff --git a/modular_doppler/reagent_forging/code/anvil.dm b/modular_doppler/reagent_forging/code/anvil.dm new file mode 100644 index 0000000000000..e175bd6eb089e --- /dev/null +++ b/modular_doppler/reagent_forging/code/anvil.dm @@ -0,0 +1,163 @@ +/obj/structure/reagent_anvil + name = "smithing anvil" + desc = "Essentially a big block of metal that you can hammer other metals on top of, crucial for anyone working metal by hand." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_structures.dmi' + icon_state = "anvil_empty" + + anchored = TRUE + density = TRUE + +/obj/structure/reagent_anvil/Initialize(mapload) + . = ..() + + AddElement(/datum/element/falling_hazard, damage = 40, wound_bonus = 10, hardhat_safety = FALSE, crushes = TRUE) + +/obj/structure/reagent_anvil/update_appearance() + . = ..() + cut_overlays() + if(!length(contents)) + return + + var/image/overlayed_item = image(icon = contents[1].icon, icon_state = contents[1].icon_state) + overlayed_item.transform = matrix(, 0, 0, 0, 0.8, 0) + add_overlay(overlayed_item) + +/obj/structure/reagent_anvil/examine(mob/user) + . = ..() + . += span_notice("You can place hot metal objects on this using some tongs.") + . += span_notice("It can be (un)secured with Right Click") + + if(length(contents)) + . += span_notice("It has [contents[1]] sitting on it.") + +/obj/structure/reagent_anvil/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + if(!can_interact(user) || !user.can_perform_action(src)) + return + + set_anchored(!anchored) + balloon_alert_to_viewers(anchored ? "secured" : "unsecured") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/structure/reagent_anvil/wrench_act(mob/living/user, obj/item/tool) + balloon_alert_to_viewers("deconstructing...") + + if(!do_after(user, 2 SECONDS, src)) + balloon_alert_to_viewers("stopped deconstructing") + return TRUE + + tool.play_tool_sound(src) + deconstruct(TRUE) + return TRUE + +/obj/structure/reagent_anvil/atom_deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/iron/ten(get_turf(src)) + return ..() + +/obj/structure/reagent_anvil/tong_act(mob/living/user, obj/item/tool) + var/obj/item/forging/forge_item = tool + var/obj/obj_anvil_search = locate() in contents + + if(forge_item.in_use) + balloon_alert(user, "already in use") + return ITEM_INTERACT_SUCCESS + + var/obj/obj_tong_search = locate() in forge_item.contents + if(obj_anvil_search && !obj_tong_search) + obj_anvil_search.forceMove(forge_item) + update_appearance() + forge_item.icon_state = "tong_full" + return ITEM_INTERACT_SUCCESS + + if(!obj_anvil_search && obj_tong_search) + obj_tong_search.forceMove(src) + update_appearance() + forge_item.icon_state = "tong_empty" + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_anvil/hammer_act(mob/living/user, obj/item/tool) + //do we have an incomplete item to hammer out? if so, here is our block of code + var/obj/item/forging/incomplete/locate_incomplete = locate() in contents + if(locate_incomplete) + if (locate_incomplete.in_use) + balloon_alert(user, "being worked on") + return ITEM_INTERACT_SUCCESS + locate_incomplete.in_use = TRUE + do_hammer(user, tool, locate_incomplete) + locate_incomplete.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + //okay, so we didn't find an incomplete item to hammer, do we have a hammerable item? + var/obj/locate_obj = locate() in contents + if(locate_obj && (locate_obj.obj_flags_doppler & ANVIL_REPAIR)) + if(locate_obj.get_integrity() >= locate_obj.max_integrity) + balloon_alert(user, "already repaired") + return ITEM_INTERACT_SUCCESS + + while(locate_obj.get_integrity() < locate_obj.max_integrity) + if(!do_after(user, 1 SECONDS, src)) + balloon_alert(user, "stopped repairing") + return ITEM_INTERACT_SUCCESS + + locate_obj.repair_damage(locate_obj.get_integrity() + 10) + user.mind.adjust_experience(/datum/skill/smithing, 5) //repairing does give some experience + playsound(src, 'modular_doppler/reagent_forging/sound/forge.ogg', 50, TRUE, ignore_walls = FALSE) + + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_anvil/proc/do_hammer(mob/living/user, obj/item/tool, obj/item/forging/incomplete/locate_incomplete) + while(locate_incomplete.times_hit < locate_incomplete.average_hits) + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) * locate_incomplete.average_wait + + if(!do_after(user, skill_modifier * tool.toolspeed, src)) + balloon_alert(user, "stopped hammering") + locate_incomplete.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + if(locate_incomplete.loc != src) + balloon_alert(user, "workpiece moved!") + locate_incomplete.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + playsound(src, 'modular_doppler/reagent_forging/sound/forge.ogg', 50, TRUE, ignore_walls = FALSE) + if(COOLDOWN_FINISHED(locate_incomplete, heating_remainder)) + balloon_alert(user, "metal too cool!") + locate_incomplete.times_hit -= 3 + if(locate_incomplete.times_hit <= -locate_incomplete.average_hits) + balloon_alert_to_viewers("workpiece breaks!") + qdel(locate_incomplete) + update_appearance() + return ITEM_INTERACT_SUCCESS + + locate_incomplete.times_hit++ + user.mind.adjust_experience(/datum/skill/smithing, 1) //A good hit gives minimal experience + + balloon_alert(user, "workpiece sounds ready") + +/obj/structure/reagent_anvil/hammer_act_secondary(mob/living/user, obj/item/tool) + hammer_act(user, tool) + +/obj/structure/reagent_anvil/onZImpact(turf/impacted_turf, levels, message = TRUE) + var/mob/living/poor_target = locate(/mob/living) in impacted_turf + if(!poor_target) + return ..() + + poor_target.apply_damage(60 * levels, forced = TRUE) + + if(istype(poor_target, /mob/living/carbon)) //If this mob is a carbon, break a few of their limbs + poor_target.take_bodypart_damage(40 * levels, wound_bonus = 5 * levels) + poor_target.take_bodypart_damage(40 * levels, wound_bonus = 5 * levels) + + poor_target.AddElement(/datum/element/squish, 30 SECONDS) + poor_target.visible_message( + span_bolddanger("[src] falls on [poor_target], crushing them!"), + span_userdanger("You are crushed by [src]!") + ) + poor_target.Paralyze(5 SECONDS) + poor_target.emote("scream") + playsound(poor_target, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) + add_memory_in_range(poor_target, 7, /datum/memory/witness_vendor_crush, protagonist = poor_target, antognist = src) + return TRUE diff --git a/modular_doppler/reagent_forging/code/centrifuge.dm b/modular_doppler/reagent_forging/code/centrifuge.dm new file mode 100644 index 0000000000000..9bb4272a5c0cc --- /dev/null +++ b/modular_doppler/reagent_forging/code/centrifuge.dm @@ -0,0 +1,51 @@ +/obj/item/reagent_containers/cup/primitive_centrifuge + name = "primitive centrifuge" + desc = "A small cup that allows a person to slowly spin out liquids they do not desire." + icon = 'modular_doppler/reagent_forging/icons/obj/misc_tools.dmi' + icon_state = "primitive_centrifuge" + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + +/obj/item/reagent_containers/cup/primitive_centrifuge/examine() + . = ..() + . += span_notice("Ctrl + Click to select chemicals to remove.") + . += span_notice("Ctrl + Shift + Click to select a chemical to keep, the rest removed.") + +/obj/item/reagent_containers/cup/primitive_centrifuge/item_ctrl_click(mob/user) + if(!length(reagents.reagent_list)) + return CLICK_ACTION_BLOCKING + + var/datum/user_input = tgui_input_list(user, "Select which chemical to remove.", "Removal Selection", reagents.reagent_list) + + if(!user_input) + balloon_alert(user, "no selection!") + return CLICK_ACTION_BLOCKING + + user.balloon_alert_to_viewers("spinning [src]...") + if(!do_after(user, 5 SECONDS, target = src)) + user.balloon_alert_to_viewers("stopped spinning [src]") + return CLICK_ACTION_BLOCKING + + reagents.del_reagent(user_input.type) + balloon_alert(user, "removed reagent from [src]") + return CLICK_ACTION_SUCCESS + +/obj/item/reagent_containers/cup/primitive_centrifuge/click_ctrl_shift(mob/user) + if(!length(reagents.reagent_list)) + return + + var/datum/user_input = tgui_input_list(user, "Select which chemical to keep, the rest removed.", "Keep Selection", reagents.reagent_list) + + if(!user_input) + balloon_alert(user, "no selection!") + return + + user.balloon_alert_to_viewers("spinning [src]...") + if(!do_after(user, 5 SECONDS, target = src)) + user.balloon_alert_to_viewers("stopped spinning [src]") + return + + for(var/datum/reagent/remove_reagent in reagents.reagent_list) + if(!istype(remove_reagent, user_input.type)) + reagents.del_reagent(remove_reagent.type) + + balloon_alert(user, "removed reagents from [src]") diff --git a/modular_doppler/reagent_forging/code/crafting_bench.dm b/modular_doppler/reagent_forging/code/crafting_bench.dm new file mode 100644 index 0000000000000..2f98728a6a018 --- /dev/null +++ b/modular_doppler/reagent_forging/code/crafting_bench.dm @@ -0,0 +1,384 @@ +/// How many planks of wood are required to complete a weapon? +#define WEAPON_COMPLETION_WOOD_AMOUNT 2 + +/// The number of hits you are set back when a bad hit is made +#define BAD_HIT_PENALTY 3 + +/obj/structure/reagent_crafting_bench + name = "forging workbench" + desc = "A crafting bench fitted with tools, securing mechanisms, and a steady surface for blacksmithing." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_structures.dmi' + icon_state = "crafting_bench_empty" + + anchored = TRUE + density = TRUE + ///whether the crafting is being hammered + var/in_use = FALSE + + /// What the currently picked recipe is + var/datum/crafting_bench_recipe/selected_recipe + /// How many successful hits towards completion of the item have we done + var/current_hits_to_completion = 0 + /// Is this bench able to complete forging items? Exists to allow non-forging workbenches to exist + var/finishes_forging_weapons = TRUE + /// The cooldown from the last hit before we allow another 'good hit' to happen + COOLDOWN_DECLARE(hit_cooldown) + /// What recipes are we allowed to choose from? + var/list/allowed_choices = list( + /datum/crafting_bench_recipe/plate_helmet, + /datum/crafting_bench_recipe/plate_vest, + /datum/crafting_bench_recipe/plate_gloves, + /datum/crafting_bench_recipe/plate_boots, + /datum/crafting_bench_recipe/ring, + // /datum/crafting_bench_recipe/collar, + /datum/crafting_bench_recipe/handcuffs, + /datum/crafting_bench_recipe/pavise, + /datum/crafting_bench_recipe/buckler, + /datum/crafting_bench_recipe/seed_mesh, + /datum/crafting_bench_recipe/centrifuge, + /datum/crafting_bench_recipe/soup_pot, + /datum/crafting_bench_recipe/bokken, + /datum/crafting_bench_recipe/bow, + ) + /// Radial options for recipes in the allowed_choices list, populated by populate_radial_choice_list + var/list/radial_choice_list = list() + /// An associative list of names --> recipe path that the radial recipe picker will choose from later + var/list/recipe_names_to_path = list() + +/obj/structure/reagent_crafting_bench/Initialize(mapload) + . = ..() + populate_radial_choice_list() + +/obj/structure/reagent_crafting_bench/proc/populate_radial_choice_list() + if(!length(allowed_choices)) + return + + if(length(radial_choice_list) && length(recipe_names_to_path)) // We already have both of these and don't need it, if this is called after these are generated for some reason + return + + for(var/recipe in allowed_choices) + var/datum/crafting_bench_recipe/recipe_to_take_from = new recipe() + var/obj/recipe_resulting_item = recipe_to_take_from.resulting_item + radial_choice_list[recipe_to_take_from.recipe_name] = image(icon = initial(recipe_resulting_item.icon), icon_state = initial(recipe_resulting_item.icon_state)) + recipe_names_to_path[recipe_to_take_from.recipe_name] = recipe + qdel(recipe_to_take_from) + + +/obj/structure/reagent_crafting_bench/examine(mob/user) + . = ..() + + if(length(contents)) + if(istype(contents[1], /obj/item/forging/complete)) + var/obj/item/forging/complete/contained_forge_item = contents[1] + + . += span_notice("[src] has a [initial(contained_forge_item.name)] sitting on it, awaiting completion.
") + var/obj/item/completion_item = contained_forge_item.spawning_item + . += span_notice("With [WEAPON_COMPLETION_WOOD_AMOUNT] sheets of wood nearby, and some hammering, it could be completed into a [initial(completion_item.name)].") + return // We don't want to show any selected recipes if there's weapon head on the bench + + if(!selected_recipe) + return + + var/obj/resulting_item = selected_recipe.resulting_item + . += span_notice("The selected recipe's resulting item is: [initial(resulting_item.name)]
") + . += span_notice("Gather the required materials, listed below, near the bench, then start hammering to complete it!
") + + if(!length(selected_recipe.recipe_requirements)) + . += span_boldwarning("Somehow, this recipe has no requirements, report this as this shouldn't happen.") + return + + for(var/obj/requirement_item as anything in selected_recipe.recipe_requirements) + if(!selected_recipe.recipe_requirements[requirement_item]) + . += span_boldwarning("[requirement_item] does not have an amount required set, this should not happen, report it.") + continue + + . += span_notice("[selected_recipe.recipe_requirements[requirement_item]] - [initial(requirement_item.name)]") + + return . + +/obj/structure/reagent_crafting_bench/update_appearance(updates) + . = ..() + cut_overlays() + + if(!length(contents)) + return + + var/image/overlayed_item = image(icon = contents[1].icon, icon_state = contents[1].icon_state) + add_overlay(overlayed_item) + +/obj/structure/reagent_crafting_bench/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(in_use) + balloon_alert(user, "already in use") + return + + update_appearance() + + if(length(contents)) + var/obj/item/contained_item = contents[1] + user.put_in_hands(contained_item) + balloon_alert(user, "[contained_item] retrieved") + update_appearance() + return + + if(selected_recipe) + clear_recipe() + balloon_alert_to_viewers("recipe cleared") + update_appearance() + return + + var/chosen_recipe = show_radial_menu(user, src, radial_choice_list, radius = 38, require_near = TRUE, tooltips = TRUE) + + if(!chosen_recipe) + balloon_alert(user, "no recipe choice") + return + + var/datum/crafting_bench_recipe/recipe_to_use = recipe_names_to_path[chosen_recipe] + selected_recipe = new recipe_to_use + + balloon_alert(user, "recipe chosen") + update_appearance() + +/// Clears the current recipe and sets hits to completion to zero +/obj/structure/reagent_crafting_bench/proc/clear_recipe() + QDEL_NULL(selected_recipe) + current_hits_to_completion = 0 + +/obj/structure/reagent_crafting_bench/attackby(obj/item/attacking_item, mob/user, params) + if(in_use) + balloon_alert(user, "already in use") + return + + if(istype(attacking_item, /obj/item/forging/complete)) + if(length(contents)) + balloon_alert(user, "already full") + return TRUE + + attacking_item.forceMove(src) + balloon_alert_to_viewers("placed [attacking_item]") + update_appearance() + return TRUE + + return ..() + +/obj/structure/reagent_crafting_bench/wrench_act(mob/living/user, obj/item/tool) + if(in_use) + balloon_alert(user, "it's currently in use!") + return + + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + + deconstruct(disassembled = TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_crafting_bench/atom_deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 5) + +/obj/structure/reagent_crafting_bench/hammer_act(mob/living/user, obj/item/tool) + if(in_use) + balloon_alert(user, "already in use") + return ITEM_INTERACT_SUCCESS + + if(length(contents)) + if(!istype(contents[1], /obj/item/forging/complete)) + balloon_alert(user, "invalid item") + return ITEM_INTERACT_SUCCESS + + var/obj/item/forging/complete/weapon_to_finish = contents[1] + + if(!weapon_to_finish.spawning_item) + balloon_alert(user, "[weapon_to_finish] cannot be completed") + return ITEM_INTERACT_SUCCESS + + var/list/wood_required_for_weapons = list( + /obj/item/stack/sheet/mineral/wood = WEAPON_COMPLETION_WOOD_AMOUNT, + ) + + if(!can_we_craft_this(wood_required_for_weapons)) + balloon_alert(user, "not enough wood") + return ITEM_INTERACT_SUCCESS + + var/list/things_to_use = can_we_craft_this(wood_required_for_weapons, TRUE) + var/obj/thing_just_made = create_thing_from_requirements(things_to_use, user = user, skill_to_grant = /datum/skill/smithing, skill_amount = 30, completing_a_weapon = TRUE) + + if(!thing_just_made) + message_admins("[src] just tried to finish a weapon but somehow created nothing! This is not working as intended!") + return ITEM_INTERACT_SUCCESS + + playsound(src, 'modular_doppler/reagent_forging/sound/forge.ogg', 50, TRUE) + + balloon_alert_to_viewers("[thing_just_made] created") + update_appearance() + return ITEM_INTERACT_SUCCESS + + if(!selected_recipe) + balloon_alert(user, "no recipe selected") + return ITEM_INTERACT_SUCCESS + + if(!can_we_craft_this(selected_recipe.recipe_requirements)) + balloon_alert(user, "missing ingredients") + return ITEM_INTERACT_SUCCESS + + in_use = TRUE + do_hammer(user, selected_recipe, current_hits_to_completion) + in_use = FALSE + var/list/things_to_use = can_we_craft_this(selected_recipe.recipe_requirements, TRUE) + create_thing_from_requirements(things_to_use, selected_recipe, user, selected_recipe.relevant_skill, selected_recipe.relevant_skill_reward) + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_crafting_bench/proc/do_hammer(mob/living/user, datum/crafting_bench_recipe/selected_recipe, current_hits_to_completion) + while(current_hits_to_completion < selected_recipe.required_good_hits) + var/skill_modifier = user.mind.get_skill_modifier(selected_recipe.relevant_skill, SKILL_SPEED_MODIFIER) * 1 SECONDS + + if(!do_after(user, skill_modifier, src)) + balloon_alert(user, "stopped hammering") + in_use = FALSE + return ITEM_INTERACT_SUCCESS + + if(!can_we_craft_this(selected_recipe.recipe_requirements)) + balloon_alert(user, "missing ingredients") + in_use = FALSE + return ITEM_INTERACT_SUCCESS + + playsound(src, 'modular_doppler/reagent_forging/sound/forge.ogg', 50, TRUE) + current_hits_to_completion++ + user.mind.adjust_experience(selected_recipe.relevant_skill, selected_recipe.relevant_skill_reward / 15) + +/// Takes the given list of item requirements and checks the surroundings for them, returns TRUE unless return_ingredients_list is set, in which case a list of all the items to use is returned +/obj/structure/reagent_crafting_bench/proc/can_we_craft_this(list/required_items, return_ingredients_list = FALSE) + if(!length(required_items)) + message_admins("[src] just tried to check for ingredients nearby without having a list of items to check for!") + return FALSE + + var/list/surrounding_items = list() + var/list/requirement_items = list() + + for(var/obj/item/potential_requirement in get_environment()) + surrounding_items += potential_requirement + + for(var/obj/item/requirement_path as anything in required_items) + var/required_amount = required_items[requirement_path] + + for(var/obj/item/nearby_item as anything in surrounding_items) + if(!istype(nearby_item, requirement_path)) + continue + + if(isstack(nearby_item)) // If the item is a stack, check if that stack has enough material in it to fill out the amount + var/obj/item/stack/nearby_stack = nearby_item + if(required_amount > 0) + requirement_items += nearby_item + required_amount -= nearby_stack.amount + else // Otherwise, we still exist and should subtract one from the required number of items + if(required_amount > 0) + requirement_items += nearby_item + required_amount -= 1 + + if(required_amount > 0) + return FALSE + + if(return_ingredients_list) + return requirement_items + else + return TRUE + +/// Passes the list of found ingredients + the recipe to use_or_delete_recipe_requirements, then spawns the given recipe's result +/obj/structure/reagent_crafting_bench/proc/create_thing_from_requirements(list/things_to_use, datum/crafting_bench_recipe/recipe_to_follow, mob/living/user, datum/skill/skill_to_grant, skill_amount, completing_a_weapon) + + if(!recipe_to_follow && !completing_a_weapon) + message_admins("[src] just tried to complete a recipe without having a recipe, and without it being the completion of a forging weapon!") + return FALSE + + if(completing_a_weapon && (!length(contents) || !istype(contents[1], /obj/item/forging/complete))) + message_admins("[src] just tried to complete a forge weapon without there being a weapon head inside it to complete!") + return FALSE + + if(!length(things_to_use)) + message_admins("[src] just tried to craft something from requirements, but was not given a list of requirements!") + return FALSE + + if(completing_a_weapon) + recipe_to_follow = new /datum/crafting_bench_recipe/weapon_completion_recipe + + var/materials_to_transfer = list() + var/list/temporary_materials_list = use_or_delete_recipe_requirements(things_to_use, recipe_to_follow) + for(var/material as anything in temporary_materials_list) + materials_to_transfer[material] += temporary_materials_list[material] + + var/obj/newly_created_thing + + if(completing_a_weapon) + var/obj/item/forging/complete/completed_forge_item = contents[1] + newly_created_thing = new completed_forge_item.spawning_item(src) + if(completed_forge_item.custom_materials) // We need to add the weapon head's materials to the completed item, too + for(var/custom_material in completed_forge_item.custom_materials) + materials_to_transfer[custom_material] += completed_forge_item.custom_materials[custom_material] + qdel(completed_forge_item) // And then we also need to 'use' the item + + else + newly_created_thing = new recipe_to_follow.resulting_item(src) + + if(!newly_created_thing) + message_admins("[src] just failed to create something while crafting!") + return FALSE + + if(recipe_to_follow.transfers_materials) + newly_created_thing.set_custom_materials(materials_to_transfer, multiplier = 1) + + user.mind.adjust_experience(skill_to_grant, skill_amount) + + clear_recipe() + update_appearance() + return newly_created_thing + +/// Takes the given list, things_to_use, compares it to recipe_to_follow's requirements, then either uses items from a stack, or deletes them otherwise. Returns custom material of forge items in the end. +/obj/structure/reagent_crafting_bench/proc/use_or_delete_recipe_requirements(list/things_to_use, datum/crafting_bench_recipe/recipe_to_follow) + var/list/materials_to_transfer = list() + + for(var/obj/requirement_item as anything in things_to_use) + if(isstack(requirement_item)) + var/stack_type + for(var/recipe_thing_to_reference as anything in recipe_to_follow.recipe_requirements) + if(!istype(requirement_item, recipe_thing_to_reference)) + continue + stack_type = recipe_thing_to_reference + break + + var/obj/item/stack/requirement_stack = requirement_item + + if(requirement_stack.amount < recipe_to_follow.recipe_requirements[stack_type]) + recipe_to_follow.recipe_requirements[stack_type] -= requirement_stack.amount + requirement_stack.use(requirement_stack.amount) + continue + + requirement_stack.use(recipe_to_follow.recipe_requirements[stack_type]) + + else if(istype(requirement_item, /obj/item/forging/complete)) + if(!requirement_item.custom_materials || !recipe_to_follow.transfers_materials) + qdel(requirement_item) + continue + + for(var/custom_material as anything in requirement_item.custom_materials) + materials_to_transfer += custom_material + qdel(requirement_item) + + else + qdel(requirement_item) + + return materials_to_transfer + +/// Gets movable atoms within one tile of range of the crafting bench +/obj/structure/reagent_crafting_bench/proc/get_environment() + . = list() + + if(!get_turf(src)) + return + + for(var/atom/movable/found_movable_atom in range(1, src)) + if((found_movable_atom.flags_1 & HOLOGRAM_1)) + continue + . += found_movable_atom + return . + +#undef WEAPON_COMPLETION_WOOD_AMOUNT diff --git a/modular_doppler/reagent_forging/code/crafting_bench_recipes.dm b/modular_doppler/reagent_forging/code/crafting_bench_recipes.dm new file mode 100644 index 0000000000000..6e608996abf38 --- /dev/null +++ b/modular_doppler/reagent_forging/code/crafting_bench_recipes.dm @@ -0,0 +1,134 @@ +/datum/crafting_bench_recipe + /// The name of the recipe to show + var/recipe_name = "generic debug recipe" + /// The items required to create the resulting item + var/list/recipe_requirements + /// What the end result of this recipe should be + var/resulting_item = /obj/item/forging + /// If we use the materials from the component parts + var/transfers_materials = TRUE + /// How many times should you have to swing the hammer to finish this item + var/required_good_hits = 6 + /// What skill is relevant to the creation of this item? + var/relevant_skill = /datum/skill/smithing + /// How much experience in our relevant skill do we give upon completion? + var/relevant_skill_reward = 30 + +/datum/crafting_bench_recipe/weapon_completion_recipe //Exists so I don't have to modify the code too much for weapon completion + recipe_name = "generic weapon completion recipe (should not be visible)" + recipe_requirements = list( + /obj/item/stack/sheet/mineral/wood = 2, + ) + +/datum/crafting_bench_recipe/plate_helmet + recipe_name = "plate helmet" + recipe_requirements = list( + /obj/item/forging/complete/plate = 4, + ) + resulting_item = /obj/item/clothing/head/helmet/forging_plate_helmet + required_good_hits = 8 + +/datum/crafting_bench_recipe/plate_vest + recipe_name = "plate vest" + recipe_requirements = list( + /obj/item/forging/complete/plate = 6, + ) + resulting_item = /obj/item/clothing/suit/armor/forging_plate_armor + required_good_hits = 12 + +/datum/crafting_bench_recipe/plate_gloves + recipe_name = "plate gloves" + recipe_requirements = list( + /obj/item/forging/complete/plate = 2, + ) + resulting_item = /obj/item/clothing/gloves/forging_plate_gloves + required_good_hits = 4 + +/datum/crafting_bench_recipe/plate_boots + recipe_name = "plate boots" + recipe_requirements = list( + /obj/item/forging/complete/plate = 4, + ) + resulting_item = /obj/item/clothing/shoes/forging_plate_boots + required_good_hits = 8 + +/datum/crafting_bench_recipe/ring + recipe_name = "ring" + recipe_requirements = list( + /obj/item/forging/complete/chain = 2, + ) + resulting_item = /obj/item/clothing/gloves/ring/reagent_clothing + required_good_hits = 4 + +// /datum/crafting_bench_recipe/collar +// recipe_name = "collar" +// recipe_requirements = list( +// /obj/item/forging/complete/chain = 3, +// ) +// resulting_item = /obj/item/clothing/neck/collar/reagent_clothing +// required_good_hits = 6 + +/datum/crafting_bench_recipe/handcuffs + recipe_name = "handcuffs" + recipe_requirements = list( + /obj/item/forging/complete/chain = 5, + ) + resulting_item = /obj/item/restraints/handcuffs/reagent_clothing + required_good_hits = 10 + +/datum/crafting_bench_recipe/pavise + recipe_name = "pavise" + recipe_requirements = list( + /obj/item/forging/complete/plate = 8, + ) + resulting_item = /obj/item/shield/buckler/reagent_weapon/pavise + required_good_hits = 16 + +/datum/crafting_bench_recipe/buckler + recipe_name = "buckler" + recipe_requirements = list( + /obj/item/forging/complete/plate = 5, + ) + resulting_item = /obj/item/shield/buckler/reagent_weapon + required_good_hits = 10 + +/datum/crafting_bench_recipe/seed_mesh + recipe_name = "seed mesh" + recipe_requirements = list( + /obj/item/forging/complete/plate = 1, + /obj/item/forging/complete/chain = 2, + ) + resulting_item = /obj/item/seed_mesh + required_good_hits = 10 + +/datum/crafting_bench_recipe/centrifuge + recipe_name = "centrifuge" + recipe_requirements = list( + /obj/item/forging/complete/plate = 1, + ) + resulting_item = /obj/item/reagent_containers/cup/primitive_centrifuge + required_good_hits = 4 + +/datum/crafting_bench_recipe/soup_pot + recipe_name = "soup pot" + recipe_requirements = list( + /obj/item/forging/complete/plate = 4, + ) + resulting_item = /obj/item/reagent_containers/cup/soup_pot + required_good_hits = 10 + +/datum/crafting_bench_recipe/bokken + recipe_name = "bokken" + recipe_requirements = list( + /obj/item/stack/sheet/mineral/wood = 4, + ) + resulting_item = /obj/item/forging/reagent_weapon/bokken + required_good_hits = 8 + +/datum/crafting_bench_recipe/bow + recipe_name = "bow" + recipe_requirements = list( + /obj/item/stack/sheet/mineral/wood = 4, + ) + resulting_item = /obj/item/forging/incomplete_bow + required_good_hits = 8 diff --git a/modular_doppler/reagent_forging/code/forge.dm b/modular_doppler/reagent_forging/code/forge.dm new file mode 100644 index 0000000000000..27902e41c9cbe --- /dev/null +++ b/modular_doppler/reagent_forging/code/forge.dm @@ -0,0 +1,1008 @@ +/// The baseline time to take for doing actions with the forge, like heating glass, setting ceramics, etc. +#define BASELINE_ACTION_TIME (4 SECONDS) + +/// The basline for how long an item such as molten glass will be kept workable after heating +#define BASELINE_HEATING_DURATION (25 SECONDS) + +/// The amount the forge's temperature will change per process +#define FORGE_DEFAULT_TEMPERATURE_CHANGE 5 +/// The maximum temperature the forge can reach +#define MAX_FORGE_TEMP 100 +/// The minimum temperature for using the forge +#define MIN_FORGE_TEMP 50 +/// The duration that objects heated in the forge are heated for +#define FORGE_HEATING_DURATION (1 MINUTES) + +/// Defines for different levels of the forge, ranging from no level (you play like a noob) to legendary +#define FORGE_LEVEL_YOU_PLAY_LIKE_A_NOOB 1 +#define FORGE_LEVEL_NOVICE 2 +#define FORGE_LEVEL_APPRENTICE 3 +#define FORGE_LEVEL_JOURNEYMAN 4 +#define FORGE_LEVEL_EXPERT 5 +#define FORGE_LEVEL_MASTER 6 +#define FORGE_LEVEL_LEGENDARY 7 + +/// The maximum amount of temperature loss decrease that upgrades can give the forge +#define MAX_TEMPERATURE_LOSS_DECREASE 5 + +/// The chance per piece of wood added that charcoal will form later +#define CHARCOAL_CHANCE 45 + +/// The minimum units of a reagent rerquired to imbue it into a weapon +#define MINIMUM_IMBUING_REAGENT_AMOUNT 100 + +/// Defines for the different levels of smoke coming out of the forge, (good, neutral, bad) are all used for baking, (not cooking) is used for when there is no tray in the forge +#define SMOKE_STATE_NONE 0 +#define SMOKE_STATE_GOOD 1 +#define SMOKE_STATE_NEUTRAL 2 +#define SMOKE_STATE_BAD 3 +#define SMOKE_STATE_NOT_COOKING 4 + +/obj/structure/reagent_forge + name = "forge" + desc = "A structure built out of bricks, for heating up metal, or glass, or ceramic, or food, or anything really." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_structures.dmi' + icon_state = "forge_inactive" + + anchored = TRUE + density = TRUE + + /// What the current internal temperature of the forge is + var/forge_temperature = 0 + /// What temperature the forge is moving towards + var/target_temperature = 0 + /// What the minimum target temperature is, used for upgrades + var/minimum_target_temperature = 0 + /// What is the current reduction for temperature decrease + var/temperature_loss_reduction = 0 + /// How many seconds of weak fuel (wood) does the forge have left + var/forge_fuel_weak = 0 + /// How many seconds of strong fuel (coal) does the forge have left + var/forge_fuel_strong = 0 + /// If the forge is capable of reagent forging or not + var/reagent_forging = FALSE + /// Cooldown time for processing on the forge + COOLDOWN_DECLARE(forging_cooldown) + /// Is the forge in use or not? If true, prevents most interactions with the forge + var/in_use = FALSE + /// The current 'level' of the forge, how upgraded is it from zero to three + var/forge_level = FORGE_LEVEL_YOU_PLAY_LIKE_A_NOOB + /// What smoke particles should be coming out of the forge + var/smoke_state = SMOKE_STATE_NONE + /// Tracks any oven tray placed inside of the forge + var/obj/item/plate/oven_tray/used_tray + /// The list of possible things to make with materials used on the forge + var/static/list/choice_list = list( + "Chain" = /obj/item/forging/incomplete/chain, + "Plate" = /obj/item/forging/incomplete/plate, + "Sword" = /obj/item/forging/incomplete/sword, + "Katana" = /obj/item/forging/incomplete/katana, + "Dagger" = /obj/item/forging/incomplete/dagger, + "Staff" = /obj/item/forging/incomplete/staff, + "Spear" = /obj/item/forging/incomplete/spear, + "Axe" = /obj/item/forging/incomplete/axe, + "Hammer" = /obj/item/forging/incomplete/hammer, + "Pickaxe" = /obj/item/forging/incomplete/pickaxe, + "Shovel" = /obj/item/forging/incomplete/shovel, + "Arrowhead" = /obj/item/forging/incomplete/arrowhead, + "Rail Nail" = /obj/item/forging/incomplete/rail_nail, + "Rail Cart" = /obj/item/forging/incomplete/rail_cart, + ) + /// List of possible choices for the selection radial + var/list/radial_choice_list = list() + /// Blacklist that contains reagents that weapons and armor are unable to be imbued with. + var/static/list/disallowed_reagents = typecacheof(list( + /datum/reagent/inverse/, + /datum/reagent/consumable/entpoly, + /datum/reagent/pax, + /datum/reagent/consumable/liquidelectricity/enriched, + /datum/reagent/teslium, + /datum/reagent/eigenstate, + /datum/reagent/toxin/acid, + /datum/reagent/phlogiston, + /datum/reagent/napalm, + /datum/reagent/thermite, + /datum/reagent/medicine/earthsblood, + /datum/reagent/medicine/ephedrine, + /datum/reagent/medicine/epinephrine, + )) + +/obj/structure/reagent_forge/examine(mob/user) + . = ..() + + if(used_tray) + . += span_notice("It has [used_tray] in it, which can be removed with an empty hand.") + else + . += span_notice("You can place an oven tray in this to bake any items on it.") + + if(forge_level < FORGE_LEVEL_LEGENDARY) + . += span_notice("Using an empty hand on [src] will upgrade it, if your forging skill level is above the current upgrade's level.") + + switch(forge_level) + if(FORGE_LEVEL_YOU_PLAY_LIKE_A_NOOB) + . += span_notice("This forge has not been upgraded yet.") + + if(FORGE_LEVEL_NOVICE) + . += span_notice("This forge has been upgraded by a novice smith.") + + if(FORGE_LEVEL_APPRENTICE) + . += span_notice("This forge has been upgraded by an apprentice smith.") + + if(FORGE_LEVEL_JOURNEYMAN) + . += span_notice("This forge has been upgraded by a journeyman smith.") + + if(FORGE_LEVEL_EXPERT) + . += span_notice("This forge has been upgraded by an expert smith.") + + if(FORGE_LEVEL_MASTER) + . += span_notice("This forge has been upgraded by a master smith.") + + if(FORGE_LEVEL_LEGENDARY) + . += span_hierophant("This forge has been upgraded by a legendary smith.") // Legendary skills give you the greatest gift of all, cool text + + switch(temperature_loss_reduction) + if(0) + . += span_notice("[src] will lose heat at a normal rate.") + if(1) + . += span_notice("[src] will lose heat slightly slower than usual.") + if(2) + . += span_notice("[src] will lose heat a bit slower than usual.") + if(3) + . += span_notice("[src] will lose heat much slower than usual.") + if(4) + . += span_notice("[src] will lose heat signficantly slower than usual.") + if(5) + . += span_notice("[src] will lose heat at a practically negligible rate.") + + . += span_notice("
[src] is currently [forge_temperature] degrees hot, going towards [target_temperature] degrees.
") + + if(reagent_forging && (is_species(user, /datum/species/lizard/ashwalker) || is_species(user, /datum/species/human/felinid/primitive))) + . += span_warning("[src] has a fine gold trim, it is ready to imbue chemicals into reagent objects.") + + return . + +/obj/structure/reagent_forge/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + populate_radial_choice_list() + update_appearance() + upgrade_forge(forced = TRUE) + +/// Fills out the radial choice list with everything in the choice_list's contents +/obj/structure/reagent_forge/proc/populate_radial_choice_list() + if(!length(choice_list)) + return + + if(length(radial_choice_list)) + return + + for(var/forge_option in choice_list) + var/obj/resulting_item = choice_list[forge_option] + radial_choice_list[forge_option] = image(icon = initial(resulting_item.icon), icon_state = initial(resulting_item.icon_state)) + +/obj/structure/reagent_forge/Destroy() + STOP_PROCESSING(SSobj, src) + QDEL_NULL(particles) + if(used_tray) + QDEL_NULL(used_tray) + . = ..() + +/obj/structure/reagent_forge/update_appearance(updates) + . = ..() + cut_overlays() + + if(reagent_forging) // If we can do reagent forging, give the forge the gold trim + var/image/gold_overlay = image(icon = icon, icon_state = "forge_masterwork_trim") + add_overlay(gold_overlay) + + if(used_tray) // If we have a tray inside, check if the forge is on or not, then give the corresponding tray overlay + var/image/tray_overlay = image(icon = icon, icon_state = "forge_tray_[check_fuel(just_checking = TRUE) ? "active" : "inactive"]") + add_overlay(tray_overlay) + +/// Checks if the forge has fuel, if so what type. If it has either type of fuel, returns TRUE, otherwise returns FALSE. just_checking will check if there is fuel without taking actions +/obj/structure/reagent_forge/proc/check_fuel(just_checking = FALSE) + if(forge_fuel_strong) // Check for strong fuel (coal) first, as it has more power over weaker fuels + if(just_checking) + return TRUE + + forge_fuel_strong -= 5 SECONDS + target_temperature = 100 + return TRUE + + if(forge_fuel_weak) // If there's no strong fuel, maybe we have weak fuel (wood) + if(just_checking) + return TRUE + + forge_fuel_weak -= 5 SECONDS + target_temperature = 50 + return TRUE + + if(just_checking) + return FALSE + + target_temperature = minimum_target_temperature // If the forge has no fuel, then we should lowly return to the minimum lowest temp we can do + return FALSE + +/// Gives the forge the ability to imbue reagents into things +/obj/structure/reagent_forge/proc/create_reagent_forge() + if(reagent_forging) // If the forge can already do reagent forging, then we can skip the rest of this + return + reagent_forging = TRUE + update_appearance() + +/// Creates both a fail message balloon alert, and sets in_use to false +/obj/structure/reagent_forge/proc/fail_message(mob/living/user, message) + balloon_alert(user, message) + in_use = FALSE + +/// Adjust the temperature to head towards the target temperature, changing icon and creating light if the temperature is rising +/obj/structure/reagent_forge/proc/check_temp() + if(forge_temperature > target_temperature) // Being above the target temperature will cause the forge to cool down + forge_temperature -= (FORGE_DEFAULT_TEMPERATURE_CHANGE - temperature_loss_reduction) + return + + else if((forge_temperature < target_temperature) && (forge_fuel_weak || forge_fuel_strong)) // Being below the target temp, and having fuel, will cause the temp to rise + forge_temperature += FORGE_DEFAULT_TEMPERATURE_CHANGE + return + +/// If the forge is in use, checks if there is an oven tray, then if there are any mobs actually in use range. If not sets the forge to not be in use. +/obj/structure/reagent_forge/proc/check_in_use() + if(!in_use) + return + + if(used_tray) // We check if there's a tray because trays inside of the forge count as it being in use, even if nobody is around + return + + for(var/mob/living/living_mob in range(1,src)) + if(!living_mob) + in_use = FALSE + +/// Spawns a piece of coal at the forge and renames it to charcoal +/obj/structure/reagent_forge/proc/spawn_coal() + var/obj/item/stack/sheet/mineral/coal/spawn_coal = new(get_turf(src)) + spawn_coal.name = "charcoal" + +/obj/structure/reagent_forge/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, forging_cooldown)) + return + + COOLDOWN_START(src, forging_cooldown, 5 SECONDS) + check_fuel() + check_temp() + check_in_use() // This is here to ensure the forge doesn't remain in_use if it really isn't + + + + if(!used_tray && check_fuel(just_checking = TRUE)) + set_smoke_state(SMOKE_STATE_NOT_COOKING) // If there is no tray but we have fuel, use the not cooking smoke state + return + + if(!check_fuel(just_checking = TRUE)) // If there's no fuel, remove it all + set_smoke_state(SMOKE_STATE_NONE) + return + + handle_baking_things(seconds_per_tick) + +/// Sends signals to bake and items on the used tray, setting the smoke state of the forge according to the most cooked item in it +/obj/structure/reagent_forge/proc/handle_baking_things(seconds_per_tick) + if(forge_temperature < MIN_FORGE_TEMP) // If we are below minimum forge temp, don't continue on to cooking + return + + /// The worst off item being baked in our forge right now, to ensure people know when gordon ramsay is gonna be upset at them + var/worst_cooked_food_state = 0 + for(var/obj/item/baked_item as anything in used_tray.contents) + + var/signal_result = SEND_SIGNAL(baked_item, COMSIG_ITEM_OVEN_PROCESS, src, seconds_per_tick) + + if(signal_result & COMPONENT_HANDLED_BAKING) + if(signal_result & COMPONENT_BAKING_GOOD_RESULT && worst_cooked_food_state < SMOKE_STATE_GOOD) + worst_cooked_food_state = SMOKE_STATE_GOOD + else if(signal_result & COMPONENT_BAKING_BAD_RESULT && worst_cooked_food_state < SMOKE_STATE_NEUTRAL) + worst_cooked_food_state = SMOKE_STATE_NEUTRAL + continue + + worst_cooked_food_state = SMOKE_STATE_BAD + baked_item.fire_act(1000) // Overcooked food really does burn, hot hot hot! + + if(SPT_PROB(10, seconds_per_tick)) + var/list/asomnia_havers = get_hearers_in_view(DEFAULT_MESSAGE_RANGE, src) + for(var/mob/cannot_smell in asomnia_havers) + if(!HAS_TRAIT(cannot_smell, TRAIT_ANOSMIA)) + asomnia_havers -= cannot_smell + visible_message(span_danger("You smell a burnt smell coming from [src]!"), ignored_mobs = asomnia_havers) + // Give indication that something is burning in the oven + set_smoke_state(worst_cooked_food_state) + +/// Sets the type of particles that the forge should be generating +/obj/structure/reagent_forge/proc/set_smoke_state(new_state) + if(new_state == smoke_state) + return + + smoke_state = new_state + + QDEL_NULL(particles) + + switch(smoke_state) + if(SMOKE_STATE_NONE) + icon_state = "forge_inactive" + set_light(0, 0) // If we aren't heating up and thus not on fire, turn the fire light off + return + + if(SMOKE_STATE_BAD) + particles = new /particles/smoke() + particles.position = list(6, 4, 0) + + if(SMOKE_STATE_NEUTRAL) + particles = new /particles/smoke/steam() + particles.position = list(6, 4, 0) + + if(SMOKE_STATE_GOOD) + particles = new /particles/smoke/steam/mild() + particles.position = list(6, 4, 0) + + if(SMOKE_STATE_NOT_COOKING) + particles = new /particles/smoke/mild() + particles.position = list(6, 4, 0) + + icon_state = "forge_active" + set_light(3, 1, LIGHT_COLOR_FIRE) + +/obj/structure/reagent_forge/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(used_tray) + remove_tray_from_forge(user) + return + + upgrade_forge(user) + +/obj/structure/reagent_forge/proc/upgrade_forge(mob/living/user, forced = FALSE) + var/level_to_upgrade_to + + if(forced || !user) // This is to make sure the ready subtype of forge still works + level_to_upgrade_to = forge_level + else + level_to_upgrade_to = user.mind.get_skill_level(/datum/skill/smithing) + + if((forge_level == level_to_upgrade_to) && !forced) + to_chat(user, span_notice("[src] was already upgraded by your level of expertise!")) + return + + switch(level_to_upgrade_to) // Remember to carry things over from past levels in case someone skips levels in upgrading + if(SKILL_LEVEL_NONE) + if(!forced) + to_chat(user, span_notice("You'll need some forging skills to really understand how to upgrade [src].")) + return + + if(SKILL_LEVEL_NOVICE) + if(!forced) + to_chat(user, span_notice("With some experience, you've come to realize there are some easily fixable spots with poor insulation...")) + temperature_loss_reduction = 1 + forge_level = FORGE_LEVEL_NOVICE + + if(SKILL_LEVEL_APPRENTICE) + if(!forced) + to_chat(user, span_notice("Further insulation and protection of the thinner areas means [src] will lose heat just that little bit slower.")) + temperature_loss_reduction = 2 + forge_level = FORGE_LEVEL_APPRENTICE + + if(SKILL_LEVEL_JOURNEYMAN) + if(!forced) + to_chat(user, span_notice("Some careful placement and stoking of the flame will allow you to keep at least the embers burning...")) + minimum_target_temperature = 25 // Will allow quicker reheating from having no fuel + temperature_loss_reduction = 3 + forge_level = FORGE_LEVEL_JOURNEYMAN + + if(SKILL_LEVEL_EXPERT) + if(!forced) + to_chat(user, span_notice("[src] has become nearly perfect, able to hold heat for long enough that even a piece of wood can outmatch the longevity of lesser forges.")) + temperature_loss_reduction = 4 + minimum_target_temperature = 25 + forge_level = FORGE_LEVEL_EXPERT + + if(SKILL_LEVEL_MASTER) + if(!forced) + to_chat(user, span_notice("The perfect forge for a perfect metalsmith, with your knowledge it should bleed heat so slowly, that not even you will live to see [src] cool.")) + temperature_loss_reduction = MAX_TEMPERATURE_LOSS_DECREASE + minimum_target_temperature = 25 + forge_level = FORGE_LEVEL_MASTER + + if(SKILL_LEVEL_LEGENDARY) + if(!forced) + if(is_species(user, /datum/species/lizard/ashwalker) || is_species(user, /datum/species/human/felinid/primitive)) + to_chat(user, span_notice("With just the right heat treating technique, metal could be made to accept reagents...")) + create_reagent_forge() + if(forge_level == FORGE_LEVEL_MASTER) + to_chat(user, span_warning("It is impossible to further improve the forge!")) + temperature_loss_reduction = MAX_TEMPERATURE_LOSS_DECREASE + minimum_target_temperature = 25 // This won't matter except in a few cases here, but we still need to cover those few cases + forge_level = FORGE_LEVEL_LEGENDARY + + playsound(src, 'sound/weapons/parry.ogg', 50, TRUE) // Play a feedback sound to really let players know we just did an upgrade + +/obj/structure/reagent_forge/attackby(obj/item/attacking_item, mob/living/user, params) + if(!used_tray && istype(attacking_item, /obj/item/plate/oven_tray)) + add_tray_to_forge(user, attacking_item) + return TRUE + + if(in_use) // If the forge is currently in use by someone (or there is a tray in it) then we cannot use it + if(used_tray) + balloon_alert(user, "remove [used_tray] first") + balloon_alert(user, "forge busy") + return TRUE + + if(istype(attacking_item, /obj/item/stack/sheet/mineral/wood)) // Wood is a weak fuel, and will only get the forge up to 50 temperature + refuel(attacking_item, user) + return TRUE + + if(istype(attacking_item, /obj/item/stack/sheet/mineral/coal)) // Coal is a strong fuel that doesn't need bellows to heat up properly + refuel(attacking_item, user, TRUE) + return TRUE + + if(istype(attacking_item, /obj/item/stack/ore)) + smelt_ore(attacking_item, user) + return TRUE + + if(attacking_item.GetComponent(/datum/component/reagent_weapon)) + handle_weapon_imbue(attacking_item, user) + return TRUE + + if(attacking_item.GetComponent(/datum/component/reagent_clothing)) + handle_clothing_imbue(attacking_item, user) + return TRUE + + if(istype(attacking_item, /obj/item/ceramic)) + handle_ceramics(attacking_item, user) + return TRUE + + if(istype(attacking_item, /obj/item/stack/sheet/glass)) + handle_glass_sheet_melting(attacking_item, user) + return TRUE + + if(istype(attacking_item, /obj/item/glassblowing/metal_cup)) + handle_metal_cup_melting(attacking_item, user) + return TRUE + + if(istype(attacking_item, /obj/item/stack/rods)) + in_use = TRUE + smelt_iron_rods(attacking_item, user) + in_use = FALSE + return TRUE + + return ..() + +/// Take the given tray and place it inside the forge, updating everything relevant to that +/obj/structure/reagent_forge/proc/add_tray_to_forge(mob/living/user, obj/item/plate/oven_tray/tray) + if(used_tray) // This shouldn't be able to happen but just to be safe + balloon_alert_to_viewers("already has tray") + return + + if(!user.transferItemToLoc(tray, src, silent = FALSE)) + return + + // need to send the right signal for each item in the tray + for(var/obj/item/baked_item in tray.contents) + SEND_SIGNAL(baked_item, COMSIG_ITEM_OVEN_PLACED_IN, src, user) + + balloon_alert_to_viewers("put [tray] in [src]") + used_tray = tray + in_use = TRUE // You can't use the forge if there's a tray sitting in it + update_appearance() + +/// Take the used_tray and spit it out, updating everything relevant to that +/obj/structure/reagent_forge/proc/remove_tray_from_forge(mob/living/carbon/user) + if(!used_tray) + if(user) + balloon_alert_to_viewers("no tray") + return + + if(user) + user.put_in_hands(used_tray) + balloon_alert_to_viewers("removed [used_tray]") + else + used_tray.forceMove(get_turf(src)) + used_tray = null + in_use = FALSE + +/// Adds to either the strong or weak fuel timers from the given stack +/obj/structure/reagent_forge/proc/refuel(obj/item/stack/refueling_stack, mob/living/user, is_strong_fuel = FALSE) + in_use = TRUE + + if(is_strong_fuel) + if(forge_fuel_strong >= 5 MINUTES) + fail_message(user, "[src] is full on coal") + return + if(forge_fuel_weak >= 5 MINUTES) + fail_message(user, "[src] is full on wood") + return + + balloon_alert_to_viewers("refueling...") + + var/obj/item/stack/sheet/stack_sheet = refueling_stack + if(!do_after(user, 3 SECONDS, target = src) || !stack_sheet.use(1)) + fail_message(user, "stopped fueling") + return + + if(is_strong_fuel) + forge_fuel_strong += 5 MINUTES + else + forge_fuel_weak += 5 MINUTES + in_use = FALSE + balloon_alert(user, "fueled [src]") + user.mind.adjust_experience(/datum/skill/smithing, 5) // You gain small amounts of experience from useful fueling + + if(prob(CHARCOAL_CHANCE) && !is_strong_fuel) + to_chat(user, span_notice("[src]'s fuel is packed densely enough to have made some charcoal!")) + addtimer(CALLBACK(src, PROC_REF(spawn_coal)), 1 MINUTES) + +/// Takes given ore and smelts it, possibly producing extra sheets if upgraded +/obj/structure/reagent_forge/proc/smelt_ore(obj/item/stack/ore/ore_item, mob/living/user) + in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + return + + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) + + if(!ore_item.refined_type) + fail_message(user, "cannot smelt [ore_item]") + return + + balloon_alert_to_viewers("smelting...") + + if(!do_after(user, skill_modifier * 3 SECONDS, target = src)) + fail_message(user, "stopped smelting [ore_item]") + return + + var/src_turf = get_turf(src) + var/spawning_item = ore_item.refined_type + var/ore_to_sheet_amount = ore_item.amount + + for(var/spawn_ore in 1 to ore_to_sheet_amount) + new spawning_item(src_turf) + + in_use = FALSE + qdel(ore_item) + return + +/// Handles weapon reagent imbuing +/obj/structure/reagent_forge/proc/handle_weapon_imbue(obj/attacking_item, mob/living/user) + //This code will refuse all non-ashwalkers & non-icecats from imbuing + if(!ishuman(user)) + to_chat(user, span_danger("It is impossible for you to imbue!")) //maybe remove (ashwalkers & icecats only) after some time + return + + var/mob/living/carbon/human/human_user = user + if(!is_species(human_user, /datum/species/lizard/ashwalker) && !is_species(human_user, /datum/species/human/felinid/primitive)) + to_chat(user, span_danger("It is impossible for you to imbue!")) //maybe remove (ashwalkers & icecats only) after some time + return + + in_use = TRUE + balloon_alert_to_viewers("imbuing...") + + var/obj/item/attacking_weapon = attacking_item + + var/datum/component/reagent_weapon/weapon_component = attacking_weapon.GetComponent(/datum/component/reagent_weapon) + if(!weapon_component) + fail_message(user, "cannot imbue") + return + + if(length(weapon_component.imbued_reagent)) + fail_message(user, "already imbued") + return + + if(!do_after(user, 10 SECONDS, target = src)) + fail_message(user, "stopped imbuing") + return + + for(var/datum/reagent/weapon_reagent as anything in attacking_weapon.reagents.reagent_list) + if(weapon_reagent.volume < MINIMUM_IMBUING_REAGENT_AMOUNT) + attacking_weapon.reagents.remove_reagent(weapon_reagent.type) + continue + + if(is_type_in_typecache(weapon_reagent, disallowed_reagents)) + balloon_alert(user, "cannot imbue with [weapon_reagent.name]") + attacking_weapon.reagents.remove_reagent(weapon_reagent.type, include_subtypes = TRUE) + continue + + weapon_component.imbued_reagent += weapon_reagent.type + attacking_weapon.name = "[weapon_reagent.name] [attacking_weapon.name]" + + attacking_weapon.color = mix_color_from_reagents(attacking_weapon.reagents.reagent_list) + balloon_alert_to_viewers("imbued [attacking_weapon]") + user.mind.adjust_experience(/datum/skill/smithing, 60) + playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) + in_use = FALSE + return TRUE + +/// Handles clothing imbuing, extremely similar to weapon imbuing but not in the same proc because of how uhh... goofy the way this has to be done is +/obj/structure/reagent_forge/proc/handle_clothing_imbue(obj/attacking_item, mob/living/user) + //This code will refuse all non-ashwalkers & non-icecats from imbuing + if(!ishuman(user)) + to_chat(user, span_danger("It is impossible for you to imbue!")) //maybe remove (ashwalkers & icecats only) after some time + return + + var/mob/living/carbon/human/human_user = user + if(!is_species(human_user, /datum/species/lizard/ashwalker) && !is_species(human_user, /datum/species/human/felinid/primitive)) + to_chat(user, span_danger("It is impossible for you to imbue!")) //maybe remove (ashwalkers & icecats only) after some time + return + + in_use = TRUE + balloon_alert_to_viewers("imbuing...") + + var/obj/item/attacking_clothing = attacking_item + + var/datum/component/reagent_clothing/clothing_component = attacking_clothing.GetComponent(/datum/component/reagent_clothing) + if(!clothing_component) + fail_message(user, "cannot imbue") + return + + if(length(clothing_component.imbued_reagent)) + fail_message(user, "already imbued") + return + + if(!do_after(user, 10 SECONDS, target = src)) + fail_message(user, "stopped imbuing") + return + + for(var/datum/reagent/clothing_reagent as anything in attacking_clothing.reagents.reagent_list) + if(clothing_reagent.volume < MINIMUM_IMBUING_REAGENT_AMOUNT) + attacking_clothing.reagents.remove_reagent(clothing_reagent.type, include_subtypes = TRUE) + continue + + if(is_type_in_typecache(clothing_reagent, disallowed_reagents)) + balloon_alert(user, "cannot imbue with [clothing_reagent.name]") + attacking_clothing.reagents.remove_reagent(clothing_reagent.type, include_subtypes = TRUE) + continue + + clothing_component.imbued_reagent += clothing_reagent.type + attacking_clothing.name = "[clothing_reagent.name] [attacking_clothing.name]" + + attacking_clothing.color = mix_color_from_reagents(attacking_clothing.reagents.reagent_list) + balloon_alert_to_viewers("imbued [attacking_clothing]") + user.mind.adjust_experience(/datum/skill/smithing, 60) + playsound(src, 'sound/magic/demon_consume.ogg', 50, TRUE) + in_use = FALSE + return TRUE + +/// Sets ceramic items from their unusable state into their finished form +/obj/structure/reagent_forge/proc/handle_ceramics(obj/attacking_item, mob/living/user) + in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + return + + var/obj/item/ceramic/ceramic_item = attacking_item + var/ceramic_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * BASELINE_ACTION_TIME + + if(!ceramic_item.forge_item) + fail_message(user, "cannot set [ceramic_item]") + return + + balloon_alert_to_viewers("setting [ceramic_item]") + + if(!do_after(user, ceramic_speed, target = src)) + fail_message("stopped setting [ceramic_item]") + return + + balloon_alert(user, "finished setting [ceramic_item]") + var/obj/item/ceramic/spawned_ceramic = new ceramic_item.forge_item(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 50) + spawned_ceramic.color = ceramic_item.color + qdel(ceramic_item) + in_use = FALSE + +/// Handles the creation of molten glass from glass sheets +/obj/structure/reagent_forge/proc/handle_glass_sheet_melting(obj/attacking_item, mob/living/user) + in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + return + + var/obj/item/stack/sheet/glass/glass_item = attacking_item + var/glassblowing_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * BASELINE_ACTION_TIME + var/glassblowing_amount = BASELINE_HEATING_DURATION / user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) + + balloon_alert_to_viewers("heating...") + + if(!do_after(user, glassblowing_speed, target = src) || !glass_item.use(1)) + fail_message(user, "stopped heating [glass_item]") + return + + in_use = FALSE + var/obj/item/glassblowing/molten_glass/spawned_glass = new /obj/item/glassblowing/molten_glass(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 10) + COOLDOWN_START(spawned_glass, remaining_heat, glassblowing_amount) + spawned_glass.total_time = glassblowing_amount + +/// Handles creating molten glass from a metal cup filled with sand +/obj/structure/reagent_forge/proc/handle_metal_cup_melting(obj/attacking_item, mob/living/user) + in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + return + + var/obj/item/glassblowing/metal_cup/metal_item = attacking_item + var/glassblowing_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * BASELINE_ACTION_TIME + var/glassblowing_amount = BASELINE_HEATING_DURATION / user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) + + if(!metal_item.has_sand) + fail_message(user, "[metal_item] has no sand") + return + + balloon_alert_to_viewers("heating...") + + if(!do_after(user, glassblowing_speed, target = src)) + fail_message(user, "stopped heating [metal_item]") + return + + in_use = FALSE + metal_item.has_sand = FALSE + metal_item.icon_state = "metal_cup_empty" // This should be handled a better way but presently this is how it works + var/obj/item/glassblowing/molten_glass/spawned_glass = new /obj/item/glassblowing/molten_glass(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 10) + COOLDOWN_START(spawned_glass, remaining_heat, glassblowing_amount) + spawned_glass.total_time = glassblowing_amount + +/// Almost a copy from the proc smelt_ore but to smelt iron rods +/obj/structure/reagent_forge/proc/smelt_iron_rods(obj/attacking_item, mob/living/user) + + var/obj/item/stack/rods/rod_item = attacking_item + + if(!istype(rod_item)) + return + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + return + + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) + + if(rod_item.amount < 2) + fail_message(user, "too few iron rods to smelt") + return + + balloon_alert_to_viewers("smelting...") + + if(!do_after(user, skill_modifier * 3 SECONDS, target = src)) + fail_message(user, "stopped smelting [rod_item]") + return + + var/rods_to_sheet_amount = round((rod_item.amount / 2)) + var/used_rods = rod_item.amount + + if(ISODD(used_rods)) + used_rods = used_rods - 1 + + rod_item.use(used_rods) + new /obj/item/stack/sheet/iron(drop_location(), rods_to_sheet_amount) + + balloon_alert_to_viewers("finished smelting!") + +/obj/structure/reagent_forge/billow_act(mob/living/user, obj/item/tool) + if(in_use) // Preventing billow use if the forge is in use to prevent spam + fail_message(user, "forge busy") + return ITEM_INTERACT_SUCCESS + + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) + var/obj/item/forging/forge_item = tool + + if(!forge_fuel_strong && !forge_fuel_weak) + fail_message(user, "no fuel in [src]") + return ITEM_INTERACT_SUCCESS + + if(forge_temperature >= MAX_FORGE_TEMP) + fail_message(user, "[src] cannot heat further") + return ITEM_INTERACT_SUCCESS + + balloon_alert_to_viewers("billowing...") + + in_use = TRUE + while(forge_temperature < 91) + if(!do_after(user, (skill_modifier * forge_item.toolspeed) SECONDS, target = src)) + balloon_alert_to_viewers("stopped billowing") + in_use = FALSE + return ITEM_INTERACT_SUCCESS + + forge_temperature += 10 + user.mind.adjust_experience(/datum/skill/smithing, 5) // Billowing, like fueling, gives you some experience in forging + + in_use = FALSE + balloon_alert(user, "successfully heated [src]") + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_forge/tong_act(mob/living/user, obj/item/tool) + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) + var/obj/item/forging/forge_item = tool + + if(in_use || forge_item.in_use) + fail_message(user, "forge busy") + return ITEM_INTERACT_SUCCESS + + in_use = TRUE + forge_item.in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "forge too cool") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + // Here we check the item used on us (tongs) for an incomplete forge item of some kind to heat + var/obj/item/forging/incomplete/search_incomplete = locate(/obj/item/forging/incomplete) in forge_item.contents + if(search_incomplete) + if(!COOLDOWN_FINISHED(search_incomplete, heating_remainder)) + fail_message(user, "metal doesn't need heating") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + balloon_alert_to_viewers("heating [search_incomplete]") + + if(!do_after(user, skill_modifier * forge_item.toolspeed, target = src)) + balloon_alert_to_viewers("stopped heating [search_incomplete]") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + COOLDOWN_START(search_incomplete, heating_remainder, FORGE_HEATING_DURATION) + in_use = FALSE + forge_item.in_use = FALSE + user.mind.adjust_experience(/datum/skill/smithing, 5) // Heating up forge items grants some experience + balloon_alert(user, "successfully heated [search_incomplete]") + return ITEM_INTERACT_SUCCESS + + // Here we check the item used on us (tongs) for a stack of some kind to create an object from + var/obj/item/stack/search_stack = locate(/obj/item/stack) in forge_item.contents + if(search_stack) + var/user_choice = show_radial_menu(user, src, radial_choice_list, radius = 38, require_near = TRUE, tooltips = TRUE) + if(!user_choice) + fail_message(user, "nothing chosen") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + // Sets up a list of the materials to give to the item later + var/list/material_list = list() + + if(search_stack.material_type) + material_list[GET_MATERIAL_REF(search_stack.material_type)] = SHEET_MATERIAL_AMOUNT + + else + for(var/material as anything in search_stack.custom_materials) + material_list[material] = SHEET_MATERIAL_AMOUNT + + if(!search_stack.use(1)) + fail_message(user, "not enough of [search_stack]") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + balloon_alert_to_viewers("heating [search_stack]") + + if(!do_after(user, skill_modifier * forge_item.toolspeed, target = src)) + balloon_alert_to_viewers("stopped heating [search_stack]") + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + + var/spawn_item = choice_list[user_choice] + var/obj/item/forging/incomplete/incomplete_item = new spawn_item(get_turf(src)) + + if(material_list) + incomplete_item.set_custom_materials(material_list) + + COOLDOWN_START(incomplete_item, heating_remainder, FORGE_HEATING_DURATION) + in_use = FALSE + forge_item.in_use = FALSE + balloon_alert(user, "prepared [search_incomplete] into [user_choice]") + search_stack = locate(/obj/item/stack) in forge_item.contents + + if(!search_stack) + forge_item.icon_state = "tong_empty" + return ITEM_INTERACT_SUCCESS + + in_use = FALSE + forge_item.in_use = FALSE + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_forge/blowrod_act(mob/living/user, obj/item/tool) + var/obj/item/glassblowing/blowing_rod/blowing_item = tool + var/glassblowing_speed = user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) * BASELINE_ACTION_TIME + var/glassblowing_amount = BASELINE_HEATING_DURATION / user.mind.get_skill_modifier(/datum/skill/production, SKILL_SPEED_MODIFIER) + + if(in_use) + to_chat(user, span_warning("You cannot do multiple things at the same time!")) + return ITEM_INTERACT_SUCCESS + in_use = TRUE + + if(forge_temperature < MIN_FORGE_TEMP) + fail_message(user, "The temperature is not hot enough to start heating [blowing_item].") + return ITEM_INTERACT_SUCCESS + + var/obj/item/glassblowing/molten_glass/find_glass = locate() in blowing_item.contents + if(!find_glass) + fail_message(user, "[blowing_item] does not have any glass to heat up.") + return ITEM_INTERACT_SUCCESS + + if(!COOLDOWN_FINISHED(find_glass, remaining_heat)) + fail_message(user, "[find_glass] is still has remaining heat.") + return ITEM_INTERACT_SUCCESS + + to_chat(user, span_notice("You begin heating up [blowing_item].")) + + if(!do_after(user, glassblowing_speed, target = src)) + fail_message(user, "[blowing_item] is interrupted in its heating process.") + return ITEM_INTERACT_SUCCESS + + COOLDOWN_START(find_glass, remaining_heat, glassblowing_amount) + find_glass.total_time = glassblowing_amount + to_chat(user, span_notice("You finish heating up [blowing_item].")) + user.mind.adjust_experience(/datum/skill/smithing, 5) + user.mind.adjust_experience(/datum/skill/production, 10) + in_use = FALSE + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_forge/wrench_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + deconstruct(TRUE) + return TRUE + +/obj/structure/reagent_forge/atom_deconstruct(disassembled) + new /obj/item/stack/sheet/iron/ten(get_turf(src)) + return ..() + +/obj/structure/reagent_forge/tier2 + forge_level = FORGE_LEVEL_NOVICE + +/obj/structure/reagent_forge/tier3 + forge_level = FORGE_LEVEL_APPRENTICE + +/obj/structure/reagent_forge/tier4 + forge_level = FORGE_LEVEL_JOURNEYMAN + +/obj/structure/reagent_forge/tier5 + forge_level = FORGE_LEVEL_EXPERT + +/obj/structure/reagent_forge/tier6 + forge_level = FORGE_LEVEL_MASTER + +/obj/structure/reagent_forge/tier7 + forge_level = FORGE_LEVEL_LEGENDARY + +/obj/structure/reagent_forge/tier7/imbuing/Initialize(mapload) + . = ..() + create_reagent_forge() + +/particles/smoke/mild + spawning = 1 + velocity = list(0, 0.3, 0) + friction = 0.25 + +#undef BASELINE_ACTION_TIME + +#undef BASELINE_HEATING_DURATION + +#undef FORGE_DEFAULT_TEMPERATURE_CHANGE +#undef MAX_FORGE_TEMP +#undef MIN_FORGE_TEMP +#undef FORGE_HEATING_DURATION + +#undef FORGE_LEVEL_YOU_PLAY_LIKE_A_NOOB +#undef FORGE_LEVEL_NOVICE +#undef FORGE_LEVEL_APPRENTICE +#undef FORGE_LEVEL_JOURNEYMAN +#undef FORGE_LEVEL_EXPERT +#undef FORGE_LEVEL_MASTER +#undef FORGE_LEVEL_LEGENDARY + +#undef MAX_TEMPERATURE_LOSS_DECREASE + +#undef CHARCOAL_CHANCE + +#undef MINIMUM_IMBUING_REAGENT_AMOUNT + +#undef SMOKE_STATE_NONE +#undef SMOKE_STATE_GOOD +#undef SMOKE_STATE_NEUTRAL +#undef SMOKE_STATE_BAD +#undef SMOKE_STATE_NOT_COOKING diff --git a/modular_doppler/reagent_forging/code/forge_clothing.dm b/modular_doppler/reagent_forging/code/forge_clothing.dm new file mode 100644 index 0000000000000..e4ccfd58ffbf0 --- /dev/null +++ b/modular_doppler/reagent_forging/code/forge_clothing.dm @@ -0,0 +1,152 @@ +// Vests +/obj/item/clothing/suit/armor/forging_plate_armor + name = "reagent plate vest" + desc = "An armor vest made of hammered, interlocking plates." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi' + // worn_icon_better_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi' + // worn_icon_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi' + // worn_icon_teshari = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi' + icon_state = "plate_vest" + supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON + resistance_flags = FIRE_PROOF + obj_flags_doppler = ANVIL_REPAIR + armor_type = /datum/armor/armor_forging_plate_armor + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + +/datum/armor/armor_forging_plate_armor + melee = 40 + bullet = 40 + fire = 50 + wound = 30 + +/obj/item/clothing/suit/armor/forging_plate_armor/Initialize(mapload) + . = ..() + AddComponent(/datum/component/armor_plate, 4) + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_OCLOTHING) + + allowed += /obj/item/forging/reagent_weapon + +// Gloves +/obj/item/clothing/gloves/forging_plate_gloves + name = "reagent plate gloves" + desc = "A set of leather gloves with protective armor plates connected to the wrists." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi' + // worn_icon_better_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi' + // worn_icon_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi' + // worn_icon_teshari = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi' + icon_state = "plate_gloves" + resistance_flags = FIRE_PROOF + obj_flags_doppler = ANVIL_REPAIR + armor_type = /datum/armor/gloves_forging_plate_gloves + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + body_parts_covered = HANDS|ARMS + +/datum/armor/gloves_forging_plate_gloves + melee = 40 + bullet = 40 + fire = 50 + wound = 30 + +/obj/item/clothing/gloves/forging_plate_gloves/Initialize(mapload) + . = ..() + AddComponent(/datum/component/armor_plate, 4) + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_GLOVES) + +// Helmets +/obj/item/clothing/head/helmet/forging_plate_helmet + name = "reagent plate helmet" + desc = "A helmet out of hammered plates with a leather neck guard and chin strap." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi' + // worn_icon_better_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi' + // worn_icon_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi' + // worn_icon_teshari = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi' + icon_state = "plate_helmet" + // supports_variations_flags = CLOTHING_SNOUTED_VARIATION_NO_NEW_ICON + resistance_flags = FIRE_PROOF + flags_inv = null + obj_flags_doppler = ANVIL_REPAIR + armor_type = /datum/armor/helmet_forging_plate_helmet + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + +/datum/armor/helmet_forging_plate_helmet + melee = 40 + bullet = 40 + fire = 50 + wound = 30 + +/obj/item/clothing/head/helmet/forging_plate_helmet/Initialize(mapload) + . = ..() + AddComponent(/datum/component/armor_plate, 4) + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_HEAD) + +// Boots +/obj/item/clothing/shoes/forging_plate_boots + name = "reagent plate boots" + desc = "A pair of leather boots with protective armor plates over the shins and toes." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi' + // worn_icon_digi = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_digi.dmi' + // worn_icon_better_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi' + // worn_icon_vox = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi' + // worn_icon_teshari = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi' + icon_state = "plate_boots" + supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION + armor_type = /datum/armor/shoes_forging_plate_boots + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + resistance_flags = FIRE_PROOF + obj_flags_doppler = ANVIL_REPAIR + can_be_tied = FALSE + body_parts_covered = FEET|LEGS + +/datum/armor/shoes_forging_plate_boots + melee = 20 + bullet = 20 + +/obj/item/clothing/shoes/forging_plate_boots/Initialize(mapload) + . = ..() + AddComponent(/datum/component/armor_plate, 2) + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_FEET) + +// Misc +/obj/item/clothing/gloves/ring/reagent_clothing + name = "reagent ring" + desc = "A tiny ring, sized to wrap around a finger." + icon_state = "ringsilver" + worn_icon_state = "sring" + inhand_icon_state = "ringsilver" + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR + obj_flags_doppler = ANVIL_REPAIR + +/obj/item/clothing/gloves/ring/reagent_clothing/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_GLOVES) + +/obj/item/clothing/neck/collar/reagent_clothing + name = "reagent collar" + desc = "A collar that is ready to be worn for certain individuals." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi' + icon_state = "collar" + inhand_icon_state = null + body_parts_covered = NECK + slot_flags = ITEM_SLOT_NECK + w_class = WEIGHT_CLASS_SMALL + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + obj_flags_doppler = ANVIL_REPAIR + +/obj/item/clothing/neck/collar/reagent_clothing/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_NECK) + +/obj/item/restraints/handcuffs/reagent_clothing + name = "reagent handcuffs" + desc = "A pair of handcuffs that are ready to keep someone captive." + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + obj_flags_doppler = ANVIL_REPAIR + +/obj/item/restraints/handcuffs/reagent_clothing/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_clothing, ITEM_SLOT_HANDCUFFED) diff --git a/modular_doppler/reagent_forging/code/forge_items.dm b/modular_doppler/reagent_forging/code/forge_items.dm new file mode 100644 index 0000000000000..3d19ad9caad6c --- /dev/null +++ b/modular_doppler/reagent_forging/code/forge_items.dm @@ -0,0 +1,326 @@ +GLOBAL_LIST_INIT(allowed_forging_materials, list( + /datum/material/iron, + /datum/material/silver, + /datum/material/gold, + /datum/material/uranium, + /datum/material/bananium, + /datum/material/titanium, + /datum/material/runite, + /datum/material/adamantine, + /datum/material/mythril, + /datum/material/metalhydrogen, + /datum/material/runedmetal, + /datum/material/bronze, + /datum/material/hauntium, + /datum/material/alloy/plasteel, + /datum/material/alloy/plastitanium, + /datum/material/alloy/alien, + /datum/material/cobolterium, + /datum/material/copporcitite, + /datum/material/tinumium, + /datum/material/brussite, +)) + +/obj/item/forging + icon = 'modular_doppler/reagent_forging/icons/obj/forge_items.dmi' + lefthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi' + righthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi' + toolspeed = 1 + ///whether the item is in use or not + var/in_use = FALSE + +/obj/item/forging/tongs + name = "forging tongs" + desc = "A set of tongs specifically crafted for use in forging. A wise man once said 'I lift things up and put them down.'" + icon = 'modular_doppler/reagent_forging/icons/obj/forge_items.dmi' + icon_state = "tong_empty" + tool_behaviour = TOOL_TONG + +/obj/item/forging/tongs/primitive + name = "primitive forging tongs" + toolspeed = 2 + +/obj/item/forging/tongs/attack_self(mob/user, modifiers) + . = ..() + var/obj/search_obj = locate(/obj) in contents + if(search_obj) + search_obj.forceMove(get_turf(src)) + icon_state = "tong_empty" + return + +/obj/item/forging/hammer + name = "forging mallet" + desc = "A mallet specifically crafted for use in forging. Used to slowly shape metal; careful, you could break something with it!" + icon_state = "hammer" + inhand_icon_state = "hammer" + worn_icon_state = "hammer_back" + tool_behaviour = TOOL_HAMMER + ///the list of things that, if attacked, will set the attack speed to rapid + var/static/list/fast_attacks = list( + /obj/structure/reagent_anvil, + /obj/structure/reagent_crafting_bench + ) + +/obj/item/forging/hammer/afterattack(atom/target, mob/user, click_parameters) + . = ..() + if(!is_type_in_list(target, fast_attacks)) + return + user.changeNext_move(CLICK_CD_RAPID) + +/obj/item/forging/hammer/primitive + name = "primitive forging hammer" + +/obj/item/forging/billow + name = "forging billow" + desc = "A billow specifically crafted for use in forging. Used to stoke the flames and keep the forge lit." + icon_state = "billow" + tool_behaviour = TOOL_BILLOW + +/obj/item/forging/billow/primitive + name = "primitive forging billow" + toolspeed = 2 + +//incomplete pre-complete items +/obj/item/forging/incomplete + name = "parent dev item" + desc = "An incomplete forge item, continue to work hard to be rewarded for your efforts." + //the time remaining that you can hammer before too cool + COOLDOWN_DECLARE(heating_remainder) + //the time between each strike + COOLDOWN_DECLARE(striking_cooldown) + ///the amount of times it takes for the item to become ready + var/average_hits = 30 + ///the amount of times the item has been hit currently + var/times_hit = 0 + ///the required time before each strike to prevent spamming + var/average_wait = 1 SECONDS + ///the path of the item that will be spawned upon completion + var/spawn_item + //because who doesn't want to have a plasma sword? + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + +/obj/item/forging/incomplete/tong_act(mob/living/user, obj/item/tool) + . = ..() + if(length(tool.contents) > 0) + user.balloon_alert(user, "tongs are full already!") + return + forceMove(tool) + tool.icon_state = "tong_full" + +/obj/item/forging/incomplete/chain + name = "incomplete chain" + icon_state = "hot_chain" + average_hits = 10 + average_wait = 0.5 SECONDS + spawn_item = /obj/item/forging/complete/chain + +/obj/item/forging/incomplete/plate + name = "incomplete plate" + icon_state = "hot_plate" + average_hits = 10 + average_wait = 0.5 SECONDS + spawn_item = /obj/item/forging/complete/plate + +/obj/item/forging/incomplete/sword + name = "incomplete sword blade" + icon_state = "hot_blade" + spawn_item = /obj/item/forging/complete/sword + +/obj/item/forging/incomplete/katana + name = "incomplete katana blade" + icon_state = "hot_katanablade" + spawn_item = /obj/item/forging/complete/katana + +/obj/item/forging/incomplete/dagger + name = "incomplete dagger blade" + icon_state = "hot_daggerblade" + spawn_item = /obj/item/forging/complete/dagger + +/obj/item/forging/incomplete/staff + name = "incomplete staff head" + icon_state = "hot_staffhead" + spawn_item = /obj/item/forging/complete/staff + +/obj/item/forging/incomplete/spear + name = "incomplete spear head" + icon_state = "hot_spearhead" + spawn_item = /obj/item/forging/complete/spear + +/obj/item/forging/incomplete/axe + name = "incomplete axe head" + icon_state = "hot_axehead" + spawn_item = /obj/item/forging/complete/axe + +/obj/item/forging/incomplete/hammer + name = "incomplete hammer head" + icon_state = "hot_hammerhead" + spawn_item = /obj/item/forging/complete/hammer + +/obj/item/forging/incomplete/pickaxe + name = "incomplete pickaxe head" + icon_state = "hot_pickaxehead" + spawn_item = /obj/item/forging/complete/pickaxe + +/obj/item/forging/incomplete/shovel + name = "incomplete shovel head" + icon_state = "hot_shovelhead" + spawn_item = /obj/item/forging/complete/shovel + +/obj/item/forging/incomplete/arrowhead + name = "incomplete arrowhead" + icon_state = "hot_arrowhead" + average_hits = 12 + average_wait = 0.5 SECONDS + spawn_item = /obj/item/forging/complete/arrowhead + +/obj/item/forging/incomplete/rail_nail + name = "incomplete rail nail" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "hot_nail" + average_hits = 10 + average_wait = 0.5 SECONDS + spawn_item = /obj/item/forging/complete/rail_nail + +/obj/item/forging/incomplete/rail_cart + name = "incomplete rail cart" + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "hot_cart" + spawn_item = /obj/vehicle/ridden/rail_cart + +//"complete" pre-complete items +/obj/item/forging/complete + ///the path of the item that will be created + var/spawning_item + //because who doesn't want to have a plasma sword? + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + +/obj/item/forging/complete/examine(mob/user) + . = ..() + if(spawning_item) + . += span_notice("
In order to finish this item, a workbench will be necessary!") + +/obj/item/forging/complete/chain + name = "chain" + desc = "A singular chain, best used in combination with multiple chains." + icon_state = "chain" + +/obj/item/forging/complete/plate + name = "plate" + desc = "A plate, best used in combination with multiple plates." + icon_state = "plate" + +/obj/item/forging/complete/sword + name = "sword blade" + desc = "A sword blade, ready to get some wood for completion." + icon_state = "blade" + spawning_item = /obj/item/forging/reagent_weapon/sword + +/obj/item/forging/complete/katana + name = "katana blade" + desc = "A katana blade, ready to get some wood for completion." + icon_state = "katanablade" + spawning_item = /obj/item/forging/reagent_weapon/katana + +/obj/item/forging/complete/dagger + name = "dagger blade" + desc = "A dagger blade, ready to get some wood for completion." + icon_state = "daggerblade" + spawning_item = /obj/item/forging/reagent_weapon/dagger + +/obj/item/forging/complete/staff + name = "staff head" + desc = "A staff head, ready to get some wood for completion." + icon_state = "staffhead" + spawning_item = /obj/item/forging/reagent_weapon/staff + +/obj/item/forging/complete/spear + name = "spear head" + desc = "A spear head, ready to get some wood for completion." + icon_state = "spearhead" + spawning_item = /obj/item/forging/reagent_weapon/spear + +/obj/item/forging/complete/axe + name = "axe head" + desc = "An axe head, ready to get some wood for completion." + icon_state = "axehead" + spawning_item = /obj/item/forging/reagent_weapon/axe + +/obj/item/forging/complete/hammer + name = "hammer head" + desc = "A hammer head, ready to get some wood for completion." + icon_state = "hammerhead" + spawning_item = /obj/item/forging/reagent_weapon/hammer + +/obj/item/forging/complete/pickaxe + name = "pickaxe head" + desc = "A pickaxe head, ready to get some wood for completion." + icon_state = "pickaxehead" + spawning_item = /obj/item/pickaxe/reagent_weapon + +/obj/item/forging/complete/shovel + name = "shovel head" + desc = "A shovel head, ready to get some wood for completion." + icon_state = "shovelhead" + spawning_item = /obj/item/shovel/reagent_weapon + +/obj/item/forging/complete/arrowhead + name = "arrowhead" + desc = "An arrowhead, ready to get some wood for completion." + icon_state = "arrowhead" + spawning_item = /obj/item/arrow_spawner + +/obj/item/forging/complete/rail_nail + name = "rail nail" + desc = "A nail, ready to be used with some wood in order to make tracks." + icon = 'modular_doppler/hearthkin/primitive_structures/icons/railroad.dmi' + icon_state = "nail" + spawning_item = /obj/item/stack/rail_track/ten + +/obj/item/forging/incomplete_bow + name = "incomplete longbow" + desc = "A wooden bow that has yet to be strung." + icon_state = "nostring_bow" + +/obj/item/forging/incomplete_bow/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/weaponcrafting/silkstring)) + new /obj/item/gun/ballistic/bow/longbow(get_turf(src)) + qdel(attacking_item) + qdel(src) + return + return ..() + +/obj/item/arrow_spawner + name = "arrow spawner" + desc = "You shouldn't see this." + /// the amount of arrows that are spawned from the spawner + var/spawning_amount = 4 + +/obj/item/arrow_spawner/Initialize(mapload) + . = ..() + var/turf/src_turf = get_turf(src) + for(var/i in 1 to spawning_amount) + new /obj/item/ammo_casing/arrow/(src_turf) + qdel(src) + +/obj/item/stack/tong_act(mob/living/user, obj/item/tool) + . = ..() + if(!(material_type in GLOB.allowed_forging_materials)) + user.balloon_alert(user, "can only forge metal!") + return + if(length(tool.contents) > 0) + user.balloon_alert(user, "tongs are full already!") + return FALSE + if(!material_type && !custom_materials) + user.balloon_alert(user, "invalid material!") + return + forceMove(tool) + tool.icon_state = "tong_full" + +/obj/tong_act(mob/living/user, obj/item/tool) + . = ..() + if(length(tool.contents)) + user.balloon_alert(user, "tongs are full already!") + return FALSE + if(obj_flags_doppler & ANVIL_REPAIR) + forceMove(tool) + tool.icon_state = "tong_full" diff --git a/modular_doppler/reagent_forging/code/forge_recipes.dm b/modular_doppler/reagent_forging/code/forge_recipes.dm new file mode 100644 index 0000000000000..1db5e5f6233f4 --- /dev/null +++ b/modular_doppler/reagent_forging/code/forge_recipes.dm @@ -0,0 +1,30 @@ +/datum/crafting_recipe/primitive_billow + name = "Primitive Forging Billow" + result = /obj/item/forging/billow/primitive + reqs = list(/obj/item/stack/sheet/mineral/wood = 5) + category = CAT_TOOLS + +/datum/crafting_recipe/primitive_tong + name = "Primitive Forging Tong" + result = /obj/item/forging/tongs/primitive + reqs = list(/obj/item/stack/sheet/iron = 5) + category = CAT_TOOLS + +/datum/crafting_recipe/primitive_hammer + name = "Primitive Forging Hammer" + result = /obj/item/forging/hammer/primitive + reqs = list(/obj/item/stack/sheet/iron = 5) + category = CAT_TOOLS + +//cargo supply pack for items +/datum/supply_pack/service/forging_items + name = "Forging Starter Item Pack" + desc = "Featuring: Forging. This pack is full of three items necessary to start your forging career: tongs, hammer, and billow." + cost = CARGO_CRATE_VALUE * 4 + contains = list(/obj/item/forging/tongs, /obj/item/forging/hammer, /obj/item/forging/billow) + crate_name = "forging start items" + crate_type = /obj/structure/closet/crate/forging_items + +/obj/structure/closet/crate/forging_items + name = "forging starter items" + desc = "A crate filled with the items necessary to start forging (billow, hammer, and tongs)." diff --git a/modular_doppler/reagent_forging/code/forge_weapons.dm b/modular_doppler/reagent_forging/code/forge_weapons.dm new file mode 100644 index 0000000000000..7eb24174bc0cf --- /dev/null +++ b/modular_doppler/reagent_forging/code/forge_weapons.dm @@ -0,0 +1,343 @@ +/obj/item/forging/reagent_weapon + icon = 'modular_doppler/reagent_forging/icons/obj/forge_items.dmi' + lefthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi' + righthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_worn.dmi' + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_COLOR + obj_flags = UNIQUE_RENAME + obj_flags_doppler = ANVIL_REPAIR + toolspeed = 0.9 //Slightly better than avg. - A forged hammer or knife is probably better than a standard one + +/obj/item/forging/reagent_weapon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_weapon) + +/obj/item/forging/reagent_weapon/examine(mob/user) + . = ..() + . += span_notice("Using a hammer on [src] will repair its damage!") + +//vanilla sword with no quirks, at least it's good versus other melees :) +/obj/item/forging/reagent_weapon/sword + name = "forged sword" + desc = "A sharp, one-handed sword most adept at blocking opposing melee strikes." + force = 20 + armour_penetration = 10 + icon_state = "sword" + inhand_icon_state = "sword" + worn_icon_state = "sword_back" + belt_icon_state = "sword_belt" + hitsound = 'sound/weapons/bladeslice.ogg' + throwforce = 10 + block_chance = 25 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + sharpness = SHARP_EDGED + max_integrity = 150 + +//katana, one which shall cut through your puny armour +/obj/item/forging/reagent_weapon/katana + name = "forged katana" + desc = "A katana sharp enough to penetrate body armor, but not quite million-times-folded sharp." + force = 20 + armour_penetration = 25 + icon_state = "katana" + inhand_icon_state = "katana" + worn_icon_state = "katana_back" + belt_icon_state = "katana_belt" + hitsound = 'sound/weapons/bladeslice.ogg' + throwforce = 10 + block_chance = 20 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + sharpness = SHARP_EDGED + +//quirky knife that lets you click fast +/obj/item/forging/reagent_weapon/dagger + name = "forged dagger" + desc = "A lightweight dagger with an extremely quick swing!" + force = 13 + icon_state = "dagger" + inhand_icon_state = "dagger" + worn_icon_state = "dagger_back" + belt_icon_state = "dagger_belt" + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 4 + embed_type = /datum/embed_data/forged_dagger + throwforce = 15 + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + sharpness = SHARP_EDGED + tool_behaviour = TOOL_KNIFE + +/datum/embed_data/forged_dagger + embed_chance = 50 + fall_chance = 1 + pain_mult = 2 + +//what a cute gimmick +/obj/item/forging/reagent_weapon/dagger/attack(mob/living/M, mob/living/user, params) + . = ..() + user.changeNext_move(CLICK_CD_RANGE) + +//this isnt a weapon... +/obj/item/forging/reagent_weapon/staff + name = "forged staff" + desc = "A staff most notably capable of being imbued with reagents, especially useful alongside its otherwise harmless nature." + force = 0 + icon_state = "staff" + inhand_icon_state = "staff" + worn_icon_state = "staff_back" + throwforce = 0 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("bonks", "bashes", "whacks", "pokes", "prods") + attack_verb_simple = list("bonk", "bash", "whack", "poke", "prod") + +//omg, two tile range! surely i wont lose a fight now... +/obj/item/forging/reagent_weapon/spear + name = "forged spear" + desc = "A long spear that can be wielded in two hands to boost damage at the cost of single-handed versatility." + force = 13 + armour_penetration = 15 + icon_state = "spear" + inhand_icon_state = "spear" + worn_icon_state = "spear_back" + throwforce = 22 + throw_speed = 4 + embed_data = /datum/embed_data/forged_spear + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb_continuous = list("attacks", "pokes", "jabs", "tears", "lacerates", "gores") + attack_verb_simple = list("attack", "poke", "jab", "tear", "lacerate", "gore") + wound_bonus = -15 + bare_wound_bonus = 15 + reach = 2 + sharpness = SHARP_EDGED + +/datum/embed_data/forged_spear + embed_chance = 75 + fall_chance = 0 + pain_mult = 6 + +//this is 1:1 with the bonespear, lets use this as a 'balance anchor'. weapons that blatantly outclass this are powercrept. +/obj/item/forging/reagent_weapon/spear/Initialize(mapload) + . = ..() + AddComponent(/datum/component/two_handed, force_unwielded = 13, force_wielded = 23) + +//throwing weapons, what a fun gimmick. lets make them actually worth using +/obj/item/forging/reagent_weapon/axe + name = "forged axe" + desc = "An axe especially balanced for throwing and embedding into fleshy targets. Nonetheless useful as a traditional melee tool." + force = 13 + armour_penetration = 10 + icon_state = "axe" + inhand_icon_state = "axe" + worn_icon_state = "axe_back" + throwforce = 18 + throw_speed = 4 + embed_type = /datum/embed_data/forged_axe + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("slashes", "bashes") + attack_verb_simple = list("slash", "bash") + sharpness = SHARP_EDGED + +/datum/embed_data/forged_axe + embed_chance = 65 + fall_chance = 10 + pain_mult = 4 + +//Boring option for doing the most raw damage +/obj/item/forging/reagent_weapon/hammer + name = "forged hammer" + desc = "A heavy, weighted hammer that packs an incredible punch but can prove to be unwieldy. Useful for forging!" + force = 24 //Requires wielding + armour_penetration = 10 + icon_state = "crush_hammer" + inhand_icon_state = "crush_hammer" + worn_icon_state = "hammer_back" + throwforce = 10 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("bashes", "whacks") + attack_verb_simple = list("bash", "whack") + tool_behaviour = TOOL_HAMMER + ///the list of things that, if attacked, will set the attack speed to rapid + var/static/list/fast_attacks = list( + /obj/structure/reagent_anvil, + /obj/structure/reagent_crafting_bench + ) + +/obj/item/forging/reagent_weapon/hammer/Initialize(mapload) + . = ..() + AddComponent(/datum/component/two_handed, force_unwielded = 24, force_wielded = 24, require_twohands = TRUE) + AddElement(/datum/element/kneejerk) + +/obj/item/forging/reagent_weapon/hammer/attack_atom(atom/attacked_atom, mob/living/user, params) + . = ..() + if(!is_type_in_list(attacked_atom, fast_attacks)) + return + user.changeNext_move(CLICK_CD_RAPID) + +/obj/item/shield/buckler/reagent_weapon + name = "forged buckler shield" + desc = "A small, round shield best used in tandem with a melee weapon in close-quarters combat." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_items.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_worn.dmi' + icon_state = "buckler" + inhand_icon_state = "buckler" + worn_icon_state = "buckler_back" + lefthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi' + righthand_file = 'modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi' + custom_materials = list(/datum/material/iron=HALF_SHEET_MATERIAL_AMOUNT) + resistance_flags = FIRE_PROOF + block_chance = 30 + transparent = FALSE + max_integrity = 150 //over double that of a wooden one + w_class = WEIGHT_CLASS_NORMAL + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_AFFECT_STATISTICS + obj_flags_doppler = ANVIL_REPAIR + shield_break_sound = 'sound/effects/bang.ogg' + shield_break_leftover = /obj/item/forging/complete/plate + +/obj/item/shield/buckler/reagent_weapon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_weapon) + +/obj/item/shield/buckler/reagent_weapon/examine(mob/user) + . = ..() + . += span_notice("Using a hammer on [src] will repair its damage!") + +/obj/item/shield/buckler/reagent_weapon/attackby(obj/item/attacking_item, mob/user, params) + if(atom_integrity >= max_integrity) + return ..() + if(istype(attacking_item, /obj/item/forging/hammer)) + var/obj/item/forging/hammer/attacking_hammer = attacking_item + var/skill_modifier = user.mind.get_skill_modifier(/datum/skill/smithing, SKILL_SPEED_MODIFIER) * attacking_hammer.toolspeed + while(atom_integrity < max_integrity) + if(!do_after(user, skill_modifier SECONDS, src)) + return + var/fixing_amount = min(max_integrity - atom_integrity, 5) + atom_integrity += fixing_amount + user.mind.adjust_experience(/datum/skill/smithing, 5) //useful heating means you get some experience + balloon_alert(user, "partially repaired!") + return + return ..() + +/obj/item/shield/buckler/reagent_weapon/pavise //similar to the adamantine shield. Huge, slow, lets you soak damage and packs a wallop. + name = "forged pavise shield" + desc = "An oblong shield used by ancient crossbowmen as cover while reloading. Probably just as useful with an actual gun." + icon_state = "pavise" + inhand_icon_state = "pavise" + worn_icon_state = "pavise_back" + block_chance = 75 + item_flags = SLOWS_WHILE_IN_HAND + w_class = WEIGHT_CLASS_HUGE + slot_flags = ITEM_SLOT_BACK + max_integrity = 300 //tanky + +/obj/item/shield/buckler/reagent_weapon/pavise/Initialize(mapload) + . = ..() + AddComponent(/datum/component/two_handed, require_twohands = TRUE, force_wielded = 15) + +/obj/item/pickaxe/reagent_weapon + name = "forged pickaxe" + +/obj/item/pickaxe/reagent_weapon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_weapon) + +/obj/item/shovel/reagent_weapon + name = "forged shovel" + +/obj/item/shovel/reagent_weapon/Initialize(mapload) + . = ..() + AddComponent(/datum/component/reagent_weapon) + +/obj/item/ammo_casing/arrow/attackby(obj/item/attacking_item, mob/user, params) + var/spawned_item + if(istype(attacking_item, /obj/item/stack/sheet/sinew)) + spawned_item = /obj/item/ammo_casing/arrow/ash + + if(istype(attacking_item, /obj/item/stack/sheet/bone)) + spawned_item = /obj/item/ammo_casing/arrow/bone + + if(istype(attacking_item, /obj/item/stack/tile/bronze)) + spawned_item = /obj/item/ammo_casing/arrow/bronze + + if(!spawned_item) + return ..() + + var/obj/item/stack/stack_item = attacking_item + if(!stack_item.use(1)) + return + + var/obj/item/ammo_casing/arrow/converted_arrow = new spawned_item(get_turf(src)) + transfer_fingerprints_to(converted_arrow) + remove_item_from_storage(user) + user.put_in_hands(converted_arrow) + qdel(src) + +#define INCREASE_BLOCK_CHANCE 2 + +/obj/item/forging/reagent_weapon/bokken + name = "bokken" + desc = "A bokken that is capable of blocking attacks when wielding in two hands, possibly including bullets should the user be brave enough." + force = 20 + icon_state = "bokken" + inhand_icon_state = "bokken" + worn_icon_state = "bokken_back" + throwforce = 10 + block_chance = 20 + slot_flags = ITEM_SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + resistance_flags = FIRE_PROOF + attack_verb_continuous = list("bonks", "bashes", "whacks", "pokes", "prods") + attack_verb_simple = list("bonk", "bash", "whack", "poke", "prod") + ///whether the bokken is being wielded or not + var/wielded = FALSE + +/obj/item/forging/reagent_weapon/bokken/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type) + if(wielded) + final_block_chance *= INCREASE_BLOCK_CHANCE + if(prob(final_block_chance)) + if(attack_type == PROJECTILE_ATTACK) + owner.visible_message(span_danger("[owner] deflects [attack_text] with [src]!")) + playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, TRUE) + else + playsound(src, 'sound/weapons/parry.ogg', 75, TRUE) + owner.visible_message(span_danger("[owner] parries [attack_text] with [src]!")) + var/owner_turf = get_turf(owner) + new block_effect(owner_turf, COLOR_YELLOW) + return TRUE + return FALSE + +#undef INCREASE_BLOCK_CHANCE + +/obj/item/forging/reagent_weapon/bokken/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, PROC_REF(on_wield)) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, PROC_REF(on_unwield)) + AddComponent(/datum/component/two_handed, force_unwielded = 20, force_wielded = 10) + +/obj/item/forging/reagent_weapon/bokken/proc/on_wield() + SIGNAL_HANDLER + wielded = TRUE + +/obj/item/forging/reagent_weapon/bokken/proc/on_unwield() + SIGNAL_HANDLER + wielded = FALSE diff --git a/modular_doppler/reagent_forging/code/reagent_component.dm b/modular_doppler/reagent_forging/code/reagent_component.dm new file mode 100644 index 0000000000000..fcffd58d779d4 --- /dev/null +++ b/modular_doppler/reagent_forging/code/reagent_component.dm @@ -0,0 +1,117 @@ +#define MAX_IMBUE_STORAGE 250 +#define REAGENT_CLOTHING_INJECT_AMOUNT 0.5 +#define REAGENT_WEAPON_INJECT_AMOUNT 1 +#define REAGENT_WEAPON_DAMAGE_MULTIPLIER 2 + +//the component that is attached to clothes that allows them to be imbued +//ONLY USE THIS FOR CLOTHING +/datum/component/reagent_clothing + ///the item that the component is attached to + var/obj/item/parent_clothing + ///the slot that the item will check + var/checking_slot + ///the human that is wearing the parent_clothing + var/mob/living/carbon/human/cloth_wearer + ///the container that will apply the chemicals + var/obj/item/reagent_containers/applying_container + ///the list of imbued reagents that will given to the human owner + var/list/imbued_reagent = list() + //the cooldown between each imbue + COOLDOWN_DECLARE(imbue_cooldown) + +/datum/component/reagent_clothing/Initialize(set_slot = null) + if(!istype(parent, /obj/item)) + return COMPONENT_INCOMPATIBLE //they need to be clothing, I already said this + + if(set_slot) + checking_slot = set_slot + + parent_clothing = parent + parent_clothing.create_reagents(MAX_IMBUE_STORAGE, INJECTABLE | REFILLABLE) + applying_container = new /obj/item/reagent_containers(src) + RegisterSignal(parent_clothing, COMSIG_ITEM_EQUIPPED, PROC_REF(set_wearer)) + RegisterSignal(parent_clothing, COMSIG_ITEM_PRE_UNEQUIP, PROC_REF(remove_wearer)) + RegisterSignal(parent_clothing, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + START_PROCESSING(SSdcs, src) + +/datum/component/reagent_clothing/Destroy(force) + parent_clothing = null + cloth_wearer = null + QDEL_NULL(applying_container) + STOP_PROCESSING(SSdcs, src) + return ..() + +/datum/component/reagent_clothing/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("[parent_clothing] is able to be inbued with a chemical at a reagent forge!") + +/datum/component/reagent_clothing/proc/set_wearer() + SIGNAL_HANDLER + + if(!ishuman(parent_clothing.loc)) + return + cloth_wearer = parent_clothing.loc + +/datum/component/reagent_clothing/proc/remove_wearer() + SIGNAL_HANDLER + cloth_wearer = null + +/datum/component/reagent_clothing/process(seconds_per_tick) + if(!parent_clothing || !cloth_wearer || !length(imbued_reagent)) + return + + if(parent_clothing != cloth_wearer.get_item_by_slot(checking_slot)) + return + + if(!COOLDOWN_FINISHED(src, imbue_cooldown)) + return + + COOLDOWN_START(src, imbue_cooldown, 3 SECONDS) + + for(var/create_reagent in imbued_reagent) + applying_container.reagents.add_reagent(create_reagent, REAGENT_CLOTHING_INJECT_AMOUNT) + applying_container.reagents.trans_to(target = cloth_wearer, amount = REAGENT_CLOTHING_INJECT_AMOUNT, methods = INJECT) + +//the component that is attached to weapons that allows them to be imbued +//ONLY USE THIS FOR WEAPONS +/datum/component/reagent_weapon + ///the item that the component is attached to + var/obj/item/parent_weapon + ///the list of imbued reagents that will given to the human owner + var/list/imbued_reagent = list() + +/datum/component/reagent_weapon/Initialize(...) + if(!istype(parent, /obj/item)) + return COMPONENT_INCOMPATIBLE //they need to be weapons, I already said this + parent_weapon = parent + parent_weapon.create_reagents(MAX_IMBUE_STORAGE, INJECTABLE | REFILLABLE) + RegisterSignal(parent_weapon, COMSIG_ITEM_ATTACK, PROC_REF(inject_attacked)) + RegisterSignal(parent_weapon, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + +/datum/component/reagent_weapon/Destroy(force) + parent_weapon = null + return ..() + +/datum/component/reagent_weapon/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("[parent_weapon] is able to be imbued with a chemical at a reagent forge!") + +/datum/component/reagent_weapon/proc/inject_attacked(datum/source, mob/living/target, mob/living/user, params) + SIGNAL_HANDLER + + //don't have the weapon or any imbued reagents? don't try + if(!parent_weapon || !length(imbued_reagent)) + return + + //lets inject that target + var/mob/living_target = target + for(var/create_reagent in imbued_reagent) + living_target.reagents.add_reagent(create_reagent, REAGENT_WEAPON_INJECT_AMOUNT) + + //now lets take damage corresponding to the amount of chems we have imbued (hit either 100 times or 50 times before it breaks) + parent_weapon.take_damage(length(imbued_reagent) * REAGENT_WEAPON_DAMAGE_MULTIPLIER) + +#undef MAX_IMBUE_STORAGE +#undef REAGENT_CLOTHING_INJECT_AMOUNT +#undef REAGENT_WEAPON_INJECT_AMOUNT +#undef REAGENT_WEAPON_DAMAGE_MULTIPLIER diff --git a/modular_doppler/reagent_forging/code/seedmesh.dm b/modular_doppler/reagent_forging/code/seedmesh.dm new file mode 100644 index 0000000000000..f4a01e0582d4c --- /dev/null +++ b/modular_doppler/reagent_forging/code/seedmesh.dm @@ -0,0 +1,36 @@ +/obj/item/seed_mesh + name = "seed mesh" + desc = "A little mesh that, when paired with sand, has the possibility of filtering out large seeds." + icon = 'modular_doppler/reagent_forging/icons/obj/misc_tools.dmi' + icon_state = "mesh" + var/list/static/seeds_blacklist = list( + /obj/item/seeds/lavaland, + /obj/item/seeds/gatfruit, + /obj/item/seeds/seedling/evil, + ) + +/obj/item/seed_mesh/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/ore_item = attacking_item + if(ore_item.points == 0) + user.balloon_alert(user, "[ore_item] is worthless!") + return + + while(ore_item.amount >= 5) + if(!do_after(user, 2 SECONDS, src)) + user.balloon_alert(user, "have to stand still!") + return + + if(!ore_item.use(5)) + user.balloon_alert(user, "unable to use five of [ore_item]!") + return + + if(prob(50)) + user.balloon_alert(user, "[ore_item] reveals nothing!") + continue + + var/spawn_seed = pick(subtypesof(/obj/item/seeds) - seeds_blacklist) + new spawn_seed(get_turf(src)) + user.balloon_alert(user, "[ore_item] revealed something!") + + return ..() diff --git a/modular_doppler/reagent_forging/code/smith_skill.dm b/modular_doppler/reagent_forging/code/smith_skill.dm new file mode 100644 index 0000000000000..248a2275bb915 --- /dev/null +++ b/modular_doppler/reagent_forging/code/smith_skill.dm @@ -0,0 +1,18 @@ +/datum/skill/smithing + name = "Smithing" + title = "Smithy" + desc = "The desperate artist who strives after the flames of the forge." + modifiers = list( + SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5), + SKILL_PROBS_MODIFIER = list(10, 15, 20, 25, 30, 35, 40) + ) + skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/smithing + +/obj/item/clothing/neck/cloak/skill_reward/smithing + name = "legendary smithy's cloak" + desc = "Worn by the most skilled smithies, this legendary cloak is only attainable by knowing every inch of the blacksmith's forge. \ + This status symbol represents a being who has forged some of the finest weapons and armors." + icon = 'modular_doppler/reagent_forging/icons/obj/cloaks.dmi' + worn_icon = 'modular_doppler/reagent_forging/icons/mob/neck.dmi' + icon_state = "smithingcloak" + associated_skill_path = /datum/skill/smithing diff --git a/modular_doppler/reagent_forging/code/tool_override.dm b/modular_doppler/reagent_forging/code/tool_override.dm new file mode 100644 index 0000000000000..4448c27d63721 --- /dev/null +++ b/modular_doppler/reagent_forging/code/tool_override.dm @@ -0,0 +1,31 @@ +/// Called on an object when a tool with wrench capabilities is used to left click an object +/atom/proc/billow_act(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to right click an object +/atom/proc/billow_act_secondary(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to left click an object +/atom/proc/tong_act(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to right click an object +/atom/proc/tong_act_secondary(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to left click an object +/atom/proc/hammer_act(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to right click an object +/atom/proc/hammer_act_secondary(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to left click an object +/atom/proc/blowrod_act(mob/living/user, obj/item/tool) + return + +/// Called on an object when a tool with wrench capabilities is used to right click an object +/atom/proc/blowrod_act_secondary(mob/living/user, obj/item/tool) + return diff --git a/modular_doppler/reagent_forging/code/water_basin.dm b/modular_doppler/reagent_forging/code/water_basin.dm new file mode 100644 index 0000000000000..049bc3a9719ce --- /dev/null +++ b/modular_doppler/reagent_forging/code/water_basin.dm @@ -0,0 +1,110 @@ +/obj/structure/reagent_water_basin + name = "water basin" + desc = "A basin full of water, ready to quench the hot metal." + icon = 'modular_doppler/reagent_forging/icons/obj/forge_structures.dmi' + icon_state = "water_basin" + anchored = TRUE + density = TRUE + + /// Tracks if you can fish from this basin + var/datum/component/fishing_spot/fishable + +/obj/structure/reagent_water_basin/Initialize(mapload) + . = ..() + +/obj/structure/reagent_water_basin/Destroy() + QDEL_NULL(fishable) + return ..() + +/obj/structure/reagent_water_basin/examine(mob/user) + . = ..() + if(!fishable) + . += span_notice("[src] can be upgraded through a bluespace crystal or a journeyman smithy!") + + else + . += span_notice("[src] looks to be a bottomless basin of water... You can even see fish swimming around down there!") + +/obj/structure/reagent_water_basin/attack_hand(mob/living/user, list/modifiers) + . = ..() + var/smithing_skill = user.mind.get_skill_level(/datum/skill/smithing) + if(smithing_skill < SKILL_LEVEL_JOURNEYMAN || fishable) + return + + balloon_alert(user, "the water deepens!") + fishable = AddComponent(/datum/component/fishing_spot, /datum/fish_source/water_basin) + +/obj/structure/reagent_water_basin/attackby(obj/item/attacking_item, mob/living/user, params) + if(istype(attacking_item, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/glass_obj = attacking_item + if(!glass_obj.use(1)) + return + + new /obj/item/stack/clay(get_turf(src)) + user.mind.adjust_experience(/datum/skill/production, 1) + return + + if(istype(attacking_item, /obj/item/stack/ore/bluespace_crystal)) + if(fishable) + return + var/obj/item/stack/ore/bluespace_crystal/bs_crystal = attacking_item + + if(!bs_crystal.use(1)) + return + + balloon_alert(user, "the water deepens!") + fishable = AddComponent(/datum/component/fishing_spot, /datum/fish_source/water_basin) + return + + return ..() + +/obj/structure/reagent_water_basin/wrench_act(mob/living/user, obj/item/tool) + user.balloon_alert_to_viewers("disassembling...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 100)) + return + + deconstruct(disassembled = TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/reagent_water_basin/atom_deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/mineral/wood(drop_location(), 5) + +/obj/structure/reagent_water_basin/tong_act(mob/living/user, obj/item/tool) + var/obj/item/forging/incomplete/search_incomplete = locate(/obj/item/forging/incomplete) in tool.contents + if(!search_incomplete) + return ITEM_INTERACT_SUCCESS + + playsound(src, 'modular_doppler/reagent_forging/sound/hot_hiss.ogg', 50, TRUE) + + if(search_incomplete?.times_hit < search_incomplete.average_hits) + to_chat(user, span_warning("You cool down [search_incomplete], but it wasn't ready yet.")) + COOLDOWN_RESET(search_incomplete, heating_remainder) + return ITEM_INTERACT_SUCCESS + + if(search_incomplete?.times_hit >= search_incomplete.average_hits) + to_chat(user, span_notice("You cool down [search_incomplete] and it's ready.")) + user.mind.adjust_experience(/datum/skill/smithing, 10) //using the water basin on a ready item gives decent experience. + + var/obj/spawned_obj = new search_incomplete.spawn_item(get_turf(src)) + if(search_incomplete.custom_materials) + spawned_obj.set_custom_materials(search_incomplete.custom_materials, 1) //lets set its material + + qdel(search_incomplete) + tool.icon_state = "tong_empty" + return ITEM_INTERACT_SUCCESS + +/// Fishing source for fishing out of basins that have been upgraded, contains saltwater fish (lizard fish fall under this too!) +/datum/fish_source/water_basin + catalog_description = "Bottomless Water Basins" + fish_table = list( + /obj/item/fish/clownfish = 15, + /obj/item/fish/pufferfish = 10, + /obj/item/fish/cardinal = 15, + /obj/item/fish/greenchromis = 15, + /obj/item/fish/lanternfish = 5, + /obj/item/fish/dwarf_moonfish = 15, + /obj/item/fish/gunner_jellyfish = 15, + /obj/item/fish/needlefish = 10, + /obj/item/fish/armorfish = 10, + /obj/effect/spawner/random/maintenance = 10, + /obj/effect/spawner/random/trash/garbage = 15, + ) diff --git a/modular_doppler/reagent_forging/icons/hud/forge_radials.dmi b/modular_doppler/reagent_forging/icons/hud/forge_radials.dmi new file mode 100644 index 0000000000000..4beb99bcd449c Binary files /dev/null and b/modular_doppler/reagent_forging/icons/hud/forge_radials.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi new file mode 100644 index 0000000000000..6b74fa160261b Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_digi.dmi b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_digi.dmi new file mode 100644 index 0000000000000..b132966dec7ac Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_digi.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi new file mode 100644 index 0000000000000..5ae74329a15f9 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_newvox.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi new file mode 100644 index 0000000000000..4c606e229eaac Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_oldvox.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi new file mode 100644 index 0000000000000..3c7d86b79fddc Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/clothing/forge_clothing_teshari.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi b/modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi new file mode 100644 index 0000000000000..5767aaef5e262 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/forge_weapon_l.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi b/modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi new file mode 100644 index 0000000000000..8f51de1a9e9bf Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/forge_weapon_r.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/forge_weapon_worn.dmi b/modular_doppler/reagent_forging/icons/mob/forge_weapon_worn.dmi new file mode 100644 index 0000000000000..532834d2336be Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/forge_weapon_worn.dmi differ diff --git a/modular_doppler/reagent_forging/icons/mob/neck.dmi b/modular_doppler/reagent_forging/icons/mob/neck.dmi new file mode 100644 index 0000000000000..4710c457349e7 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/mob/neck.dmi differ diff --git a/modular_doppler/reagent_forging/icons/obj/cloaks.dmi b/modular_doppler/reagent_forging/icons/obj/cloaks.dmi new file mode 100644 index 0000000000000..0a56972bd76d7 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/obj/cloaks.dmi differ diff --git a/modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi b/modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi new file mode 100644 index 0000000000000..076a0a70b06c5 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/obj/forge_clothing.dmi differ diff --git a/modular_doppler/reagent_forging/icons/obj/forge_items.dmi b/modular_doppler/reagent_forging/icons/obj/forge_items.dmi new file mode 100644 index 0000000000000..c333e753d6594 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/obj/forge_items.dmi differ diff --git a/modular_doppler/reagent_forging/icons/obj/forge_structures.dmi b/modular_doppler/reagent_forging/icons/obj/forge_structures.dmi new file mode 100644 index 0000000000000..c5d8e84977983 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/obj/forge_structures.dmi differ diff --git a/modular_doppler/reagent_forging/icons/obj/misc_tools.dmi b/modular_doppler/reagent_forging/icons/obj/misc_tools.dmi new file mode 100644 index 0000000000000..21d7b4c967f23 Binary files /dev/null and b/modular_doppler/reagent_forging/icons/obj/misc_tools.dmi differ diff --git a/modular_doppler/reagent_forging/readme.md b/modular_doppler/reagent_forging/readme.md new file mode 100644 index 0000000000000..f5cdcd6b42da9 --- /dev/null +++ b/modular_doppler/reagent_forging/readme.md @@ -0,0 +1,31 @@ +## Title: Reagent Forging + +MODULE ID: REAGENT_FORGING + +### Description: + +Reagent forging is a form of forging where users are able to create various items that have the ability to become imbued with reagents. Items that have been imbued have the ability to permanently inject imbued reagents into the targets. + +### TG Proc Changes: + +| proc | file | +| --------------------------------------------------------------------- | ----------------------------------- | +| `/atom/proc/tool_act(mob/living/user, obj/item/tool, list/modifiers)` | `code\game\atom\atom_tool_acts.dm` | + +### Defines: + +- `code\__DEFINES\~doppler_defines\reagent_forging_tools.dm` reagent forging tools' define + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `code\__DEFINES\~doppler_defines\obj_flags_doppler.dm` anvil_repair define +- `code\_globalvars\~doppler_globalvars\bitfields.dm` anvil_repair bitfield +- `modular_doppler\modular_crafting\code\sheet_types.dm` contains some of the recipes + +### Credits: + +Jake Park diff --git a/modular_doppler/reagent_forging/sound/forge.ogg b/modular_doppler/reagent_forging/sound/forge.ogg new file mode 100644 index 0000000000000..8ade5b6485d0c Binary files /dev/null and b/modular_doppler/reagent_forging/sound/forge.ogg differ diff --git a/modular_doppler/reagent_forging/sound/hot_hiss.ogg b/modular_doppler/reagent_forging/sound/hot_hiss.ogg new file mode 100644 index 0000000000000..36f312b210f0c Binary files /dev/null and b/modular_doppler/reagent_forging/sound/hot_hiss.ogg differ diff --git a/modular_doppler/religion/code/chaplain.dm b/modular_doppler/religion/code/chaplain.dm new file mode 100644 index 0000000000000..85351440744c5 --- /dev/null +++ b/modular_doppler/religion/code/chaplain.dm @@ -0,0 +1,21 @@ +/datum/job/chaplain/after_spawn(mob/living/spawned, client/player_client) + . = ..() + var/mob/living/carbon/human/spawned_chaplain = spawned + + // no mind? get out of here, this shouldn't be happening + if(!spawned_chaplain.mind) + return + + // if the mob is not a high priest, add to successors list + if(spawned_chaplain.mind.holy_role != HOLY_ROLE_HIGHPRIEST) + if(isnull(GLOB.holy_successors)) + GLOB.holy_successors = list() + GLOB.holy_successors |= WEAKREF(spawned_chaplain) + return + + // if the mob joins as a high priest (and there has been a previous high priest before them), make sure they get their own nullrod. + if(isnull(GLOB.current_highpriest)) + spawned_chaplain.put_in_hands(new /obj/item/nullrod(spawned_chaplain)) + + // keep a record of the current high priest + GLOB.current_highpriest = WEAKREF(spawned_chaplain) diff --git a/modular_doppler/religion/code/mind.dm b/modular_doppler/religion/code/mind.dm new file mode 100644 index 0000000000000..892f2a491ed89 --- /dev/null +++ b/modular_doppler/religion/code/mind.dm @@ -0,0 +1,11 @@ +// Free chaplain highpriest role if the chaplain's mind gets deleted for some reason +/datum/mind/Destroy() + var/list/holy_successors = list_holy_successors() + if(current && (current in holy_successors)) // if this mob was a holy successor then remove them from the pool + GLOB.holy_successors -= WEAKREF(src) + + // Handle freeing the high priest role for the next chaplain in line + if(holy_role == HOLY_ROLE_HIGHPRIEST) + reset_religion() + + return ..() diff --git a/modular_doppler/religion/code/religious_sects.dm b/modular_doppler/religion/code/religious_sects.dm new file mode 100644 index 0000000000000..1a465d8421ed1 --- /dev/null +++ b/modular_doppler/religion/code/religious_sects.dm @@ -0,0 +1,86 @@ +// Allowing religion to be reselected by new chaplains if something happened to the old one (cryo or being deleted) + +/datum/religion_sect/on_select() + . = ..() + + // if the same religious sect gets selected, carry the favor over + if(istype(src, GLOB.prev_sect_type)) + set_favor(GLOB.prev_favor) + +/// It's time to kill GLOB +/** + * Reset religion to its default state so the new chaplain becomes high priest and can change the sect, armor, weapon type, etc + * Also handles the selection of a holy successor from existing crew if multiple chaplains are on station. + */ +/proc/reset_religion() + + // try to pick the successor from existing crew, or leave it empty if no valid candidates found + var/mob/living/carbon/human/chosen_successor = pick_holy_successor() + GLOB.current_highpriest = chosen_successor ? WEAKREF(chosen_successor) : null // if a successor is already on the station then pick the first in line + + if(isnull(GLOB.religious_sect)) // sect already been reset, maybe a chaplain cryo'd before choosing a sect + return + + // remember what the previous sect and favor values were so they can be restored if the same one gets chosen + GLOB.prev_favor = GLOB.religious_sect.favor + GLOB.prev_sect_type = GLOB.religious_sect.type + + // set the altar references to the old religious_sect to null + SEND_GLOBAL_SIGNAL(COMSIG_RELIGIOUS_SECT_RESET) + + QDEL_NULL(GLOB.religious_sect) // queue for removal but also set it to null, in case a new chaplain joins before it can be deleted + + // set the rest of the global vars to null for the new chaplain + GLOB.religion = null + GLOB.deity = null + GLOB.bible_name = null + GLOB.bible_icon_state = null + GLOB.bible_inhand_icon_state = null + GLOB.holy_armor_type = null + GLOB.holy_weapon_type = null + +/** + * Chooses a valid holy successor from GLOB.holy_successor weakref list and sets things up for them to be the new high priest + * + * Returns the chosen holy successor, or null if no valid successor + */ +/proc/pick_holy_successor() + for(var/datum/weakref/successor as anything in GLOB.holy_successors) + var/mob/living/carbon/human/actual_successor = successor.resolve() + if(!actual_successor) + GLOB.holy_successors -= successor + continue + + if(!actual_successor.key || !actual_successor.mind) + continue + + // we have a match! set the religious globals up properly and make the candidate high priest + GLOB.holy_successors -= successor + GLOB.religion = actual_successor.client?.prefs?.read_preference(/datum/preference/name/religion) || DEFAULT_RELIGION + GLOB.bible_name = actual_successor.client?.prefs?.read_preference(/datum/preference/name/deity) || DEFAULT_DEITY + GLOB.deity = actual_successor.client?.prefs?.read_preference(/datum/preference/name/bible) || DEFAULT_BIBLE + + actual_successor.mind.holy_role = HOLY_ROLE_HIGHPRIEST + + to_chat(actual_successor, span_warning("You have been chosen as the successor to the previous high priest. Visit a holy altar to declare the station's religion!")) + + return actual_successor + + return null + +/** + * Create a list of the holy successors mobs from GLOB.holy_successors weakref list + * + * Returns the list of valid holy successors + */ +/proc/list_holy_successors() + var/list/holy_successors = list() + for(var/datum/weakref/successor as anything in GLOB.holy_successors) + var/mob/living/carbon/human/actual_successor = successor.resolve() + if(!actual_successor) + GLOB.holy_successors -= successor + continue + + holy_successors += actual_successor + + return holy_successors diff --git a/modular_doppler/religion/readme.md b/modular_doppler/religion/readme.md new file mode 100644 index 0000000000000..bc65711c9bb7e --- /dev/null +++ b/modular_doppler/religion/readme.md @@ -0,0 +1,25 @@ +## Title: Religion + +MODULE ID: RELIGION + +### Description: + +Allows religion to be reselected by new chaplains if something happened to the old one (cryo or being deleted) + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: diff --git a/modular_doppler/stone/code/ore_veins.dm b/modular_doppler/stone/code/ore_veins.dm new file mode 100644 index 0000000000000..5fbf37618292c --- /dev/null +++ b/modular_doppler/stone/code/ore_veins.dm @@ -0,0 +1,116 @@ +/obj/structure/ore_vein + name = "ore vein" + desc = "An ore vein that can mined." + icon = 'modular_doppler/stone/icons/ore.dmi' + icon_state = "stone1" + base_icon_state = "stone" + density = TRUE + anchored = TRUE + /// When we start mining, what do we tell the user they're mining? + var/ore_descriptor = "stone" + /// What type of ore do we drop? + var/ore_type = /obj/item/stack/stone + /// How much ore do we drop? + var/ore_amount = 5 + /// If the ore vein has been recently mined. If so, we cannot mine and must wait for it to regenerate. + var/depleted = FALSE + /// How long it takes for the ore to 'respawn' after being mined. + var/regeneration_time = 3 MINUTES + /// How long it takes for a tool to mine the ore vein. + var/mining_time = 10 SECONDS + /// How many unique sprites for ore we have, we will pick them at random. + var/unique_sprites = 3 + /// If we should pick a random sprite for the ore vein or not. + var/random_sprite = TRUE + /// Our original description to hold. We'll revert to this when switching between the ore vein being depleted and not. + var/base_desc = "" + +/obj/structure/ore_vein/Initialize(mapload) + . = ..() + base_desc = desc + if(random_sprite) + base_icon_state += "[rand(1, (unique_sprites))]" + + icon_state = base_icon_state + +/obj/structure/ore_vein/update_icon_state() + icon_state = "[base_icon_state][depleted ? "_depleted" : ""]" + + return ..() + +/obj/structure/ore_vein/examine() + . = ..() + . += "[depleted ? "The ore vein is exhausted." : ""]" + +/obj/structure/ore_vein/attackby(obj/item/W, mob/user, params) + if(W.tool_behaviour != TOOL_MINING) + to_chat(user, span_notice("You need a pickaxe to mine this.")) + return FALSE + if(!ore_type) + to_chat(user, span_notice("There's no ore to mine!")) + return FALSE + if(!ore_amount) + to_chat(user, span_notice("The [src] is too low quality to yield any useful amount of [ore_descriptor].")) + return FALSE + if(depleted == TRUE) + to_chat(user, span_notice("This ore vein is exhausted.")) + return FALSE + // Our early return checks to tell the user what went wrong. + to_chat(user, span_notice("You start mining the [ore_descriptor]...")) + if(W.use_tool(src, user, src.mining_time, volume=50)) + to_chat(user, span_notice("You mine the [ore_descriptor].")) + if(ore_type && ore_amount && depleted == FALSE) + new ore_type(loc, ore_amount) + SSblackbox.record_feedback("tally", "pick_used_mining", 1, W.type) + depleted = TRUE + update_icon_state() + addtimer(CALLBACK(src, PROC_REF(regenerate_ore)), regeneration_time) + +/// After the ore vein finishes its wait, we make the ore 'respawn' and return the ore to its original post-Initialize() icon_state. +/obj/structure/ore_vein/proc/regenerate_ore() + depleted = FALSE + update_icon_state() + +/obj/structure/ore_vein/stone + name = "large rocks" + desc = "Various types of high quality stone that could probably make a good construction material if dug up and refined." + +/obj/structure/ore_vein/iron + name = "rusted rocks" + desc = "The rusty brown color on these rocks gives away the fact they are full of iron!" + icon_state = "iron1" + base_icon_state = "iron" + ore_descriptor = "iron" + ore_type = /obj/item/stack/ore/iron + +/obj/structure/ore_vein/silver + name = "silvery-blue rocks" + desc = "These rocks have the giveaway blued-silver look of, well, raw silver." + icon_state = "silver1" + base_icon_state = "silver" + ore_descriptor = "silver" + ore_type = /obj/item/stack/ore/silver + +/obj/structure/ore_vein/gold + name = "gold streaked rocks" + desc = "Fairly normal looking rocks... aside from the streaks of shining gold running through some of them!." + icon_state = "gold1" + base_icon_state = "gold" + ore_descriptor = "gold" + ore_type = /obj/item/stack/ore/gold + +/obj/structure/ore_vein/plasma + name = "plasma rich rocks" + desc = "Rocks with unrefined plasma visible on the outside of several... Do be careful with open flames near this." + icon_state = "plasma1" + base_icon_state = "plasma" + ore_descriptor = "plasma" + ore_type = /obj/item/stack/ore/plasma + +/obj/structure/ore_vein/diamond + name = "diamond studded rocks" + desc = "While nowhere near as rare as you'd think, the diamonds studding these rocks are still both useful and valuable." + icon_state = "diamond1" + base_icon_state = "diamond" + ore_descriptor = "diamond" + ore_type = /obj/item/stack/ore/diamond diff --git a/modular_doppler/stone/code/stone.dm b/modular_doppler/stone/code/stone.dm new file mode 100644 index 0000000000000..1cac695222c94 --- /dev/null +++ b/modular_doppler/stone/code/stone.dm @@ -0,0 +1,152 @@ +/obj/item/stack/sheet/mineral/stone + name = "stone" + desc = "Stone brick." + singular_name = "stone block" + icon = 'modular_doppler/stone/icons/ore.dmi' + icon_state = "sheet-stone" + inhand_icon_state = "sheet-metal" + mats_per_unit = list(/datum/material/stone=SHEET_MATERIAL_AMOUNT) + force = 10 + throwforce = 15 + resistance_flags = FIRE_PROOF + merge_type = /obj/item/stack/sheet/mineral/stone + grind_results = null + material_type = /datum/material/stone + matter_amount = 0 + source = null + walltype = /turf/closed/wall/mineral/stone + stairs_type = /obj/structure/stairs/stone + drop_sound = SFX_BRICK_DROP + pickup_sound = SFX_BRICK_PICKUP + +GLOBAL_LIST_INIT(stone_recipes, list ( \ + new/datum/stack_recipe("stone brick wall", /turf/closed/wall/mineral/stone, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("stone brick tile", /obj/item/stack/tile/mineral/stone, 1, 4, 20, category = CAT_TILES), + new/datum/stack_recipe("millstone", /obj/structure/millstone, 6, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("stone cauldron", /obj/machinery/cauldron, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("stone stove", /obj/machinery/primitive_stove, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("stone oven", /obj/machinery/oven/stone, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + new/datum/stack_recipe("stone griddle", /obj/machinery/griddle/stone, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), + )) + +/obj/item/stack/sheet/mineral/stone/get_main_recipes() + . = ..() + . += GLOB.stone_recipes + +/datum/material/stone + name = "stone" + desc = "It's stone." + categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE) + sheet_type = /obj/item/stack/sheet/mineral/stone + value_per_unit = 0.005 + beauty_modifier = 0.01 + color = "#59595a" + greyscale_colors = "#59595a" + value_per_unit = 0.0025 + armor_modifiers = list(MELEE = 0.75, BULLET = 0.5, LASER = 1.25, ENERGY = 0.5, BOMB = 0.5, BIO = 0.25, FIRE = 1.5, ACID = 1.5) + beauty_modifier = 0.3 + turf_sound_override = FOOTSTEP_PLATING + +/obj/item/stack/stone + name = "rough stone" + desc = "Large chunks of uncut stone, tough enough to safely build out of... if you could manage to cut them into something usable." + icon = 'modular_doppler/stone/icons/ore.dmi' + icon_state = "stone_ore" + singular_name = "rough stone boulder" + mats_per_unit = list(/datum/material/stone = SHEET_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/stone + force = 10 + throwforce = 15 + +/obj/item/stack/stone/examine() + . = ..() + . += span_notice("With a chisel or even a pickaxe of some kind, you could cut this into blocks.") + +/obj/item/stack/stone/attackby(obj/item/attacking_item, mob/user, params) + if((attacking_item.tool_behaviour != TOOL_MINING) && !(istype(attacking_item, /obj/item/chisel))) + return ..() + playsound(src, 'sound/effects/picaxe1.ogg', 50, TRUE) + balloon_alert_to_viewers("cutting...") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert_to_viewers("stopped cutting") + return FALSE + new /obj/item/stack/sheet/mineral/stone(get_turf(src), amount) + qdel(src) + +/obj/item/stack/tile/mineral/stone + name = "stone tile" + singular_name = "stone floor tile" + desc = "A tile made of stone bricks, for that fortress look." + icon_state = "tile_herringbone" + inhand_icon_state = "tile" + turf_type = /turf/open/floor/stone + mineralType = "stone" + mats_per_unit = list(/datum/material/stone= HALF_SHEET_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/tile/mineral/stone + +/turf/open/floor/stone + desc = "Blocks of stone arranged in a tile-like pattern, odd, really, how it looks like real stone too, because it is!" //A play on the original description for stone tiles + +/turf/closed/wall/mineral/stone + name = "stone wall" + desc = "A wall made of solid stone bricks." + icon = 'modular_doppler/stone/icons/wall.dmi' + icon_state = "wall-0" + base_icon_state = "wall" + sheet_type = /obj/item/stack/sheet/mineral/stone + explosive_resistance = 2 // Rock and stone to the bone, or at least a bit longer than walls made of metal sheets! + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_STONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_STONE_WALLS + custom_materials = list( + /datum/material/stone = SHEET_MATERIAL_AMOUNT * 2, + ) + +/turf/closed/wall/mineral/stone/try_decon(obj/item/item_used, mob/user) // Lets you break down stone walls with stone breaking tools + if(item_used.tool_behaviour != TOOL_MINING) + return ..() + + if(!item_used.tool_start_check(user, amount = 0)) + return FALSE + + balloon_alert_to_viewers("breaking down...") + + if(!item_used.use_tool(src, user, 5 SECONDS)) + return FALSE + dismantle_wall() + return TRUE + +/turf/closed/indestructible/stone + name = "stone wall" + desc = "A wall made of unusually solid stone bricks." + icon = 'modular_doppler/stone/icons/wall.dmi' + icon_state = "wall-0" + base_icon_state = "wall" + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_STONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_STONE_WALLS + custom_materials = list( + /datum/material/stone = SHEET_MATERIAL_AMOUNT * 2, + ) + +/obj/structure/falsewall/stone + name = "stone wall" + desc = "A wall made of solid stone bricks." + icon = 'modular_doppler/stone/icons/wall.dmi' + icon_state = "wall-open" + base_icon_state = "wall" + fake_icon = 'modular_doppler/stone/icons/wall.dmi' + mineral = /obj/item/stack/sheet/mineral/stone + walltype = /turf/closed/wall/mineral/stone + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_STONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_STONE_WALLS + +/turf/closed/mineral/gets_drilled(mob/user, give_exp = FALSE) + if(prob(5)) + new /obj/item/stack/stone(src) + + return ..() + +/obj/item/stack/sheet/mineral/stone/fifty + amount = 50 diff --git a/modular_doppler/stone/icons/ore.dmi b/modular_doppler/stone/icons/ore.dmi new file mode 100644 index 0000000000000..398c03567fb0c Binary files /dev/null and b/modular_doppler/stone/icons/ore.dmi differ diff --git a/modular_doppler/stone/icons/wall.dmi b/modular_doppler/stone/icons/wall.dmi new file mode 100644 index 0000000000000..1384db4a89a00 Binary files /dev/null and b/modular_doppler/stone/icons/wall.dmi differ diff --git a/modular_doppler/stone/readme.md b/modular_doppler/stone/readme.md new file mode 100644 index 0000000000000..033a152106fac --- /dev/null +++ b/modular_doppler/stone/readme.md @@ -0,0 +1,25 @@ +## Title: Stone and Ore Veins + +MODULE ID: STONE + +### Description: + +Adds stone and stone recipies, as well as ore veins. + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +- `code\__DEFINES\icon_smoothing.dm` added the smoothing group `SMOOTH_GROUP_STONE_WALLS`used in `/turf/closed/wall/mineral/stone` + +### Credits: diff --git a/modular_doppler/vending_machines/code/vendor_containers.dm b/modular_doppler/vending_machines/code/vendor_containers.dm new file mode 100644 index 0000000000000..7feb74dfbcfd6 --- /dev/null +++ b/modular_doppler/vending_machines/code/vendor_containers.dm @@ -0,0 +1,89 @@ +/obj/item/storage/box/foodpack + name = "wrapped meal container" + desc = "A generic brown paper food package, you aren't quite sure where this comes from." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodpack_generic_big" + illustration = null + custom_price = PAYCHECK_CREW * 1.8 + ///What the main course of this package is + var/main_course = /obj/item/trash/empty_food_tray + ///What the side of this package should be + var/side_item = /obj/item/food/vendor_snacks + ///What kind of condiment pack should we give the package + var/condiment_pack = /obj/item/reagent_containers/condiment/pack/ketchup + +/obj/item/storage/box/foodpack/PopulateContents() + . = ..() + new main_course(src) + new side_item(src) + new condiment_pack(src) + +/obj/item/storage/box/foodpack/nt + name = "\improper NT-Combo Meal - Salisbury Steak" + desc = "A relatively bland package made of reflective metal foil, it has a blue sprite and the letters 'NT' printed on the top." + icon_state = "foodpack_nt_big" + main_course = /obj/item/food/vendor_tray_meal + side_item = /obj/effect/spawner/random/vendor_meal_sides/nt + condiment_pack = /obj/item/reagent_containers/condiment/pack/ketchup + +/obj/item/storage/box/foodpack/nt/burger + name = "\improper NT-Combo Meal - Cheeseburger" + main_course = /obj/item/food/vendor_tray_meal/burger + +/obj/item/storage/box/foodpack/nt/chicken_sammy + name = "\improper NT-Combo Meal - Spicy Chicken Sandwich" + main_course = /obj/item/food/vendor_tray_meal/chicken_sandwich + +/obj/item/storage/box/foodpack/yangyu + name = "\improper Atatakai shokuji - Homestyle Noodles" + desc = "A well decorated red and white plastic package, covered in nearly incomprehensible yangyu text." + icon_state = "foodpack_yangyu_big" + main_course = /obj/item/food/vendor_tray_meal/ramen + side_item = /obj/effect/spawner/random/vendor_meal_sides/yangyu + condiment_pack = /obj/item/reagent_containers/condiment/pack/hotsauce + +/obj/item/storage/box/foodpack/yangyu/sushi + name = "\improper Atatakai shokuji - Carp Sushi Rolls" + main_course = /obj/item/food/vendor_tray_meal/sushi + +/obj/item/storage/box/foodpack/yangyu/beef_rice + name = "\improper Atatakai shokuji - Beef and Rice" + main_course = /obj/item/food/vendor_tray_meal/beef_rice + +/obj/item/storage/box/foodpack/moth + name = "\improper Ration Type M - Pesto Pizza" + desc = "A cardboard-colored paper package with the symbol of the nomad fleet stamped upon it." + icon_state = "foodpack_moth_big" + main_course = /obj/item/food/vendor_tray_meal/pesto_pizza + side_item = /obj/effect/spawner/random/vendor_meal_sides/moth + condiment_pack = /obj/item/reagent_containers/condiment/pack/astrotame + +/obj/item/storage/box/foodpack/moth/baked_rice + name = "\improper Ration Type M - Baked Rice and Grilled Cheese" + main_course = /obj/item/food/vendor_tray_meal/baked_rice + +/obj/item/storage/box/foodpack/moth/fuel_jack + name = "\improper Ration Type M - Fueljack's Feast" + main_course = /obj/item/food/vendor_tray_meal/fueljack + +/obj/item/storage/box/foodpack/tizira + name = "\improper Tizira Imports Pack - Moonfish Nizaya" + desc = "A dull, metal foil package with the colors of the Tiziran flag striped across it, as well as a stamp of legitimate origin from the Tiziran exports office." + icon_state = "foodpack_tizira_big" + main_course = /obj/item/food/vendor_tray_meal/moonfish_nizaya + side_item = /obj/effect/spawner/random/vendor_meal_sides/tizira + condiment_pack = /obj/item/reagent_containers/condiment/pack/bbqsauce + custom_price = PAYCHECK_CREW * 2 //Tiziran imports are a bit more expensive + +/obj/item/storage/box/foodpack/tizira/examine_more(mob/user) + . = ..() + . += span_notice("Now that you look at it, the origin stamp appears to be a poor imitation of the real thing!") + return . + +/obj/item/storage/box/foodpack/tizira/roll + name = "\improper Tizira Imports Pack - Emperor Roll" + main_course = /obj/item/food/vendor_tray_meal/emperor_roll + +/obj/item/storage/box/foodpack/tizira/stir_fry + name = "\improper Tizira Imports Pack - Mushroom Stirfry" + main_course = /obj/item/food/vendor_tray_meal/mushroom_fry diff --git a/modular_doppler/vending_machines/code/vendor_food.dm b/modular_doppler/vending_machines/code/vendor_food.dm new file mode 100644 index 0000000000000..2e036b9ec31a2 --- /dev/null +++ b/modular_doppler/vending_machines/code/vendor_food.dm @@ -0,0 +1,318 @@ +// Packaged whole meals and sides for the 'meals' tab of vendors + +/* TRASH */ + +/obj/item/trash/empty_food_tray + name = "empty plastic food tray" + desc = "The condensation and what you can only hope are the leftovers of food make this a bit hard to reuse." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodtray_empty" + custom_materials = list( + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + ) + +/obj/item/trash/empty_side_pack + name = "empty side wrapper" + desc = "Unfortunately, this no longer holds any sides to distract you from the other 'food'." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodpack_generic_trash" + custom_materials = list( + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + ) + +/obj/item/trash/empty_side_pack/nt + icon_state = "foodpack_nt_trash" + +/obj/item/trash/empty_side_pack/yangyu + icon_state = "foodpack_yangyu_trash" + +/obj/item/trash/empty_side_pack/moth + icon_state = "foodpack_moth_trash" + +/obj/item/trash/empty_side_pack/tizira + icon_state = "foodpack_tizira_trash" + +/* MEALS */ + +/* +* NT Meals +*/ + +/obj/item/food/vendor_tray_meal + name = "\improper NT-Meal: Steak and Macaroni" + desc = "A 'salisbury steak' drowning in something similar to a gravy, with a macaroni and cheese substitute mix sitting right beside it." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodtray_sad_steak" + trash_type = /obj/item/trash/empty_food_tray + food_reagents = list(/datum/reagent/consumable/nutriment = 8) + tastes = list("meat?" = 2, "cheese?" = 2, "laziness" = 1) + foodtypes = MEAT | GRAIN | DAIRY + food_flags = FOOD_FINGER_FOOD + w_class = WEIGHT_CLASS_SMALL + ///Does this food have the steam effect on it when initialized + var/hot_and_steamy = TRUE + +/obj/item/food/vendor_tray_meal/Initialize(mapload) + . = ..() + if(hot_and_steamy) + overlays += mutable_appearance('icons/effects/steam.dmi', "steam_triple", ABOVE_OBJ_LAYER) + +/obj/item/food/vendor_tray_meal/examine_more(mob/user) + . = ..() + . += span_notice("You browse the back of the box...") + . += "\t[span_warning("Warning: Packaged in a factory where every allergen known is present.")]" + . += "\t[span_warning("Warning: Contents might be hot.")]" + . += "\t[span_info("Per 200g serving contains: 8g Sodium; 25g Fat, of which 22g are saturated; 2g Sugar.")]" + return . + +/obj/item/food/vendor_tray_meal/burger + name = "\improper NT-Meal: Cheeseburger" + desc = "A pretty sad looking burger with a kinda soggy bottom bun and highlighter yellow cheese." + icon_state = "foodtray_burg" + tastes = list("bread" = 2, "meat?" = 2, "cheese?" = 2, "laziness" = 1) + foodtypes = MEAT | GRAIN | DAIRY + +/obj/item/food/vendor_tray_meal/chicken_sandwich + name = "\improper NT-Meal: Spicy Chicken Sandwich" + desc = "A pretty sad looking chicken sandwich, the 'meat' patty is covered in so many manufactured spices that it has become an eerie red color." + icon_state = "foodtray_chickie_sandwich" + food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/capsaicin = 10) + tastes = list("bread" = 2, "chicken?" = 2, "overwhelming spice" = 2, "laziness" = 1) + foodtypes = MEAT | GRAIN | DAIRY + +/* +* Yangyu Meals +*/ + +/obj/item/food/vendor_tray_meal/ramen + name = "\improper Meinkosu: Homestyle Noodles" + desc = "A brick of the finest factory made ramen, with a small amount of rehydrated vegetables and herbs floating around." + icon_state = "foodtray_noodle" + tastes = list("cheap noodles" = 2, "laziness" = 1) + foodtypes = GRAIN | VEGETABLES + +/obj/item/food/vendor_tray_meal/sushi + name = "\improper Meinkosu: Fresh Carp Rolls" + desc = "A pair of sushi rolls, the appearance of which would suggest that the label is lying to you." + icon_state = "foodtray_gas_station_sushi" + tastes = list("imitation space carp" = 2, "stale rice" = 2, "laziness" = 1) + foodtypes = GRAIN | SEAFOOD + +/obj/item/food/vendor_tray_meal/beef_rice + name = "\improper Meinkosu: Beef and Fried Rice" + desc = "A few slices of seemingly grilled beef, paired with a disproportionately large amount of rice." + icon_state = "foodtray_beef_n_rice" + tastes = list("cheap beef" = 1, "rice" = 3, "laziness" = 1) + foodtypes = GRAIN | MEAT + +/* +* Mothic Meals +*/ + +/obj/item/food/vendor_tray_meal/pesto_pizza + name = "\improper Main Course - Type M: Pesto Pizza" + desc = "A rectangular pizza with a suspiciously bright green pesto in place of the standard tomato sauce." + icon_state = "foodtray_pesto_pizza" + tastes = list("tomato?" = 2, "cheese?" = 2, "herbs" = 1, "laziness" = 1) + foodtypes = GRAIN | DAIRY | VEGETABLES + +/obj/item/food/vendor_tray_meal/baked_rice + name = "\improper Main Course - Type M: Baked Rice and Grilled Cheese" + desc = "Some sub-par looking fleet style rice, with a very grilled chunk of cheese." + icon_state = "foodtray_rice_n_grilled_cheese" + tastes = list("rice" = 2, "peppers" = 2, "charred cheese" = 2, "laziness" = 1) + foodtypes = GRAIN | DAIRY | VEGETABLES + +/obj/item/food/vendor_tray_meal/fueljack + name = "\improper Main Course - Type M: Fueljack's Tray" + desc = "A flat chunk of fueljack's lunch, seemingly missing most of the usual variety in ingredients." + icon_state = "foodtray_fuel_jacks_meal" + tastes = list("potato" = 2, "cabbage" = 2, "cheese?" = 2, "laziness" = 1) + foodtypes = DAIRY | VEGETABLES + +/* +* Tiziran Meals +*/ + +/obj/item/food/vendor_tray_meal/moonfish_nizaya + name = "\improper Tizira Imports: Moonfish and Nizaya" + desc = "An almost synthetic looking cut of moonfish, paired with an equal helping of nizaya pasta." + icon_state = "foodtray_moonfish_nizaya" + tastes = list("fish?" = 2, "cheap noodles" = 2, "laziness" = 1) + foodtypes = VEGETABLES | NUTS | SEAFOOD + +/obj/item/food/vendor_tray_meal/emperor_roll + name = "\improper Tizira Imports: Emperor Roll" + desc = "A pretty sad looking emperor roll, if you can even call it that; it seems caviar wasn't in the budget." + icon_state = "foodtray_emperor_roll" + tastes = list("bread" = 2, "cheese?" = 2, "liver?" = 2, "laziness" = 1) + foodtypes = VEGETABLES | NUTS | MEAT | GORE + +/obj/item/food/vendor_tray_meal/mushroom_fry + name = "\improper Tizira Imports: Mushroom Stirfry" + desc = "A mix of what was likely mushrooms too low quality to be used in making actual food, lightly fried and tossed in a plastic container together." + icon_state = "foodtray_shroom_fry" + tastes = list("mushroom" = 4, "becoming rich" = 1, "laziness" = 1) + foodtypes = VEGETABLES + +/* SIDES */ + +/obj/effect/spawner/random/vendor_meal_sides + name = "random side spawner" + desc = "I hope I get one that actually matches my meal." + icon_state = "loot" + +/* +* NT Sides +*/ + +/obj/effect/spawner/random/vendor_meal_sides/nt + name = "random nt side spawner" + +/obj/effect/spawner/random/vendor_meal_sides/nt/Initialize(mapload) + loot = list( + /obj/item/food/vendor_tray_meal/side, + /obj/item/food/vendor_tray_meal/side/crackers_and_jam, + /obj/item/food/vendor_tray_meal/side/crackers_and_cheese, + ) + . = ..() + +/obj/item/food/vendor_tray_meal/side + name = "\improper NT-Side: Flatbread and Peanut Butter" + desc = "A small stack of tough flatbread, and a small spread of peanut butter for each." + icon_state = "foodpack_nt" + trash_type = /obj/item/trash/empty_side_pack/nt + food_reagents = list(/datum/reagent/consumable/nutriment = 5) + tastes = list("tough bread" = 2, "peanut butter" = 2) + foodtypes = GRAIN + hot_and_steamy = FALSE + custom_price = PAYCHECK_LOWER * 2.5 + +/obj/item/food/vendor_tray_meal/side/crackers_and_jam + name = "\improper NT-Side: Flatbread and Berry Jelly" + desc = "A small stack of tough flatbread, and a small spread of nondescript berry jelly for each." + tastes = list("tough bread" = 2, "berries" = 2) + foodtypes = GRAIN | FRUIT + +/obj/item/food/vendor_tray_meal/side/crackers_and_cheese + name = "\improper NT-Side: Flatbread and Cheese Spread" + desc = "A small stack of tough flatbread, and a small spread of cheese for each." + tastes = list("tough bread" = 2, "cheese" = 2) + foodtypes = GRAIN | DAIRY + +/* +* Yangyu Sides +*/ + +/obj/effect/spawner/random/vendor_meal_sides/yangyu + name = "random yangyu side spawner" + +/obj/effect/spawner/random/vendor_meal_sides/yangyu/Initialize(mapload) + loot = list( + /obj/item/food/vendor_tray_meal/side/miso, + /obj/item/food/vendor_tray_meal/side/rice, + /obj/item/food/vendor_tray_meal/side/pickled_vegetables, + ) + . = ..() + +/obj/item/food/vendor_tray_meal/side/miso + name = "\improper Fukusai: Miso Soup" + desc = "This is quite literally just a plastic bag full of miso soup, opening it on any side other than the one indicated may result in spilled soup." + icon_state = "foodpack_yangyu" + trash_type = /obj/item/trash/empty_side_pack/yangyu + tastes = list("miso" = 2) + foodtypes = VEGETABLES + +/obj/item/food/vendor_tray_meal/side/rice + name = "\improper Fukusai: White Rice" + desc = "A bag stuffed full of white rice, in case your meal didn't come with enough to sate your needs." + icon_state = "foodpack_yangyu" + trash_type = /obj/item/trash/empty_side_pack/yangyu + tastes = list("old rice" = 2) + foodtypes = GRAIN + +/obj/item/food/vendor_tray_meal/side/pickled_vegetables + name = "\improper Fukusai: Pickled Vegetables" + desc = "Contains a small assortment of vegetables pickled in a vinegar-like solution." + icon_state = "foodpack_yangyu" + trash_type = /obj/item/trash/empty_side_pack/yangyu + tastes = list("vinegar" = 4) + foodtypes = VEGETABLES + +/* +* Mothic Sides +*/ + +/obj/effect/spawner/random/vendor_meal_sides/moth + name = "random mothic side spawner" + +/obj/effect/spawner/random/vendor_meal_sides/moth/Initialize(mapload) + loot = list( + /obj/item/food/vendor_tray_meal/side/moffin, + /obj/item/food/vendor_tray_meal/side/cornbread, + /obj/item/food/vendor_tray_meal/side/roasted_seeds, + ) + . = ..() + +/obj/item/food/vendor_tray_meal/side/moffin + name = "\improper Side Course - Type M: Moffin" + desc = "The result of taking a perfectly fine moffin, and flattening it into a more wafer-like form." + icon_state = "foodpack_moth" + trash_type = /obj/item/trash/empty_side_pack/moth + tastes = list("fabric?" = 2, "sugar" = 2) + foodtypes = CLOTH | GRAIN | SUGAR + +/obj/item/food/vendor_tray_meal/side/cornbread + name = "\improper Side Course - Type M: Cornbread" + desc = "A flattened cut of sweetened cornbread, goes well with butter." + icon_state = "foodpack_moth" + trash_type = /obj/item/trash/empty_side_pack/moth + tastes = list("cornbread" = 2, "sweetness" = 2) + foodtypes = GRAIN | SUGAR + +/obj/item/food/vendor_tray_meal/side/roasted_seeds + name = "\improper Side Course - Type M: Roasted Seeds" + desc = "A packet full of various oven roasted seeds." + icon_state = "foodpack_moth" + trash_type = /obj/item/trash/empty_side_pack/moth + tastes = list("seeds" = 2, "char" = 2) + foodtypes = NUTS + +/* +* Tiziran Sides +*/ + +/obj/effect/spawner/random/vendor_meal_sides/tizira + name = "random tiziran side spawner" + +/obj/effect/spawner/random/vendor_meal_sides/tizira/Initialize(mapload) + loot = list( + /obj/item/food/vendor_tray_meal/side/root_crackers, + /obj/item/food/vendor_tray_meal/side/korta_brittle, + /obj/item/food/vendor_tray_meal/side/crispy_headcheese, + ) + . = ..() + +/obj/item/food/vendor_tray_meal/side/root_crackers + name = "\improper Tizira Imports: Rootbread Crackers and Pate" + desc = "A small stack of rootbread crackers, with a small spread of meat pate for each." + icon_state = "foodpack_tizira" + trash_type = /obj/item/trash/empty_side_pack/tizira + tastes = list("tough rootbread" = 2, "pate" = 2) + foodtypes = VEGETABLES | NUTS | MEAT + +/obj/item/food/vendor_tray_meal/side/korta_brittle + name = "\improper Tizira Imports: Korta Brittle" + desc = "A perfectly rectangular portion of unsweetened korta brittle." + icon_state = "foodpack_tizira" + trash_type = /obj/item/trash/empty_side_pack/tizira + tastes = list("peppery heat" = 2) + foodtypes = NUTS + +/obj/item/food/vendor_tray_meal/side/crispy_headcheese + name = "\improper Tizira Imports: Crisped Headcheese" + desc = "A processed looking block of breaded headcheese." + icon_state = "foodpack_tizira" + trash_type = /obj/item/trash/empty_side_pack/tizira + tastes = list("cheese" = 1, "oil" = 1) + foodtypes = MEAT | VEGETABLES | NUTS | GORE diff --git a/modular_doppler/vending_machines/code/vendor_snacks.dm b/modular_doppler/vending_machines/code/vendor_snacks.dm new file mode 100644 index 0000000000000..0b5fd481f2b4d --- /dev/null +++ b/modular_doppler/vending_machines/code/vendor_snacks.dm @@ -0,0 +1,302 @@ +// Snacks and drinks for the 'snacks' tab of vendors + +/obj/item/food/vendor_snacks + name = "\improper God's Strongest Snacks" + desc = "You best hope you aren't a sinner. (You should never see this item please report it)" + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodpack_generic" + trash_type = /obj/item/trash/vendor_trash + bite_consumption = 10 + food_reagents = list(/datum/reagent/consumable/nutriment = INFINITY) + junkiness = 10 + custom_price = PAYCHECK_LOWER * INFINITY + tastes = list("the unmatched power of the sun" = 10) + foodtypes = JUNKFOOD | CLOTH | GORE | NUTS | FRIED | FRUIT + w_class = WEIGHT_CLASS_SMALL + +/obj/item/trash/vendor_trash + name = "\improper God's Weakest Snacks" + desc = "The leftovers of what was likely a great snack in a past time." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "foodpack_generic_trash" + custom_materials = list( + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + ) + +/* +* Yangyu Snacks +*/ + +/obj/item/reagent_containers/cup/glass/dry_ramen/prepared + name = "cup ramen" + desc = "This one even comes with water, amazing!" + list_reagents = list(/datum/reagent/consumable/hot_ramen = 15, /datum/reagent/consumable/salt = 3) + +/obj/item/reagent_containers/cup/glass/dry_ramen/prepared/hell + name = "spicy cup ramen" + desc = "This one comes with water, AND a security checkpoint's worth of capsaicin!" + list_reagents = list(/datum/reagent/consumable/hell_ramen = 15, /datum/reagent/consumable/salt = 3) + +/obj/item/food/vendor_snacks/rice_crackers + name = "rice crackers" + desc = "Despite most of the package being clear, you will never truly know what flavor these are until you eat them." + icon_state = "rice_cracka" + trash_type = /obj/item/trash/vendor_trash/rice_crackers + food_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/rice = 2) + tastes = list("incomprehensible flavoring" = 1, "rice cracker" = 2) + foodtypes = JUNKFOOD | GRAIN + custom_price = PAYCHECK_LOWER * 0.8 + +/obj/item/food/vendor_snacks/rice_crackers/make_leave_trash() + AddElement(/datum/element/food_trash, trash_type, FOOD_TRASH_POPABLE) + +/obj/item/trash/vendor_trash/rice_crackers + name = "empty rice crackers bag" + desc = "You never did find out what flavor that was supposed to be, did you?" + icon_state = "rice_cracka_trash" + +/obj/item/food/vendor_snacks/mochi_ice_cream + name = "mochi ice cream balls - vanilla" + desc = "A six pack of mochi ice cream, which is to say vanilla icecream surrounded by mochi. Comes with small plastic skewer for consumption." + icon_state = "mochi_ice" + trash_type = /obj/item/trash/vendor_trash/mochi_ice_cream + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/ice = 3) + tastes = list("rice cake" = 2, "vanilla" = 2) + foodtypes = JUNKFOOD | DAIRY | GRAIN + custom_price = PAYCHECK_LOWER + +/obj/item/food/vendor_snacks/mochi_ice_cream/matcha + name = "mochi ice cream balls - matcha" + desc = "A six pack of mochi ice cream - or, more specifically, matcha icecream surrounded by mochi. Comes with small plastic skewer for consumption." + icon_state = "mochi_ice_green" + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/ice = 1, /datum/reagent/consumable/tea = 2) + tastes = list("rice cake" = 1, "bitter matcha" = 2) + custom_price = PAYCHECK_LOWER * 1.2 + +/obj/item/food/vendor_snacks/mochi_ice_cream/matcha/examine_more(mob/user) + . = ..() + . += span_notice("A small label on the container specifies that this icecream is made using only culinary grade matcha grown outside of the Sol system.") + return . + +/obj/item/trash/vendor_trash/mochi_ice_cream + name = "empty mochi ice cream tray" + desc = "Somehow, that tiny plastic skewer it came with has gone missing." + icon_state = "mochi_ice_trash" + +/obj/item/reagent_containers/cup/glass/waterbottle/tea + name = "bottle of tea" + desc = "A bottle of tea brought to you in a convenient plastic bottle." + icon = 'modular_doppler/vending_machines/icons/imported_quick_foods.dmi' + icon_state = "tea_bottle" + list_reagents = list(/datum/reagent/consumable/tea = 40) + cap_icon_state = "bottle_cap_tea" + flip_chance = 5 + custom_price = PAYCHECK_LOWER * 1.2 + fill_icon_state = null + +/obj/item/reagent_containers/cup/glass/waterbottle/tea/astra + name = "bottle of tea astra" + desc = "A bottle of tea astra, known for the rather unusual tastes the leaf is known to give when brewed." + icon_state = "tea_bottle_blue" + list_reagents = list( + /datum/reagent/consumable/tea = 25, + /datum/reagent/medicine/salglu_solution = 10, + /datum/reagent/consumable/nutriment/vitamin = 5, + ) + custom_price = PAYCHECK_LOWER * 2 + +/obj/item/reagent_containers/cup/glass/waterbottle/tea/strawberry + name = "bottle of strawberry tea" + desc = "A bottle of strawberry flavored tea; does not contain any actual strawberries." + icon_state = "tea_bottle_pink" + list_reagents = list(/datum/reagent/consumable/pinktea = 40) + custom_price = PAYCHECK_LOWER * 2 + +/obj/item/reagent_containers/cup/glass/waterbottle/tea/nip + name = "bottle of catnip tea" + desc = "A bottle of catnip tea, required to be at or under a 50% concentration by the SFDA for safety purposes." + icon_state = "tea_bottle_pink" + list_reagents = list( + /datum/reagent/consumable/catnip_tea = 20, + /datum/reagent/consumable/pinkmilk = 20, + ) + custom_price = PAYCHECK_LOWER * 2.5 + +/* +* Mothic Snacks +*/ + +/obj/item/food/vendor_snacks/mothmallow + name = "mothmallow" + desc = "A vacuum sealed bag containing a pretty crushed looking mothmallow, someone save him!" + icon_state = "mothmallow" + trash_type = /obj/item/trash/vendor_trash/mothmallow + food_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/sugar = 4) + tastes = list("vanilla" = 1, "cotton" = 1, "chocolate" = 1) + foodtypes = VEGETABLES | SUGAR + custom_price = PAYCHECK_LOWER + +/obj/item/food/vendor_snacks/mothmallow/make_leave_trash() + AddElement(/datum/element/food_trash, trash_type, FOOD_TRASH_POPABLE) + +/obj/item/trash/vendor_trash/mothmallow + name = "empty mothmallow bag" + desc = "Finally he is free." + icon_state = "mothmallow_trash" + +/obj/item/food/vendor_snacks/moth_bag + name = "engine fodder" + desc = "A vacuum sealed bag containing a small portion of colorful engine fodder." + icon_state = "fodder" + trash_type = /obj/item/trash/vendor_trash/moth_bag + food_reagents = list(/datum/reagent/consumable/sugar = 3, /datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/salt = 2) + tastes = list("seeds" = 1, "nuts" = 1, "chocolate" = 1, "salt" = 1, "popcorn" = 1, "potato" = 1) + foodtypes = GRAIN | NUTS | VEGETABLES | SUGAR + custom_price = PAYCHECK_LOWER * 1.2 + +/obj/item/food/vendor_snacks/moth_bag/fuel_jack + name = "fueljack's snack" + desc = "A vacuum sealed bag containing a smaller than usual brick of fueljack's lunch, ultimately downgrading it to a fueljack's snack." + icon_state = "fuel_jack_snack" + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/protein = 1) + tastes = list("cabbage" = 1, "potato" = 1, "onion" = 1, "chili" = 1, "cheese" = 1) + foodtypes = DAIRY | VEGETABLES + custom_price = PAYCHECK_LOWER * 1.2 + +/obj/item/food/vendor_snacks/moth_bag/cheesecake + name = "chocolate cheesecake cube" + desc = "A vacuum sealed bag containing a small cube of a mothic style cheesecake, this one is covered in chocolate." + icon_state = "choco_cheese_cake" + food_reagents = list(/datum/reagent/consumable/nutriment/protein = 2, /datum/reagent/consumable/sugar = 4) + tastes = list("cheesecake" = 1, "chocolate" = 1) + foodtypes = SUGAR | FRIED | DAIRY | GRAIN + custom_price = PAYCHECK_LOWER * 1.4 + +/obj/item/food/vendor_snacks/moth_bag/cheesecake/honey + name = "honey cheesecake cube" + desc = "A vacuum sealed bag containing a small cube of a mothic style cheesecake, this one is covered in honey." + icon_state = "honey_cheese_cake" + tastes = list("cheesecake" = 1, "honey" = 1) + foodtypes = SUGAR | FRIED | DAIRY | GRAIN + +/obj/item/trash/vendor_trash/moth_bag + name = "empty mothic snack bag" + desc = "The clear plastic reveals that this no longer holds tasty treats for your winged friends." + icon_state = "moth_bag_trash" + +/obj/item/reagent_containers/cup/soda_cans/doppler/lemonade + name = "\improper Gyárhajó 1023: Lemonade" + desc = "A can of lemonade, for those who are into that kind of thing, or just have no choice." + icon_state = "lemonade" + list_reagents = list(/datum/reagent/consumable/lemonade = 30) + drink_type = FRUIT + +/obj/item/reagent_containers/cup/soda_cans/doppler/lemonade/examine_more(mob/user) + . = ..() + . += span_notice("Markings on the can indicate this one was made on factory ship 1023 of the Grand Nomad Fleet.") + return . + +/obj/item/reagent_containers/cup/soda_cans/doppler/navy_rum + name = "\improper Gyárhajó 1506: Navy Rum" + desc = "A can of navy rum brewed up and imported from a detachment of the nomad fleet, or so the can says." + icon_state = "navy_rum" + list_reagents = list(/datum/reagent/consumable/ethanol/navy_rum = 30) + drink_type = ALCOHOL + +/obj/item/reagent_containers/cup/soda_cans/doppler/navy_rum/examine_more(mob/user) + . = ..() + . += span_notice("Markings on the can indicate this one was made on factory ship 1506 of the Grand Nomad Fleet.") + return . + +/obj/item/reagent_containers/cup/soda_cans/doppler/soda_water_moth + name = "\improper Gyárhajó 1023: Soda Water" + desc = "A can of soda water. Why not make a rum and soda? Now that you think of it, maybe not." + icon_state = "soda_water" + list_reagents = list(/datum/reagent/consumable/sodawater = 30) + drink_type = SUGAR + +/obj/item/reagent_containers/cup/soda_cans/doppler/soda_water_moth/examine_more(mob/user) + . = ..() + . += span_notice("Markings on the can indicate this one was made on factory ship 1023 of the Grand Nomad Fleet.") + return . + +/obj/item/reagent_containers/cup/soda_cans/doppler/ginger_beer + name = "\improper Gyárhajó 1023: Ginger Beer" + desc = "A can of ginger beer, don't let the beer part mislead you, this is entirely non-alcoholic." + icon_state = "gingie_beer" + list_reagents = list(/datum/reagent/consumable/sol_dry = 30) + drink_type = SUGAR + +/obj/item/reagent_containers/cup/soda_cans/doppler/ginger_beer/examine_more(mob/user) + . = ..() + . += span_notice("Markings on the can indicate this one was made on factory ship 1023 of the Grand Nomad Fleet.") + return . + +/* +* Tiziran Snacks +*/ + +/obj/item/food/vendor_snacks/lizard_bag + name = "candied mushroom" + desc = "An odd treat of the lizard empire, a mushroom dipped in caramel; unfortunately, it seems to have been bagged before the caramel fully hardened." + icon_state = "candied_shroom" + trash_type = /obj/item/trash/vendor_trash/lizard_bag + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/caramel = 2) + tastes = list("savouriness" = 1, "sweetness" = 1) + foodtypes = SUGAR | VEGETABLES + custom_price = PAYCHECK_LOWER * 1.4 //Tiziran imports are a bit more expensive overall + +/obj/item/food/vendor_snacks/lizard_bag/make_leave_trash() + AddElement(/datum/element/food_trash, trash_type, FOOD_TRASH_POPABLE) + +/obj/item/food/vendor_snacks/lizard_bag/moon_jerky + name = "moonfish jerky" + desc = "A fish jerky, made from what you can only hope is moonfish. It also seems to taste subtly of barbecue" + icon_state = "moon_jerky" + food_reagents = list(/datum/reagent/consumable/nutriment/protein = 2, /datum/reagent/consumable/bbqsauce = 2) + tastes = list("fish" = 1, "smokey sauce" = 1) + foodtypes = MEAT + custom_price = PAYCHECK_LOWER * 1.6 + +/obj/item/trash/vendor_trash/lizard_bag + name = "empty tiziran snack bag" + desc = "All that money importing tiziran snacks just to end at this?" + icon_state = "tizira_bag_trash" + +/obj/item/food/vendor_snacks/lizard_box + name = "tiziran dumplings" + desc = "A three pack of tiziran style dumplings, not actually stuffed with anything." + icon_state = "dumpling" + trash_type = /obj/item/trash/vendor_trash/lizard_box + food_reagents = list(/datum/reagent/consumable/nutriment = 3) + tastes = list("potato" = 1, "earthy heat" = 1) + foodtypes = VEGETABLES | NUTS + custom_price = PAYCHECK_LOWER * 1.6 + +/obj/item/food/vendor_snacks/lizard_box/sweet_roll + name = "honey roll" + desc = "Definitely don't let the guards find out that someone stole your last one." + icon_state = "sweet_roll" + food_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/honey = 2) + tastes = list("bread" = 1, "honey" = 1, "fruit" = 1) + foodtypes = VEGETABLES | NUTS | FRUIT + custom_price = PAYCHECK_LOWER *1.8 + +/obj/item/trash/vendor_trash/lizard_box + name = "empty tiziran snack box" + desc = "Tizira, contributing to the space plastic crisis since 2530." + icon_state = "tizira_box_trash" + +/obj/item/reagent_containers/cup/glass/waterbottle/tea/mushroom + name = "bottle of mushroom tea" + desc = "A bottle of somewhat bitter mushroom tea, a favorite of the Tiziran empire." + icon_state = "tea_bottle_grey" + list_reagents = list(/datum/reagent/consumable/mushroom_tea = 40) + custom_price = PAYCHECK_LOWER * 2 + +/obj/item/reagent_containers/cup/soda_cans/doppler/kortara + name = "kortara" + desc = "A can of kortara, alcohol brewed from korta seeds, which gives it a unique peppery spice flavor." + icon_state = "kortara" + list_reagents = list(/datum/reagent/consumable/ethanol/kortara = 30) + drink_type = ALCOHOL diff --git a/modular_doppler/vending_machines/code/vendors.dm b/modular_doppler/vending_machines/code/vendors.dm new file mode 100644 index 0000000000000..f5a4085f79a4c --- /dev/null +++ b/modular_doppler/vending_machines/code/vendors.dm @@ -0,0 +1,229 @@ +/obj/effect/spawner/random/vending/snackvend + loot = list( + /obj/machinery/vending/imported/nt, + /obj/machinery/vending/imported/yangyu, + /obj/machinery/vending/imported/mothic, + /obj/machinery/vending/imported/tiziran, +// /obj/machinery/vending/deforest_medvend, + ) + +/obj/effect/spawner/random/vending/colavend //These can serve both snacks AND drinks so it's kinda both of them? + loot = list( + /obj/machinery/vending/imported/nt, + /obj/machinery/vending/imported/yangyu, + /obj/machinery/vending/imported/mothic, + /obj/machinery/vending/imported/tiziran, +// /obj/machinery/vending/deforest_medvend, + ) + +/datum/supply_pack/vending/imported/fill(obj/structure/closet/crate/target_crate) + . = ..() + for(var/obj/vendor_refill as anything in subtypesof(/obj/item/vending_refill/snack/imported)) + new vendor_refill(target_crate) + +/obj/machinery/vending/imported + icon = 'modular_doppler/vending_machines/icons/imported_vendors.dmi' + icon_state = null + panel_type = "panel15" + default_price = PAYCHECK_CREW * 0.5 + extra_price = PAYCHECK_COMMAND + payment_department = NO_FREEBIES + +/obj/machinery/vending/imported/nt + name = "NT Sustenance Supplier" + desc = "A vending machine serving up only the finest of human college student food." + icon_state = "nt_food" + light_mask = "nt_food-light-mask" + light_color = LIGHT_COLOR_LIGHT_CYAN + product_slogans = "Caution, contents may be selling hot!;Look at these low prices!;Hungry? Me too- Wait, no, you didn't hear that!" + product_categories = list( + list( + "name" = "Snacks", + "icon" = "cookie", + "products" = list( + /obj/item/food/peanuts/random = 6, + /obj/item/food/cnds/random = 6, + /obj/item/food/pistachios = 6, + /obj/item/food/cornchips/random = 6, + /obj/item/food/sosjerky = 6, + /obj/item/reagent_containers/cup/soda_cans/cola = 6, + /obj/item/reagent_containers/cup/soda_cans/lemon_lime = 6, + /obj/item/reagent_containers/cup/soda_cans/starkist = 6, + /obj/item/reagent_containers/cup/soda_cans/pwr_game = 6, + ), + ), + list( + "name" = "Meals", + "icon" = "pizza-slice", + "products" = list( + /obj/item/storage/box/foodpack/nt = 6, + /obj/item/storage/box/foodpack/nt/burger = 6, + /obj/item/storage/box/foodpack/nt/chicken_sammy = 6, + /obj/item/food/vendor_tray_meal/side = 6, + /obj/item/food/vendor_tray_meal/side/crackers_and_jam = 6, + /obj/item/food/vendor_tray_meal/side/crackers_and_cheese = 6, + ), + ), + ) + + refill_canister = /obj/item/vending_refill/snack/imported/nt + +/obj/item/vending_refill/snack/imported/nt + machine_name = "NT Sustenance Supplier" + +/obj/machinery/vending/imported/yangyu + name = "Fudobenda" + desc = "A vendor selling traditional Sol eastern foods of dubious quality." + icon_state = "yangyu_food" + light_mask = "yangyu_food-light-mask" + light_color = LIGHT_COLOR_FLARE + product_slogans = "Fresh farmed space carp from local space!;Imitation lobstrocity sushi choices availible!;Made with traditional recipes and care!" + product_categories = list( + list( + "name" = "Snacks", + "icon" = "cookie", + "products" = list( + /obj/item/reagent_containers/cup/glass/dry_ramen/prepared = 6, + /obj/item/reagent_containers/cup/glass/dry_ramen/prepared/hell = 6, + /obj/item/food/vendor_snacks/rice_crackers = 6, + /obj/item/food/vendor_snacks/mochi_ice_cream = 6, + /obj/item/food/vendor_snacks/mochi_ice_cream/matcha = 6, + /obj/item/reagent_containers/cup/glass/waterbottle/tea = 6, + /obj/item/reagent_containers/cup/glass/waterbottle/tea/astra = 6, + /obj/item/reagent_containers/cup/glass/waterbottle/tea/strawberry = 6, + /obj/item/reagent_containers/cup/glass/waterbottle/tea/nip = 6, + ), + ), + list( + "name" = "Meals", + "icon" = "pizza-slice", + "products" = list( + /obj/item/storage/box/foodpack/yangyu = 6, + /obj/item/storage/box/foodpack/yangyu/sushi = 6, + /obj/item/storage/box/foodpack/yangyu/beef_rice = 6, + /obj/item/food/vendor_tray_meal/side/miso = 6, + /obj/item/food/vendor_tray_meal/side/rice = 6, + /obj/item/food/vendor_tray_meal/side/pickled_vegetables = 6, + ), + ), + ) + + refill_canister = /obj/item/vending_refill/snack/imported/yangyu + initial_language_holder = /datum/language_holder/yangyu_vendor + +/datum/language_holder/yangyu_vendor + understood_languages = list( + /datum/language/yangyu = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/yangyu = list(LANGUAGE_ATOM), + ) + +/obj/machinery/vending/imported/yangyu/examine_more(mob/user) + . = ..() + . += span_notice("Someone appears to have written \"Don't trust the sushi!\" in marker on the side of the vendor.") + return . + +/obj/item/vending_refill/snack/imported/yangyu + machine_name = "Fudobenda" + +/obj/machinery/vending/imported/mothic + name = "Nomad Fleet Ration Chit Exchange" + desc = "One of the Nomad Fleet's own ration vendors; in spite of the name engraved into it, it's been fitted to accept credits." + icon_state = "moth_food" + light_mask = "moth_food-light-mask" + light_color = LIGHT_COLOR_HALOGEN + product_slogans = "Support the fleet, conserve rations today!;Some options in reduced portion and cost!;Do your part to keep the fleet flying!" + product_categories = list( + list( + "name" = "Snacks", + "icon" = "cookie", + "products" = list( + /obj/item/food/vendor_snacks/mothmallow = 6, + /obj/item/food/vendor_snacks/moth_bag = 6, + /obj/item/food/vendor_snacks/moth_bag/fuel_jack = 6, + /obj/item/food/vendor_snacks/moth_bag/cheesecake = 6, + /obj/item/food/vendor_snacks/moth_bag/cheesecake/honey = 6, + /obj/item/reagent_containers/cup/soda_cans/doppler/lemonade = 6, + /obj/item/reagent_containers/cup/soda_cans/doppler/navy_rum = 6, + /obj/item/reagent_containers/cup/soda_cans/doppler/soda_water_moth = 6, + /obj/item/reagent_containers/cup/soda_cans/doppler/ginger_beer = 6, + ), + ), + list( + "name" = "Meals", + "icon" = "pizza-slice", + "products" = list( + /obj/item/storage/box/foodpack/moth = 6, + /obj/item/storage/box/foodpack/moth/baked_rice = 6, + /obj/item/storage/box/foodpack/moth/fuel_jack = 6, + /obj/item/food/vendor_tray_meal/side/moffin = 6, + /obj/item/food/vendor_tray_meal/side/cornbread = 6, + /obj/item/food/vendor_tray_meal/side/roasted_seeds = 6, + ), + ), + ) + + refill_canister = /obj/item/vending_refill/snack/imported/mothic + initial_language_holder = /datum/language_holder/moffic_vendor + +/datum/language_holder/moffic_vendor + understood_languages = list( + /datum/language/moffic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/moffic = list(LANGUAGE_ATOM), + ) + +/obj/item/vending_refill/snack/imported/mothic + machine_name = "Nomad Fleet Ration Chit Exchange" + +/obj/machinery/vending/imported/tiziran + name = "Tiziran Imported Delicacies" + desc = "A vendor serving a fine collection of what is very likely knock-offs of popular Tiziran brands." + icon_state = "tizira_food" + light_mask = "tizira_food-light-mask" + light_color = LIGHT_COLOR_FIRE + product_slogans = "Real imports from the capital itself, we promise!;Rare selections of salt water catch!;Moonfish glaze included with all meat options!" + product_categories = list( + list( + "name" = "Snacks", + "icon" = "cookie", + "products" = list( + /obj/item/food/chips/shrimp = 6, + /obj/item/food/vendor_snacks/lizard_bag = 6, + /obj/item/food/vendor_snacks/lizard_bag/moon_jerky = 6, + /obj/item/food/vendor_snacks/lizard_box = 6, + /obj/item/food/vendor_snacks/lizard_box/sweet_roll = 6, + /obj/item/reagent_containers/cup/glass/bottle/mushi_kombucha = 6, + /obj/item/reagent_containers/cup/glass/waterbottle/tea/mushroom = 6, + /obj/item/reagent_containers/cup/soda_cans/doppler/kortara = 6, + ), + ), + list( + "name" = "Meals", + "icon" = "pizza-slice", + "products" = list( + /obj/item/storage/box/foodpack/tizira = 6, + /obj/item/storage/box/foodpack/tizira/roll = 6, + /obj/item/storage/box/foodpack/tizira/stir_fry = 6, + /obj/item/food/vendor_tray_meal/side/root_crackers = 6, + /obj/item/food/vendor_tray_meal/side/korta_brittle = 6, + /obj/item/food/vendor_tray_meal/side/crispy_headcheese = 6, + ), + ), + ) + + refill_canister = /obj/item/vending_refill/snack/imported/tiziran + initial_language_holder = /datum/language_holder/draconic_vendor + +/datum/language_holder/draconic_vendor + understood_languages = list( + /datum/language/draconic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/draconic = list(LANGUAGE_ATOM), + ) + +/obj/item/vending_refill/snack/imported/tiziran + machine_name = "Tiziran Imported Delicacies" diff --git a/modular_doppler/vending_machines/icons/imported_quick_foods.dmi b/modular_doppler/vending_machines/icons/imported_quick_foods.dmi new file mode 100644 index 0000000000000..4249a5f4f0a48 Binary files /dev/null and b/modular_doppler/vending_machines/icons/imported_quick_foods.dmi differ diff --git a/modular_doppler/vending_machines/icons/imported_vendors.dmi b/modular_doppler/vending_machines/icons/imported_vendors.dmi new file mode 100644 index 0000000000000..d26e14e5a38ed Binary files /dev/null and b/modular_doppler/vending_machines/icons/imported_vendors.dmi differ diff --git a/modular_doppler/vending_machines/readme.md b/modular_doppler/vending_machines/readme.md new file mode 100644 index 0000000000000..b9f0bff08d00c --- /dev/null +++ b/modular_doppler/vending_machines/readme.md @@ -0,0 +1,26 @@ +## Title: ImportedVendors + +MODULE ID: CulturalVendors + +### Description: + +Adds random, imported food and drink vendors to the pool of snack vendors that can spawn across the maps + +### TG Proc Changes: + +- N/A + +### Defines: + +- N/A + +### Master file additions + +- N/A + +### Included files that are not contained in this module: + +- N/A + +### Credits: +Paxilmaniac diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/amauri.dm b/modular_doppler/xenoarch/code/modules/hydroponics/amauri.dm new file mode 100644 index 0000000000000..0315da4335804 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/amauri.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/amauri + name = "amauri seed pack" + desc = "These seeds grow into amauri plants. Grows bulbs full of potent toxins." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "amauri" + species = "amauri" + plantname = "Amauri Plant" + product = /obj/item/food/grown/amauri + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "amauri-stage" + growthstages = 3 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/preserved) + reagents_add = list(/datum/reagent/toxin = 0.1, /datum/reagent/toxin/venom = 0.1, /datum/reagent/toxin/hot_ice = 0.1) + +/obj/item/food/grown/amauri + seed = /obj/item/seeds/amauri + name = "amauri" + desc = "A toxic amauri bulb, you shouldn't eat this." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "amauri" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/toxin + tastes = list("poison" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/gelthi.dm b/modular_doppler/xenoarch/code/modules/hydroponics/gelthi.dm new file mode 100644 index 0000000000000..174daadc33eaa --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/gelthi.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/gelthi + name = "gelthi seed pack" + desc = "These seeds grow into gelthi plants. Lauded by chefs for its unique ability to produce honey, and often hoarded for this very reason." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "gelthi" + species = "gelthi" + plantname = "Gelthi Plant" + product = /obj/item/food/grown/gelthi + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "gelthi-stage" + growthstages = 3 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/squash) + reagents_add = list(/datum/reagent/consumable/sprinkles = 0.1, /datum/reagent/consumable/astrotame = 0.1, /datum/reagent/consumable/honey = 0.2) + +/obj/item/food/grown/gelthi + seed = /obj/item/seeds/gelthi + name = "gelthi" + desc = "A cluster of gelthi pods. Each pod contains a different sweetener, and the pods can be juiced into raw sugar." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "gelthi" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/consumable/sugar + tastes = list("overpowering sweetness" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/jurlmah.dm b/modular_doppler/xenoarch/code/modules/hydroponics/jurlmah.dm new file mode 100644 index 0000000000000..1fc8b2d9fd4bd --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/jurlmah.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/jurlmah + name = "jurlmah seed pack" + desc = "These seeds grow into jurlmah plants. Often used as makeshift cryo-treatment in areas where a dedicated cryotube setup is impossible." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "jurlmah" + species = "jurlmah" + plantname = "Jurlmah Plant" + product = /obj/item/food/grown/jurlmah + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "jurlmah-stage" + growthstages = 5 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/glow/blue) + reagents_add = list(/datum/reagent/medicine/cryoxadone = 0.1, /datum/reagent/inverse/healing/tirimol = 0.1, /datum/reagent/consumable/frostoil = 0.1) + +/obj/item/food/grown/jurlmah + seed = /obj/item/seeds/jurlmah + name = "jurlmah" + desc = "A frosty jurlmah fruit, it feels cold to the touch." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "jurlmah" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/medicine/cryoxadone + tastes = list("snow" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/nofruit.dm b/modular_doppler/xenoarch/code/modules/hydroponics/nofruit.dm new file mode 100644 index 0000000000000..5e003367e498e --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/nofruit.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/nofruit + name = "nofruit seed pack" + desc = "These seeds grow into nofruit plants. A strange plant often cultivated by silent performers." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "nofruit" + species = "nofruit" + plantname = "Nofruit Plant" + product = /obj/item/food/grown/nofruit + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "nofruit-stage" + growthstages = 4 + genes = list(/datum/plant_gene/trait/repeated_harvest) + reagents_add = list(/datum/reagent/consumable/nothing = 0.1, /datum/reagent/toxin/mimesbane = 0.1, /datum/reagent/toxin/mutetoxin = 0.1) + +/obj/item/food/grown/nofruit + seed = /obj/item/seeds/nofruit + name = "nofruit" + desc = "A cubic nofruit, the leaf on top of the nofruit gesticulates wildly." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "nofruit" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/consumable/nothing + tastes = list("nothing" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/shand.dm b/modular_doppler/xenoarch/code/modules/hydroponics/shand.dm new file mode 100644 index 0000000000000..349eaec78b892 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/shand.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/shand + name = "shand seed pack" + desc = "These seeds grow into shand plants. While not very useful on its own, it is full of chemicals that no other plant can produce. A good candidate for crossbreeding." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "shand" + species = "shand" + plantname = "Shand Plant" + product = /obj/item/food/grown/shand + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "shand-stage" + growthstages = 3 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/maxchem) + reagents_add = list(/datum/reagent/bromine = 0.1, /datum/reagent/sodium = 0.1, /datum/reagent/copper = 0.1) + +/obj/item/food/grown/shand + seed = /obj/item/seeds/shand + name = "shand" + desc = "A handful of shand leaves, the leaves are oily and smell like a laboratory." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "shand" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/bromine + tastes = list("chemicals" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/surik.dm b/modular_doppler/xenoarch/code/modules/hydroponics/surik.dm new file mode 100644 index 0000000000000..a70eeb6ebdce5 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/surik.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/surik + name = "surik seed pack" + desc = "These seeds grow into surik plants. Said to contain the very essence of Indecipheres." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "surik" + species = "surik" + plantname = "Surik Plant" + product = /obj/item/food/grown/surik + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "surik-stage" + growthstages = 4 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/fire_resistance) + reagents_add = list(/datum/reagent/brimdust = 0.1, /datum/reagent/medicine/omnizine/godblood = 0.1, /datum/reagent/wittel = 0.1) + +/obj/item/food/grown/surik + seed = /obj/item/seeds/surik + name = "surik" + desc = "A shimmering surik crystal. The center of the gem thrums with volcanic activity." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "surik" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/brimdust + tastes = list("crystals" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/telriis.dm b/modular_doppler/xenoarch/code/modules/hydroponics/telriis.dm new file mode 100644 index 0000000000000..23a3994e0b0ae --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/telriis.dm @@ -0,0 +1,29 @@ +/obj/item/seeds/telriis + name = "telriis seed pack" + desc = "These seeds grow into telriis plants. A distant relative of milkweed, this grass can actually be juiced into milk." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "telriis" + species = "telriis" + plantname = "Telriis Plant" + product = /obj/item/food/grown/telriis + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "telriis-stage" + growthstages = 4 + plant_icon_offset = 7 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/invasive) + reagents_add = list(/datum/reagent/consumable/milk = 0.1, /datum/reagent/consumable/soymilk = 0.1, /datum/reagent/consumable/korta_milk) + +/obj/item/food/grown/telriis + seed = /obj/item/seeds/telriis + name = "telriis" + desc = "A sheaf of telris, it can be ground or juiced into a milky liquid." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "telriis" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/consumable/coconut_milk + tastes = list("milk" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/thaadra.dm b/modular_doppler/xenoarch/code/modules/hydroponics/thaadra.dm new file mode 100644 index 0000000000000..550a9f25b1b27 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/thaadra.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/thaadra + name = "thaadra seed pack" + desc = "These seeds grow into thaadra plants. A strange flower full of unique medicines and silver." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "thaadra" + species = "thaadra" + plantname = "Thaadra Plant" + product = /obj/item/food/grown/thaadra + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "thaadra-stage" + growthstages = 4 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/preserved) + reagents_add = list(/datum/reagent/silver = 0.1, /datum/reagent/medicine/sansufentanyl = 0.1, /datum/reagent/medicine/cordiolis_hepatico = 0.1) + +/obj/item/food/grown/thaadra + seed = /obj/item/seeds/thaadra + name = "thaadra" + desc = "A cluster of thaadra petals, full of niche medicinal chemicals." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "thaadra" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/silver + tastes = list("silver" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/vale.dm b/modular_doppler/xenoarch/code/modules/hydroponics/vale.dm new file mode 100644 index 0000000000000..cfd420c8c6835 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/vale.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/vale + name = "vale seed pack" + desc = "These seeds grow into vale plants. Once sold as a luxury for their unique aesthetics, after the trees suddenly combusted they were taken off of the market." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "vale" + species = "vale" + plantname = "Vale Plant" + product = /obj/item/food/grown/vale + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "vale-stage" + growthstages = 4 + genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/glow/pink) + reagents_add = list(/datum/reagent/stable_plasma = 0.1, /datum/reagent/toxin/plasma = 0.1, /datum/reagent/napalm = 0.1) + +/obj/item/food/grown/vale + seed = /obj/item/seeds/vale + name = "vale" + desc = "A cluster of vale leaves, keep away from open flames." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "vale" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/toxin/plasma + tastes = list("plasma" = 1) diff --git a/modular_doppler/xenoarch/code/modules/hydroponics/vaporsac.dm b/modular_doppler/xenoarch/code/modules/hydroponics/vaporsac.dm new file mode 100644 index 0000000000000..efa36cec89b49 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/hydroponics/vaporsac.dm @@ -0,0 +1,28 @@ +/obj/item/seeds/vaporsac + name = "vaporsac seed pack" + desc = "These seeds grow into vaporsac plants. Normally vaporsac plants spread by floating through the air and exploding, but this strand of vaporsac thankfully does not." + icon = 'modular_doppler/xenoarch/icons/seeds.dmi' + icon_state = "vaporsac" + species = "vaporsac" + plantname = "Vaporsac Plant" + product = /obj/item/food/grown/vaporsac + lifespan = 55 + endurance = 35 + yield = 5 + growing_icon = 'modular_doppler/xenoarch/icons/growing.dmi' + icon_grow = "vaporsac-stage" + growthstages = 3 + genes = list(/datum/plant_gene/trait/squash, /datum/plant_gene/trait/smoke) + reagents_add = list(/datum/reagent/nitrous_oxide = 0.1, /datum/reagent/medicine/muscle_stimulant = 0.1, /datum/reagent/medicine/coagulant = 0.1) + +/obj/item/food/grown/vaporsac + seed = /obj/item/seeds/vaporsac + name = "vaporsac" + desc = "An buoyant vaporsac, full of aerosolized chemicals." + icon = 'modular_doppler/xenoarch/icons/harvest.dmi' + icon_state = "vaporsac" + filling_color = "#FF4500" + bite_consumption_mod = 0.5 + foodtypes = FRUIT + juice_typepath = /datum/reagent/nitrous_oxide + tastes = list("sleep" = 1) diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/designs_and_tech.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/designs_and_tech.dm new file mode 100644 index 0000000000000..903bc2c129297 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/designs_and_tech.dm @@ -0,0 +1,239 @@ +#define RND_SUBCATEGORY_MACHINE_XENOARCH "/Xenoarchaeology Machinery" +#define RND_SUBCATEGORY_EQUIPMENT_XENOARCH "/Xenoarchaeology Equipment" +#define RND_SUBCATEGORY_TOOLS_XENOARCH "/Xenoarchaeology Tools" +#define RND_SUBCATEGORY_TOOLS_XENOARCH_ADVANCED "/Xenoarchaeology Tools (Advanced)" + +/datum/design/xenoarch + build_type = PROTOLATHE | AWAY_LATHE + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_CARGO + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + ) + +/datum/design/xenoarch/tool + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_XENOARCH, + ) + +/datum/design/xenoarch/tool/hammer + desc = "A hammer that can slowly remove debris on strange rocks." + +/datum/design/xenoarch/tool/hammer/cm1 + name = "Hammer (cm 1)" + id = "hammer_cm1" + build_path = /obj/item/xenoarch/hammer/cm1 + +/datum/design/xenoarch/tool/hammer/cm2 + name = "Hammer (cm 2)" + id = "hammer_cm2" + build_path = /obj/item/xenoarch/hammer/cm2 + +/datum/design/xenoarch/tool/hammer/cm3 + name = "Hammer (cm 3)" + id = "hammer_cm3" + build_path = /obj/item/xenoarch/hammer/cm3 + +/datum/design/xenoarch/tool/hammer/cm4 + name = "Hammer (cm 4)" + id = "hammer_cm4" + build_path = /obj/item/xenoarch/hammer/cm4 + +/datum/design/xenoarch/tool/hammer/cm5 + name = "Hammer (cm 5)" + id = "hammer_cm5" + build_path = /obj/item/xenoarch/hammer/cm5 + +/datum/design/xenoarch/tool/hammer/cm6 + name = "Hammer (cm 6)" + id = "hammer_cm6" + build_path = /obj/item/xenoarch/hammer/cm6 + +/datum/design/xenoarch/tool/hammer/cm10 + name = "Hammer (cm 10)" + id = "hammer_cm10" + build_path = /obj/item/xenoarch/hammer/cm10 + +/datum/design/xenoarch/tool/brush + name = "Brush" + desc = "A brush that can slowly remove debris on a strange rock." + id = "xenoarch_brush" + build_path = /obj/item/xenoarch/brush + +/datum/design/xenoarch/tool/xeno_tape + name = "Xenoarch Tape Measure" + desc = "A tape measure used to measure the dug depth of strange rocks." + id = "xenoarch_tapemeasure" + build_path = /obj/item/xenoarch/tape_measure + +/datum/design/xenoarch/tool/scanner + name = "Xenoarch Handheld Scanner" + desc = "A handheld scanner for strange rocks, capable of tagging a \"safe\" depth and maximum depth." + id = "xenoarch_handscanner" + build_path = /obj/item/xenoarch/handheld_scanner + +/datum/design/xenoarch/tool/advanced + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, + ) + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_XENOARCH_ADVANCED, + ) + + +/datum/design/xenoarch/tool/advanced/scanner + name = "Xenoarch Advanced Handheld Scanner" + id = "xenoarch_handscanner_adv" + build_path = /obj/item/xenoarch/handheld_scanner/advanced + +/datum/design/xenoarch/tool/advanced/recoverer + name = "Xenoarch Handheld Recoverer" + desc = "A device with the capabilities to recover items lost due to time." + id = "xenoarch_handrecoverer" + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + ) + // rebalance material req after first repath/categorization? + build_path = /obj/item/xenoarch/handheld_recoverer + +/datum/design/xenoarch/tool/advanced/adv_hammer + name = "Advanced Hammer" + desc = "A hammer that can quickly remove debris on a strange rock and change digging depths." + id = "xenoarch_adv_hammer" + build_path = /obj/item/xenoarch/hammer/adv + +/datum/design/xenoarch/tool/advanced/adv_brush + name = "Advanced Brush" + desc = "A brush that can quickly remove debris on a strange rock." + id = "xenoarch_adv_brush" + build_path = /obj/item/xenoarch/brush/adv + +/datum/design/xenoarch/equipment + // everything under this except the adv bag feels redundant because cloth/leather are there too + // but i guess we'll burn that bridge another time + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_XENOARCH, + ) + +/datum/design/xenoarch/equipment/bag + name = "Xenoarchaeology Bag" + desc = "A bag that can hold about twenty-five strange rocks." + id = "xenoarch_bag" + build_path = /obj/item/storage/bag/xenoarch + +/datum/design/xenoarch/equipment/belt + name = "Xenoarchaeology Belt" + desc = "A belt that can hold all of the essential tools for xenoarchaeology." + id = "xenoarch_belt" + build_path = /obj/item/storage/belt/utility/xenoarch + +/datum/design/xenoarch/equipment/bag_adv + name = "Advanced Xenoarch Bag" + desc = "A bag that can hold about fifty strange rocks." + id = "xenoarch_bag_adv" + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, + ) + // i kinda hate how this requires diamond, but this is supposed to be a fix pr, burn the gbp on it later + build_path = /obj/item/storage/bag/xenoarch/adv + +/datum/design/board/xenoarch + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_XENOARCH, + ) + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/board/xenoarch/researcher + name = "Machine Design (Xenoarch Researcher)" + desc = "Allows for the construction of circuit boards used to build a new xenoarch researcher." + id = "xeno_researcher" + build_path = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_researcher + +/datum/design/board/xenoarch/scanner + name = "Machine Design (Xenoarch Scanner)" + desc = "Allows for the construction of circuit boards used to build a new xenoarch scanner." + id = "xeno_scanner" + build_path = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_scanner + +/datum/design/board/xenoarch/recoverer + name = "Machine Design (Xenoarch Recoverer)" + desc = "Allows for the construction of circuit boards used to build a new xenoarch recoverer." + id = "xeno_recoverer" + build_path = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_recoverer + +/datum/design/board/xenoarch/digger + name = "Machine Design (Xenoarch Digger)" + desc = "Allows for the construction of circuit boards used to build a new xenoarch digger." + id = "xeno_digger" + build_path = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_digger + +/datum/techweb_node/basic_xenoarch + id = TECHWEB_NODE_XENOARCH_BASIC + starting_node = TRUE + display_name = "Basic Xenoarchaeology" + description = "The basic designs of xenoarchaeology." + design_ids = list( + "hammer_cm1", + "hammer_cm2", + "hammer_cm3", + "hammer_cm4", + "hammer_cm5", + "hammer_cm6", + "hammer_cm10", + "xenoarch_brush", + "xenoarch_tapemeasure", + "xenoarch_handscanner", + ) + +/datum/techweb_node/xenoarch_storage + id = TECHWEB_NODE_XENOARCH_STORAGE + display_name = "Xenoarchaeology Storage" + description = "When dealing with xenoarchaeology, one may need storage." + prereq_ids = list(TECHWEB_NODE_XENOARCH_BASIC) + design_ids = list( + "xenoarch_belt", + "xenoarch_bag", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) + +/datum/techweb_node/xenoarch_machines + id = TECHWEB_NODE_XENOARCH_MACHINES + display_name = "Xenoarchaeology Machines" + description = "Sometimes, xenoarchaeology can be time consuming, perhaps machines can help?" + prereq_ids = list(TECHWEB_NODE_XENOARCH_BASIC) + design_ids = list( + "xeno_researcher", + "xeno_scanner", + "xeno_recoverer", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) + +/datum/techweb_node/adv_xenoarch + id = TECHWEB_NODE_XENOARCH_ADVANCED + display_name = "Advanced Xenoarchaeology" + description = "After some time, those tools we used have become antiquated-- we need an upgrade." + prereq_ids = list(TECHWEB_NODE_XENOARCH_BASIC, TECHWEB_NODE_XENOARCH_MACHINES, TECHWEB_NODE_XENOARCH_STORAGE) + design_ids = list( + "xenoarch_adv_hammer", + "xenoarch_adv_brush", + "xenoarch_bag_adv", + "xenoarch_handscanner_adv", + "xenoarch_handrecoverer", + "xeno_digger", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS) + required_experiments = list(/datum/experiment/scanning/points/xenoarch) + +/datum/experiment/scanning/points/xenoarch + name = "Advanced Xenoarchaeology Tools" + description = "It is possible to create even more advanced tools for xenoarchaeoloy." + required_points = 10 + required_atoms = list( + /obj/item/xenoarch/useless_relic = 1, + /obj/item/xenoarch/broken_item = 2, + ) diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/glassblowing_integration.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/glassblowing_integration.dm new file mode 100644 index 0000000000000..d9813652ed136 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/glassblowing_integration.dm @@ -0,0 +1,18 @@ +/obj/item/glassblowing/magnifying_glass + name = "magnifying glass" + desc = "A tool that, with the assistance of a magnifying lens, allows you to view what is small." + icon_state = "magnifying_glass" + +/obj/item/glassblowing/magnifying_glass/examine(mob/user) + . = ..() + if(HAS_TRAIT(user, TRAIT_XENOARCH_QUALIFIED)) + . += span_notice("You can use [src] on useless relics to realize their full potential!") + +/datum/crafting_recipe/magnifying_glass + name = "Magnifying Glass" + result = /obj/item/glassblowing/magnifying_glass + reqs = list( + /obj/item/stack/sheet/mineral/wood = 1, + /obj/item/glassblowing/glass_lens = 1, + ) + category = CAT_EQUIPMENT diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/strange_rock.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/strange_rock.dm new file mode 100644 index 0000000000000..25908df022a79 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/strange_rock.dm @@ -0,0 +1,305 @@ +#define DIG_UNDEFINED (1<<0) //when the strange rock is dug by an item with no dig depth. +#define DIG_DELETE (1<<1) //when the strange rock is dug too deep and gets destroyed in the process. +#define DIG_ROCK (1<<2) //when the strange rock is just dug, with no additional effects. + +#define BRUSH_DELETE (2<<0) //when the strange rock is brushed and the strange rock gets destroyed. +#define BRUSH_UNCOVER (2<<1) //when the strange rock is brushed and the strange rock reveals what it held. +#define BRUSH_NONE (2<<2) //when the strange rock is brushed, with no additional effects. + +#define REWARD_ONE 1 +#define REWARD_TWO 2 +#define REWARD_THREE 3 + +/obj/item/xenoarch/strange_rock + name = "strange rock" + desc = "A mysterious, strange rock that has the potential to have a wonderful item. Also possible for it to have our disposed garbage." + icon_state = "rock" + + ///The max depth a strange rock can be + var/max_depth + ///The depth away/subtracted from the max_depth + var/safe_depth + //The depth chosen between the max and the max - safe depth + var/item_depth + //The depth that has been currently dug + var/dug_depth = 0 + //The item that is hidden within the strange rock + var/hidden_item + ///Whether the item has been measured, revealing the dug depth + var/measured = FALSE + ///Whether the ite has been scanned, revealing the max and safe depth + var/scanned = FALSE + ///Whether the ite has been advance scanned, revealing the true depth + var/adv_scanned = FALSE + ///The scan state for when encountering the strange rock ore in mining. + var/scan_state = "rock_Strange" + ///The tier of the item that was chosen, 1-100 then 1-3 + var/choose_tier + +/obj/item/xenoarch/strange_rock/Initialize(mapload) + . = ..() + create_item() + create_depth() + +/obj/item/xenoarch/strange_rock/examine(mob/user) + . = ..() + . += span_notice("[scanned ? "This item has been scanned. Max Depth: [max_depth] cm. Safe Depth: [safe_depth] cm." : "This item has not been scanned."]") + if(adv_scanned) + . += span_notice("The item depth is [item_depth] cm.") + . += span_notice("[measured ? "This item has been measured. Dug Depth: [dug_depth]." : "This item has not been measured."]") + if(measured && dug_depth > item_depth) + . += span_warning("The rock is crumbling, even just brushing it will destroy it!") + +/obj/item/xenoarch/strange_rock/proc/create_item() + choose_tier = rand(1,100) + switch(choose_tier) + if(1 to 60) + hidden_item = pick_weight(GLOB.tier1_reward) + choose_tier = REWARD_ONE + if(61 to 87) + hidden_item = pick_weight(GLOB.tier2_reward) + choose_tier = REWARD_TWO + if(88 to 100) + hidden_item = pick_weight(GLOB.tier3_reward) + choose_tier = REWARD_THREE + +/obj/item/xenoarch/strange_rock/proc/create_depth() + max_depth = rand(21, (22 * choose_tier)) + safe_depth = rand(1, 10) + item_depth = rand((max_depth - safe_depth), max_depth) + dug_depth = rand(0, 10) + +//returns true if the strange rock is measured +/obj/item/xenoarch/strange_rock/proc/get_measured() + if(measured) + return FALSE + measured = TRUE + return TRUE + +//returns true if the strange rock is scanned +/obj/item/xenoarch/strange_rock/proc/get_scanned(use_advanced = FALSE) + if(scanned) + if(!adv_scanned && use_advanced) + adv_scanned = TRUE + return TRUE + return FALSE + scanned = TRUE + if(use_advanced) + adv_scanned = TRUE + return TRUE + +/obj/item/xenoarch/strange_rock/proc/try_dig(dig_amount) + if(!dig_amount) + return DIG_UNDEFINED + dug_depth += dig_amount + if(dug_depth > item_depth) + qdel(src) + return DIG_DELETE + return DIG_ROCK + +/obj/item/xenoarch/strange_rock/proc/try_uncover() + if(dug_depth > item_depth) + qdel(src) + return BRUSH_DELETE + if(dug_depth == item_depth) + new hidden_item(get_turf(src)) + qdel(src) + return BRUSH_UNCOVER + try_dig(1) + return BRUSH_NONE + +/obj/item/xenoarch/strange_rock/attackby(obj/item/I, mob/living/user, params) + . = ..() + if(istype(I, /obj/item/xenoarch/hammer)) + var/obj/item/xenoarch/hammer/xeno_hammer = I + to_chat(user, span_notice("You begin carefully using your hammer.")) + if(!do_after(user, xeno_hammer.dig_speed, target = src)) + to_chat(user, span_warning("You interrupt your careful planning, damaging the rock in the process!")) + dug_depth += rand(1,5) + return + switch(try_dig(xeno_hammer.dig_amount)) + if(DIG_UNDEFINED) + message_admins("Tell coders something broke with xenoarch hammers and dig amount.") + return + if(DIG_DELETE) + to_chat(user, span_warning("The rock crumbles, leaving nothing behind.")) + return + if(DIG_ROCK) + to_chat(user, span_notice("You successfully dig around the item.")) + + if(istype(I, /obj/item/xenoarch/brush)) + var/obj/item/xenoarch/brush/xeno_brush = I + to_chat(user, span_notice("You begin carefully using your brush.")) + if(!do_after(user, xeno_brush.dig_speed, target = src)) + to_chat(user, span_warning("You interrupt your careful planning, damaging the rock in the process!")) + dug_depth += rand(1,5) + return + switch(try_uncover()) + if(BRUSH_DELETE) + to_chat(user, span_warning("The rock crumbles, leaving nothing behind.")) + return + if(BRUSH_UNCOVER) + to_chat(user, span_notice("You successfully brush around the item, fully revealing the item!")) + return + if(BRUSH_NONE) + to_chat(user, span_notice("You brush around the item, but it wasn't revealed... hammer some more.")) + + if(istype(I, /obj/item/xenoarch/tape_measure)) + to_chat(user, span_notice("You begin carefully using your measuring tape.")) + if(!do_after(user, 4 SECONDS, target = src)) + to_chat(user, span_warning("You interrupt your careful planning, damaging the rock in the process!")) + dug_depth += rand(1,5) + return + if(get_measured()) + to_chat(user, span_notice("You successfully attach a holo measuring tape to the strange rock; the strange rock will now report its dug depth always!")) + return + to_chat(user, span_warning("The strange rock was already marked with a holo measuring tape.")) + + if(istype(I, /obj/item/xenoarch/handheld_scanner)) + var/obj/item/xenoarch/handheld_scanner/item_scanner = I + to_chat(user, span_notice("You begin to scan [src] using [item_scanner].")) + if(!do_after(user, item_scanner.scanning_speed, target = src)) + to_chat(user, span_warning("You interrupt your scanning, damaging the rock in the process!")) + dug_depth += rand(1,5) + return + if(get_scanned(item_scanner.scan_advanced)) + to_chat(user, span_notice("You successfully attach a holo scanning module to the strange rock; the strange rock will now report its depth information always!")) + if(adv_scanned) + to_chat(user, span_notice("The rock's item depth is being reported!")) + return + to_chat(user, span_warning("The strange rock was already marked with a holo scanning module.")) + +//turfs +/turf/closed/mineral/strange_rock + mineralAmt = 1 + icon = MAP_SWITCH('modular_doppler/xenoarch/icons/smoothrocks.dmi', 'modular_doppler/xenoarch/icons/mining.dmi') + scan_state = "rock_Strange" + mineralType = /obj/item/xenoarch/strange_rock + +/turf/closed/mineral/strange_rock/volcanic + turf_type = /turf/open/misc/asteroid/basalt/lava_land_surface + baseturfs = /turf/open/misc/asteroid/basalt/lava_land_surface + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + defer_change = TRUE + +/turf/closed/mineral/random/volcanic + turf_type = /turf/open/misc/asteroid/basalt/lava_land_surface + baseturfs = /turf/open/misc/asteroid/basalt/lava_land_surface + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + defer_change = TRUE + mineralChance = 10 + +/turf/closed/mineral/random/volcanic/mineral_chances() + return list( + /obj/item/stack/ore/iron = 40, + /obj/item/stack/ore/plasma = 20, + /obj/item/stack/ore/silver = 12, + /obj/item/stack/ore/titanium = 11, + /obj/item/stack/ore/gold = 10, + /turf/closed/mineral/strange_rock/volcanic = 10, + /obj/item/stack/ore/uranium = 5, + /turf/closed/mineral/gibtonite/volcanic = 4, + /obj/item/stack/ore/diamond = 1, + /obj/item/stack/ore/bluespace_crystal = 1 + ) + +/turf/closed/mineral/strange_rock/ice + icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'modular_doppler/xenoarch/icons/mining.dmi') + icon_state = "icerock_strange" + base_icon_state = "icerock_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER + turf_type = /turf/open/misc/asteroid/snow/ice + baseturfs = /turf/open/misc/asteroid/snow/ice + initial_gas_mix = FROZEN_ATMOS + defer_change = TRUE + +/turf/closed/mineral/strange_rock/ice/icemoon + turf_type = /turf/open/misc/asteroid/snow/ice/icemoon + baseturfs = /turf/open/misc/asteroid/snow/ice/icemoon + initial_gas_mix = ICEMOON_DEFAULT_ATMOS + +/turf/closed/mineral/random/snow/mineral_chances() + return list( + /obj/item/stack/ore/iron = 40, + /obj/item/stack/ore/plasma = 20, + /obj/item/stack/ore/silver = 12, + /obj/item/stack/ore/titanium = 11, + /obj/item/stack/ore/gold = 10, + /turf/closed/mineral/strange_rock/ice/icemoon = 10, + /obj/item/stack/ore/uranium = 5, + /turf/closed/mineral/gibtonite/ice/icemoon = 4, + /obj/item/stack/ore/diamond = 1, + /obj/item/stack/ore/bluespace_crystal = 1, + ) + +/turf/closed/mineral/random/snow/underground + baseturfs = /turf/open/misc/asteroid/snow/icemoon + // abundant ore + mineralChance = 20 + +/turf/closed/mineral/random/snow/underground/mineral_chances() + return list( + /obj/item/stack/ore/silver = 24, + /obj/item/stack/ore/titanium = 22, + /obj/item/stack/ore/gold = 20, + /obj/item/stack/ore/plasma = 20, + /obj/item/stack/ore/iron = 20, + /obj/item/stack/ore/uranium = 10, + /turf/closed/mineral/strange_rock/ice/icemoon = 10, + /turf/closed/mineral/gibtonite/ice/icemoon = 8, + /obj/item/stack/ore/diamond = 4, + /obj/item/stack/ore/bluespace_crystal = 2, + /obj/item/stack/ore/bananium = 1, + ) + +//small gibonite fix +/turf/closed/mineral/gibtonite/asteroid + icon = MAP_SWITCH('modular_doppler/xenoarch/icons/mining.dmi', 'icons/turf/mining.dmi') + icon_state = "redrock_Gibonite_inactive" + base_icon_state = "red_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER + turf_type = /turf/open/misc/asteroid + baseturfs = /turf/open/misc/asteroid + initial_gas_mix = OPENTURF_DEFAULT_ATMOS + defer_change = TRUE + +/turf/closed/mineral/strange_rock/asteroid + icon = MAP_SWITCH('modular_doppler/xenoarch/icons/mining.dmi', 'icons/turf/mining.dmi') + icon_state = "redrock_strange" + base_icon_state = "red_wall" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER + turf_type = /turf/open/misc/asteroid + baseturfs = /turf/open/misc/asteroid + initial_gas_mix = OPENTURF_DEFAULT_ATMOS + defer_change = TRUE + +/turf/closed/mineral/random/stationside/asteroid/rockplanet + initial_gas_mix = OPENTURF_DEFAULT_ATMOS + turf_type = /turf/open/misc/asteroid + mineralChance = 30 + +/turf/closed/mineral/random/stationside/asteroid/rockplanet/mineral_chances() + return list( + /obj/item/stack/ore/iron = 40, + /obj/item/stack/ore/plasma = 20, + /obj/item/stack/ore/silver = 12, + /obj/item/stack/ore/titanium = 11, + /obj/item/stack/ore/gold = 10, + /turf/closed/mineral/strange_rock/asteroid = 10, + /obj/item/stack/ore/uranium = 5, + /turf/closed/mineral/gibtonite/asteroid = 4, + /obj/item/stack/ore/bluespace_crystal = 1, + /obj/item/stack/ore/diamond = 1, + ) + +#undef DIG_UNDEFINED +#undef DIG_DELETE +#undef DIG_ROCK + +#undef BRUSH_DELETE +#undef BRUSH_UNCOVER +#undef BRUSH_NONE + +#undef REWARD_ONE +#undef REWARD_TWO +#undef REWARD_THREE diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_item.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_item.dm new file mode 100644 index 0000000000000..f70d56f492051 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_item.dm @@ -0,0 +1,255 @@ +//useless relics +/obj/item/xenoarch/useless_relic + name = "useless relic" + desc = "A useless relic that can be redeemed for cargo or research points." + ///Used to spawn the same relic + var/magnified_number + +/obj/item/xenoarch/useless_relic/Initialize(mapload) + . = ..() + magnified_number = rand(1,8) + icon_state = "useless[magnified_number]" + +/obj/item/xenoarch/useless_relic/attackby(obj/item/attacking_item, mob/user, params) + if(istype(attacking_item, /obj/item/glassblowing/magnifying_glass)) + if(istype(src, /obj/item/xenoarch/useless_relic/magnified)) + balloon_alert(user, "already magnified!") + return + if(!HAS_TRAIT(user, TRAIT_XENOARCH_QUALIFIED)) + balloon_alert(user, "needs training!") // it was very tempting to replace this with "skill issue" + return + balloon_alert(user, "starting analysis!") + if(!do_after(user, 5 SECONDS, target = src)) + balloon_alert(user, "stand still!") + return + loc.balloon_alert(user, "magnified!") + spawn_magnified(magnified_number) + return + return ..() + +#define ANCIENT_URN 1 +#define ANCIENT_BOWL 2 +#define ANCIENT_CROWN 3 +#define ANCIENT_COIL 4 +#define ANCIENT_LIGHT 5 +#define ANCIENT_CUP 6 +#define ANCIENT_UTENSILS 7 +#define ANCIENT_R_BOWL 8 + +/obj/item/xenoarch/useless_relic/proc/spawn_magnified(type_number) + var/obj/item/xenoarch/useless_relic/magnified/new_item = new(get_turf(src)) + new_item.icon_state = "useless[type_number]" + switch(type_number) + if(ANCIENT_URN) + new_item.name = "ancient urn" + new_item.desc = "This useless relic is an ancient urn that dates from around [rand(400,600)] years ago. \ + It has made of a ceramic substance and is clearly crumbling at the edges. Perhaps it has ashes \ + of someone from long ago." + if(ANCIENT_BOWL) + new_item.name = "ancient bowl" + new_item.desc = "This useless relic is an ancient bowl that dates from around [rand(400,600)] years ago. \ + It is made of a bronze alloy and is dented, with some scratches along the inside. Perhaps it could \ + have had DNA of someone from long ago." + if(ANCIENT_CROWN) + new_item.name = "ancient crown" + new_item.desc = "This useless relic is an ancient crown that dates from around [rand(900,1100)] years ago. \ + It is made from some unknown alloy, with small inlets that would have been used for jewels. Perhaps if we \ + look around, we could find some of those old jewels." + if(ANCIENT_COIL) + new_item.name = "ancient coil" + new_item.desc = "This useless relic is an ancient coil that dates from around [rand(400,600)] years ago. \ + It is made of iron and copper. It has some burn marks around the iron rod. Perhaps later on, we could \ + use it for some machines." + if(ANCIENT_LIGHT) + new_item.name = "ancient light" + new_item.desc = "This useless relic is an ancient light that dates from around [rand(400,600)] years ago. \ + It is made of iron and has glass shards around it. It has dents on the iron and clear damage from misuse. \ + Perhaps we could research this later on to see how the ancients made lights." + if(ANCIENT_CUP) + new_item.name = "ancient cup" + new_item.desc = "This useless relic is an ancient cup that dates from around [rand(900,1100)] years ago. \ + It is made of hardened stone. There are small cracks all along the surface, as long as chisel marks. \ + Perhaps it will give insight into the ancient's eating and drinking habits." + if(ANCIENT_UTENSILS) + new_item.name = "ancient utensils" + new_item.desc = "These useless relics are ancient utensils that dates from around [rand(900,1100)] years ago. \ + It is made of hardened stone. There are small cracks all along the surface, as long as chisel marks. \ + Perhaps it will give insight into the ancient's eating and drinking habits." + if(ANCIENT_R_BOWL) + new_item.name = "ancient rock bowl" + new_item.desc = "This useless relic is an ancient rock bowl that dates from around [rand(900,1100)] years ago. \ + It is made of hardened stone. There are small cracks all along the surface, as long as chisel marks. \ + Perhaps it will give insight into the ancient's eating and drinking habits." + new_item.desc += " Whatever use it possibly had in the past, its only use now is either as a museum piece, or being sold off to collectors via the Cargo shuttle." + qdel(src) + +#undef ANCIENT_URN +#undef ANCIENT_BOWL +#undef ANCIENT_CROWN +#undef ANCIENT_COIL +#undef ANCIENT_LIGHT +#undef ANCIENT_CUP +#undef ANCIENT_UTENSILS +#undef ANCIENT_R_BOWL + +/obj/item/xenoarch/useless_relic/magnified + name = "magnified useless relic" + desc = "A useless relic that can be exported through Cargo. Has been magnified." + +/datum/export/xenoarch/useless_relic + cost = CARGO_CRATE_VALUE * 3 //600 + unit_name = "useless relic" + export_types = list(/obj/item/xenoarch/useless_relic) + include_subtypes = FALSE + k_elasticity = 0 + +/datum/export/xenoarch/broken_item + cost = CARGO_CRATE_VALUE*5 + unit_name = "broken object" + export_types = list(/obj/item/xenoarch/broken_item) + include_subtypes = TRUE + k_elasticity = 0 + +/datum/export/xenoarch/useless_relic/magnified + cost = CARGO_CRATE_VALUE * 6 //1200 + unit_name = "magnified useless relic" + export_types = list(/obj/item/xenoarch/useless_relic/magnified) + include_subtypes = FALSE + +//broken items +/obj/item/xenoarch/broken_item + name = "broken item" + desc = "An item that has been damaged, destroyed for quite some time. It is possible to recover it." + +/obj/item/xenoarch/broken_item/tech + name = "broken tech" + icon_state = "recover_tech" + +/obj/item/xenoarch/broken_item/weapon + name = "broken weapon" + icon_state = "recover_weapon" + +/obj/item/xenoarch/broken_item/illegal + name = "broken unknown object" + icon_state = "recover_illegal" + +/obj/item/xenoarch/broken_item/alien + name = "broken unknown object" + icon_state = "recover_illegal" + +/obj/item/xenoarch/broken_item/plant + name = "withered plant" + desc = "A plant that is long past its prime. It is possible to recover it." + icon_state = "recover_plant" + +/obj/item/xenoarch/broken_item/animal + name = "preserved animal carcass" + desc = "An animal that is long past its prime. It is possible to recover it. Can be swabbed to recover its original animal's remnant DNA." + icon_state = "recover_animal" + +/obj/item/xenoarch/broken_item/animal/Initialize(mapload) + . = ..() + var/pick_celltype = pick(CELL_LINE_TABLE_BEAR, + CELL_LINE_TABLE_BLOBBERNAUT, + CELL_LINE_TABLE_BLOBSPORE, + CELL_LINE_TABLE_CARP, + CELL_LINE_TABLE_CAT, + CELL_LINE_TABLE_CHICKEN, + CELL_LINE_TABLE_COCKROACH, + CELL_LINE_TABLE_CORGI, + CELL_LINE_TABLE_COW, + CELL_LINE_TABLE_MOONICORN, + CELL_LINE_TABLE_GELATINOUS, + CELL_LINE_TABLE_GRAPE, + CELL_LINE_TABLE_MEGACARP, + CELL_LINE_TABLE_MOUSE, + CELL_LINE_TABLE_PINE, + CELL_LINE_TABLE_PUG, + CELL_LINE_TABLE_SLIME, + CELL_LINE_TABLE_SNAKE, + CELL_LINE_TABLE_VATBEAST, + CELL_LINE_TABLE_NETHER, + CELL_LINE_TABLE_GLUTTON, + CELL_LINE_TABLE_FROG, + CELL_LINE_TABLE_WALKING_MUSHROOM, + CELL_LINE_TABLE_QUEEN_BEE, + CELL_LINE_TABLE_MEGA_ARACHNID) + AddElement(/datum/element/swabable, pick_celltype, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + +/obj/item/xenoarch/broken_item/clothing + name = "petrified clothing" + desc = "A piece of clothing that has long since lost its beauty." + icon_state = "recover_clothing" + + +//circuit boards +/obj/item/circuitboard/machine/xenoarch_machine + greyscale_colors = CIRCUIT_COLOR_SCIENCE + req_components = list( + /datum/stock_part/micro_laser = 1, + /datum/stock_part/matter_bin = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stack/sheet/glass = 2, + ) + needs_anchored = TRUE + +/obj/item/circuitboard/machine/xenoarch_machine/xenoarch_researcher + name = "Xenoarch Researcher (Machine Board)" + build_path = /obj/machinery/xenoarch/researcher + +/obj/item/circuitboard/machine/xenoarch_machine/xenoarch_scanner + name = "Xenoarch Scanner (Machine Board)" + build_path = /obj/machinery/xenoarch/scanner + +/obj/item/circuitboard/machine/xenoarch_machine/xenoarch_recoverer + name = "Xenoarch Recoverer (Machine Board)" + build_path = /obj/machinery/xenoarch/recoverer + +/obj/item/circuitboard/machine/xenoarch_machine/xenoarch_digger + name = "Xenoarch Digger (Machine Board)" + build_path = /obj/machinery/xenoarch/digger + +/obj/item/paper/fluff/xenoarch_guide + name = "xenoarchaeology guide - MUST READ" + default_raw_text = {"
Xenoarchaeology Guide

\ + Let's start right from the beginning: what is Xenoarchaeology?
\ + Great question! Xenoarchaeology is the study of ancient foreign bodies that are trapped within strange rocks.
\ + Your goal as a xenoarchaeologist is to find these strange rocks and unearth the secrets that are held within.
\ + You will find that these rocks are plentiful throughout the astronomical bodies that we typically orbit.
\ +
\ + Tools of the Trade
\ +
\ + There are plenty of tools that are required (and some just for the quality of life for the xenoarchaeologist).
\ + There are the hammers, the brushes, the tape, the belt, the bag, the handheld machines, and the machines.
\ + In this line of work, the brushes and hammers will be the bread and butter.
\ + They will allow you to unearth the foreign bodies held within the strange rocks.
\ + The hammers (with varying depths) allow you to reach the depths in a faster manner than the brushes.
\ + The brushes allow you to uncover the items within the proper depths without damaging it.
\ + The tape will allow you to tag the strange rock with the current depth. Continue to examine the rock for updates.
\ + The belt will allow you to store your mobile/handheld tools for easy access.
\ + The bag will allow you to store and automatically pickup strange rocks that you find lying on the floor.
\ + The handheld machines allow you to not have to be stuck at the machines. There are only handheld scanners and recoverers.
\ + The Scanner is a machine which allows you to tag the strange rock with its max and safe depth.
\ + The Researcher is a machine that allows you to compile/condense relics and items into larger strange artifacts.
\ + The Recoverer is a machine that allows you to recover long lost objects from broken items.
\ +
\ + The Process
\ +
\ + 1) Find yourself a strange rock out in the wilderness.
\ + 2) Go back (or stay) to the xenoarchaeology labratory.
\ + 3) Process the rock in the scanner (or use the handheld scanner).
\ + 4) Use the measuring tape on the rock.
\ + 5) Subtract the safe depth (SD) from the max depth (MD).
\ + 5a) QUESTION: What is the depth you dig to when the MD is 50 and the SD is 16?
\ + ANSWER: 34. Just make sure to not dig 34 as there will be previous depth involved.
\ + 6) Subtract the current depth (CD) from the answer to step 5.
\ + 7) Use the hammers to dig the answer to step 6.
\ + 8) Once you've reached the answer to step 5, use the brush until you reveal the item.
\ + 9) Enjoy the use of your unearthed secret!
\ + 9a) If it is a useless relic, sell it or use it in the Researcher for a surprise.
\ + 9b) If it is a broken item, sell it or use it in the Recoverer for a surprise.
\ +
\ + I hope this has been helpful and I wish you great success!
\ +
\ + - KB
\ + Director of Xenoarchaeological Studies"} diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_machine.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_machine.dm new file mode 100644 index 0000000000000..e15a3468845f0 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_machine.dm @@ -0,0 +1,313 @@ +// Researcher, Scanner, Recoverer, and Digger + +/obj/machinery/xenoarch + icon = 'modular_doppler/xenoarch/icons/xenoarch_machines.dmi' + density = TRUE + layer = BELOW_OBJ_LAYER + use_power = IDLE_POWER_USE + idle_power_usage = 100 + pass_flags = PASSTABLE + /// the item that holds everything + var/obj/item/storage_unit + ///how long between each process + var/process_speed = 10 SECONDS + COOLDOWN_DECLARE(process_delay) + +/obj/machinery/xenoarch/Initialize(mapload) + . = ..() + storage_unit = new /obj/item(src) + +/obj/machinery/xenoarch/Destroy() + QDEL_NULL(storage_unit) + return ..() + +/obj/machinery/xenoarch/RefreshParts() + . = ..() + var/efficiency = -2 //to allow t1 parts to not change the base speed + for(var/datum/stock_part/stockpart in component_parts) + efficiency += stockpart.tier + + process_speed = initial(process_speed) - (efficiency) + +/obj/machinery/xenoarch/process() + if(machine_stat & (NOPOWER|BROKEN) || !anchored) + COOLDOWN_RESET(src, process_delay) //if you are broken or no power (or not anchored), you aren't allowed to progress! + return + + if(!COOLDOWN_FINISHED(src, process_delay)) + return + + COOLDOWN_START(src, process_delay, process_speed) + xenoarch_process() + +/obj/machinery/xenoarch/proc/xenoarch_process() + return + +/obj/machinery/xenoarch/wrench_act(mob/living/user, obj/item/tool) + . = ..() + + if(default_unfasten_wrench(user, tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/xenoarch/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + + toggle_panel_open() + to_chat(user, span_notice("You [panel_open ? "open":"close"] the maintenance panel of [src].")) + tool.play_tool_sound(src) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/xenoarch/crowbar_act(mob/living/user, obj/item/tool) + . = ..() + + if(default_deconstruction_crowbar(tool)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/xenoarch/researcher + name = "xenoarch researcher" + desc = "A machine that is used to condense strange rocks, useless relics, and broken objects into bigger artifacts." + icon_state = "researcher" + circuit = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_researcher + /// the amount of research that is currently done + var/current_research = 0 + /// the max amount of value we can have + var/max_research = 300 + /// the value of each accepted item + var/list/accepted_types = list( + /obj/item/xenoarch/strange_rock = 1, + /obj/item/xenoarch/useless_relic = 5, + /obj/item/xenoarch/useless_relic/magnified = 10, + /obj/item/xenoarch/broken_item = 10, + ) + +/obj/machinery/xenoarch/researcher/examine(mob/user) + . = ..() + + . += span_notice("
[current_research]/[max_research] research available.") + . += span_notice("L-Click to insert items or take out all the strange rocks. R-Click to use research points.") + +/obj/machinery/xenoarch/researcher/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/storage/bag/xenoarch)) + for(var/obj/strange_rocks in weapon.contents) + strange_rocks.forceMove(storage_unit) + + balloon_alert(user, "rocks inserted!") + return + + if(is_type_in_list(weapon, accepted_types)) + weapon.forceMove(storage_unit) + balloon_alert(user, "item inserted!") + return + + return ..() + +/obj/machinery/xenoarch/researcher/attack_hand(mob/living/user, list/modifiers) + . = ..() + var/choice = tgui_input_list(user, "Remove the rocks from [src]?", "Rock Removal", list("Yes", "No")) + if(choice != "Yes") + return + var/turf/src_turf = get_turf(src) + for(var/obj/item/removed_item in storage_unit.contents) + removed_item.forceMove(src_turf) + + balloon_alert(user, "items removed!") + +/obj/machinery/xenoarch/researcher/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + var/turf/src_turf = get_turf(src) + var/choice = tgui_input_list(user, "Choose which reward you would like!", "Reward Choice", list("Lavaland Chest (150)", "Anomalous Crystal (150)", "Bepis Tech (100)")) + if(!choice) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + switch(choice) + if("Lavaland Chest (150)") + if(current_research < 150) + balloon_alert(user, "insufficient research!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + current_research -= 150 + new /obj/structure/closet/crate/necropolis/tendril(src_turf) + + if("Anomalous Crystal (150)") + if(current_research < 150) + balloon_alert(user, "insufficient research!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + current_research -= 150 + var/list/choices = subtypesof(/obj/machinery/anomalous_crystal) - /obj/machinery/anomalous_crystal/theme_warp + var/random_crystal = pick(choices) + new random_crystal(src_turf) + + if("Bepis Tech (100)") + if(current_research < 100) + balloon_alert(user, "insufficient research!") + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + current_research -= 100 + new /obj/item/disk/design_disk/bepis/remove_tech(src_turf) + + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/machinery/xenoarch/researcher/xenoarch_process() + if(length(storage_unit.contents) <= 0) + return + + if(current_research >= max_research) + return + + var/obj/item/first_item = storage_unit.contents[1] + var/reward_attempt = accepted_types[first_item.type] + current_research = min(current_research + reward_attempt, 300) + qdel(first_item) + +/obj/machinery/xenoarch/scanner + name = "xenoarch scanner" + desc = "A machine that is used to scan strange rocks, making it easier to extract the item inside." + icon_state = "scanner" + circuit = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_scanner + +/obj/machinery/xenoarch/scanner/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/storage/bag/xenoarch)) + for(var/obj/item/xenoarch/strange_rock/chosen_rocks in weapon.contents) + chosen_rocks.get_scanned() + + balloon_alert(user, "scan complete!") + return + + if(istype(weapon, /obj/item/xenoarch/strange_rock)) + var/obj/item/xenoarch/strange_rock/chosen_rock + if(chosen_rock.get_scanned()) + balloon_alert(user, "scan complete!") + return + + to_chat(user, span_warning("[chosen_rock] was unable to be scanned, perhaps it was already scanned?")) + return + + return ..() + +/obj/machinery/xenoarch/recoverer + name = "xenoarch recoverer" + desc = "A machine that will recover the damaged, destroyed objects found within the strange rocks." + icon_state = "recoverer" + circuit = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_recoverer + +/obj/machinery/xenoarch/recoverer/examine(mob/user) + . = ..() + . += span_notice("
L-Click to remove all items inside [src].") + +/obj/machinery/xenoarch/recoverer/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/xenoarch/broken_item)) + weapon.forceMove(storage_unit) + balloon_alert(user, "item inserted!") + return + + return ..() + +/obj/machinery/xenoarch/recoverer/attack_hand(mob/living/user, list/modifiers) + var/choice = tgui_input_list(user, "Remove the broken items from [src]?", "Item Removal", list("Yes", "No")) + if(choice != "Yes") + return + + var/turf/src_turf = get_turf(src) + for(var/obj/item/removed_item in storage_unit.contents) + removed_item.forceMove(src_turf) + + balloon_alert(user, "items removed!") + +/obj/machinery/xenoarch/recoverer/xenoarch_process() + var/turf/src_turf = get_turf(src) + if(length(storage_unit.contents) <= 0) + return + + var/obj/item/content_obj = storage_unit.contents[1] + if(!istype(content_obj, /obj/item/xenoarch/broken_item)) + qdel(content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/tech)) + var/spawn_item = pick_weight(GLOB.tech_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/weapon)) + var/spawn_item = pick_weight(GLOB.weapon_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/illegal)) + var/spawn_item = pick_weight(GLOB.illegal_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/alien)) + var/spawn_item = pick_weight(GLOB.alien_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/plant)) + var/spawn_item = pick_weight(GLOB.plant_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/clothing)) + var/spawn_item = pick_weight(GLOB.clothing_reward) + recover_item(spawn_item, content_obj) + return + + if(istype(content_obj, /obj/item/xenoarch/broken_item/animal)) + var/spawn_item + for(var/looptime in 1 to rand(1,4)) + spawn_item = pick_weight(GLOB.animal_reward) + new spawn_item(src_turf) + + recover_item(spawn_item, content_obj) + return + +/obj/machinery/xenoarch/recoverer/proc/recover_item(obj/insert_obj, obj/delete_obj) + var/src_turf = get_turf(src) + new insert_obj(src_turf) + playsound(src, 'sound/machines/click.ogg', 50, TRUE) + qdel(delete_obj) + +/obj/machinery/xenoarch/digger + name = "xenoarch digger" + desc = "A machine that is used to slowly uncover items within strange rocks." + icon_state = "digger" + circuit = /obj/item/circuitboard/machine/xenoarch_machine/xenoarch_digger + +/obj/machinery/xenoarch/digger/examine(mob/user) + . = ..() + . += span_notice("
L-Click to remove all items inside [src].") + +/obj/machinery/xenoarch/digger/attackby(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/storage/bag/xenoarch)) + for(var/obj/strange_rocks in weapon.contents) + strange_rocks.forceMove(storage_unit) + + balloon_alert(user, "rocks inserted!") + return + + if(istype(weapon, /obj/item/xenoarch/strange_rock)) + weapon.forceMove(src) + balloon_alert(user, "rock inserted!") + return + +/obj/machinery/xenoarch/digger/attack_hand(mob/living/user, list/modifiers) + var/choice = tgui_input_list(user, "Remove the rocks from [src]?", "Rock Removal", list("Yes", "No")) + if(choice != "Yes") + return + + var/turf/src_turf = get_turf(src) + for(var/obj/item/removed_item in storage_unit.contents) + removed_item.forceMove(src_turf) + + balloon_alert(user, "items removed!") + +/obj/machinery/xenoarch/digger/xenoarch_process() + var/turf/src_turf = get_turf(src) + if(length(storage_unit.contents) <= 0) + return + + var/obj/item/xenoarch/strange_rock/first_item = storage_unit.contents[1] + new first_item.hidden_item(src_turf) + qdel(first_item) diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_reward.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_reward.dm new file mode 100644 index 0000000000000..afed67fefe23b --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_reward.dm @@ -0,0 +1,97 @@ +GLOBAL_LIST_INIT(tier1_reward, list( + /obj/item/xenoarch/useless_relic = 5, + /obj/item/stack/sheet/sinew = 1, + /obj/item/stack/sheet/animalhide/goliath_hide = 1, + /obj/item/stack/sheet/bone = 1, + /obj/item/organ/internal/monster_core/regenerative_core/legion = 1, +)) + +GLOBAL_LIST_INIT(tier2_reward, list( + /obj/item/xenoarch/broken_item/tech = 1, + /obj/item/xenoarch/broken_item/plant = 1, + /obj/item/xenoarch/broken_item/clothing = 1, + /obj/item/xenoarch/broken_item/animal = 1, + /obj/item/xenoarch/useless_relic = 5, +)) + +GLOBAL_LIST_INIT(tier3_reward, list( + /obj/item/xenoarch/broken_item/weapon = 3, + /obj/item/xenoarch/broken_item/illegal = 1, + /obj/item/xenoarch/broken_item/alien = 1, + /obj/item/stack/spacecash/c10000 = 1, +)) + + + +GLOBAL_LIST_INIT(tech_reward, list( + /obj/item/pipe_dispenser = 1, + /obj/item/construction/rcd = 1, + /obj/item/anomaly_neutralizer = 1, + /obj/item/megaphone = 1, + /obj/item/bodybag/bluespace = 1, + /obj/item/relic = 1, + /obj/item/raw_anomaly_core/random = 1, + /obj/item/bag_of_holding_inert = 1, + /obj/item/construction/plumbing = 1, + /obj/item/mmi/posibrain = 1, + /obj/item/storage/portable_chem_mixer = 1, +)) + +GLOBAL_LIST_INIT(weapon_reward, list( + /obj/item/spear/bonespear = 6, + /obj/item/gun/ballistic/bow/tribalbow/ashen = 2, + /obj/item/ammo_casing/arrow/ash = 1, + /obj/item/claymore/cutlass = 1, + /obj/item/gun/ballistic/automatic/pistol = 1, + /obj/item/shield/riot = 1, + /obj/item/shield/roman = 1, + /obj/item/pneumatic_cannon = 1, + /obj/item/gun/syringe/rapidsyringe = 1, +)) + +GLOBAL_LIST_INIT(clothing_reward, list( + /obj/item/clothing/neck/necklace/translator = 1, + /obj/item/clothing/head/helmet/gladiator = 1, + /obj/item/clothing/under/costume/gladiator/ash_walker = 1, +)) + +GLOBAL_LIST_INIT(illegal_reward, list( + /obj/item/stack/telecrystal = 1, + /obj/item/storage/box/rndboards = 1, +)) + +GLOBAL_LIST_INIT(plant_reward, list( + /obj/item/food/grown/gelthi = 1, + /obj/item/seeds/random = 1, + /obj/item/food/grown/amauri = 1, + /obj/item/food/grown/jurlmah = 1, + /obj/item/food/grown/nofruit = 1, + /obj/item/food/grown/shand = 1, + /obj/item/food/grown/surik = 1, + /obj/item/food/grown/telriis = 1, + /obj/item/food/grown/thaadra = 1, + /obj/item/food/grown/vale = 1, + /obj/item/food/grown/vaporsac = 1, +)) + +GLOBAL_LIST_INIT(animal_reward, list( + /obj/item/stack/sheet/sinew = 1, + /obj/item/stack/sheet/animalhide/goliath_hide = 1, + /obj/item/stack/sheet/bone = 1, + /obj/item/organ/internal/monster_core/regenerative_core/legion = 1, +)) + +GLOBAL_LIST_INIT(alien_reward, list( + /obj/item/wrench/abductor = 1, + /obj/item/wirecutters/abductor = 1, + /obj/item/weldingtool/abductor = 1, + /obj/item/screwdriver/abductor = 1, + /obj/item/crowbar/abductor = 1, + /obj/item/multitool/abductor = 1, + /obj/item/scalpel/alien = 1, + /obj/item/hemostat/alien = 1, + /obj/item/retractor/alien = 1, + /obj/item/circular_saw/alien = 1, + /obj/item/surgicaldrill/alien = 1, + /obj/item/cautery/alien = 1, +)) diff --git a/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_tool.dm b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_tool.dm new file mode 100644 index 0000000000000..f29654aad4043 --- /dev/null +++ b/modular_doppler/xenoarch/code/modules/research/xenoarch/xenoarch_tool.dm @@ -0,0 +1,306 @@ +/obj/item/xenoarch + name = "parent dev item" + icon = 'modular_doppler/xenoarch/icons/xenoarch_items.dmi' + +// HAMMERS + +/obj/item/xenoarch/hammer + name = "parent dev item" + desc = "A hammer that can be used to remove dirt from strange rocks." + tool_behaviour = TOOL_HAMMER + var/dig_amount = 1 + var/dig_speed = 1 SECONDS + var/advanced = FALSE + +/obj/item/xenoarch/hammer/examine(mob/user) + . = ..() + if(advanced) + . += span_notice("This is an advanced hammer. It can change its digging depth from 1 to 30. Click to change depth.") + . += span_notice("Current Digging Depth: [dig_amount]cm") + +/obj/item/xenoarch/hammer/attack_self(mob/user, modifiers) + . = ..() + if(!advanced) + to_chat(user, span_warning("This is not an advanced hammer, it cannot change its digging depth.")) + return + var/user_choice = input(user, "Choose the digging depth. 1 to 30", "Digging Depth Selection") as null|num + if(!user_choice) + dig_amount = 1 + dig_speed = 1 + return + if(dig_amount <= 0) + dig_amount = 1 + dig_speed = 1 + return + var/round_dig = round(user_choice) + if(round_dig >= 30) + dig_amount = 30 + dig_speed = 30 + return + dig_amount = round_dig + dig_speed = round_dig * 0.5 + to_chat(user, span_notice("You change the hammer's digging depth to [round_dig]cm.")) + +/obj/item/xenoarch/hammer/cm1 + name = "hammer (1cm)" + icon_state = "hammer1" + dig_amount = 1 + dig_speed = 0.5 SECONDS + +/obj/item/xenoarch/hammer/cm2 + name = "hammer (2cm)" + icon_state = "hammer2" + dig_amount = 2 + dig_speed = 1 SECONDS + +/obj/item/xenoarch/hammer/cm3 + name = "hammer (3cm)" + icon_state = "hammer3" + dig_amount = 3 + dig_speed = 1.5 SECONDS + +/obj/item/xenoarch/hammer/cm4 + name = "hammer (4cm)" + icon_state = "hammer4" + dig_amount = 4 + dig_speed = 2 SECONDS + +/obj/item/xenoarch/hammer/cm5 + name = "hammer (5cm)" + icon_state = "hammer5" + dig_amount = 5 + dig_speed = 2.5 SECONDS + +/obj/item/xenoarch/hammer/cm6 + name = "hammer (6cm)" + icon_state = "hammer6" + dig_amount = 6 + dig_speed = 3 SECONDS + +/obj/item/xenoarch/hammer/cm10 + name = "hammer (10cm)" + icon_state = "hammer10" + dig_amount = 10 + dig_speed = 5 SECONDS + +/obj/item/xenoarch/hammer/adv + name = "advanced hammer" + icon_state = "adv_hammer" + dig_amount = 1 + dig_speed = 1 + advanced = TRUE + +// BRUSHES + +/obj/item/xenoarch/brush + name = "brush" + desc = "A brush that is used to uncover the secrets of the past from strange rocks." + var/dig_speed = 3 SECONDS + icon_state = "brush" + +/obj/item/xenoarch/brush/adv + name = "advanced brush" + dig_speed = 0.5 SECONDS + icon_state = "adv_brush" + +// MISC. + +/obj/item/xenoarch/tape_measure + name = "measuring tape" + desc = "A measuring tape specifically produced to measure the depth that has been dug into strange rocks." + icon_state = "tape" + +/obj/item/xenoarch/handheld_scanner + name = "handheld scanner" + desc = "A handheld scanner for strange rocks. It tags the depths to the rock." + icon_state = "scanner" + var/scanning_speed = 3 SECONDS + var/scan_advanced = FALSE + +/obj/item/xenoarch/handheld_scanner/advanced + name = "advanced handheld scanner" + icon_state = "adv_scanner" + scanning_speed = 0.5 SECONDS + scan_advanced = TRUE + +/obj/item/xenoarch/handheld_recoverer + name = "handheld recoverer" + desc = "An item that has the capabilities to recover items lost due to time." + icon_state = "recoverer" + +/obj/item/xenoarch/handheld_recoverer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + var/turf/target_turf = get_turf(interacting_with) + . = ITEM_INTERACT_SUCCESS + if(istype(interacting_with, /obj/item/xenoarch/broken_item/tech)) + var/spawn_item = pick_weight(GLOB.tech_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/weapon)) + var/spawn_item = pick_weight(GLOB.weapon_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/illegal)) + var/spawn_item = pick_weight(GLOB.illegal_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/alien)) + var/spawn_item = pick_weight(GLOB.alien_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/plant)) + var/spawn_item = pick_weight(GLOB.plant_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/clothing)) + var/spawn_item = pick_weight(GLOB.clothing_reward) + new spawn_item(target_turf) + qdel(interacting_with) + return + if(istype(interacting_with, /obj/item/xenoarch/broken_item/animal)) + var/spawn_item + var/turf/src_turf = get_turf(src) + for(var/looptime in 1 to rand(1,4)) + spawn_item = pick_weight(GLOB.animal_reward) + new spawn_item(src_turf) + qdel(interacting_with) + return + return NONE + +/obj/item/storage/belt/utility/xenoarch + name = "xenoarch toolbelt" + desc = "Holds tools." + icon = 'modular_doppler/xenoarch/icons/xenoarch_items.dmi' + icon_state = "xenoarch_belt" + content_overlays = FALSE + custom_premium_price = PAYCHECK_CREW * 2 + +/obj/item/storage/belt/utility/xenoarch/Initialize(mapload) + . = ..() + atom_storage.max_total_storage = 100 + atom_storage.max_slots = 15 + atom_storage.set_holdable(list( + /obj/item/xenoarch/hammer, + /obj/item/xenoarch/brush, + /obj/item/xenoarch/tape_measure, + /obj/item/xenoarch/handheld_scanner, + /obj/item/xenoarch/handheld_recoverer, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/mining_scanner, + /obj/item/gps + )) + +/obj/item/storage/bag/xenoarch + name = "xenoarch mining satchel" + desc = "This little bugger can be used to store and transport strange rocks." + icon = 'modular_doppler/xenoarch/icons/xenoarch_items.dmi' + icon_state = "satchel" + worn_icon_state = "satchel" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + var/insert_speed = 1 SECONDS + var/mob/listeningTo + var/range = null + + var/spam_protection = FALSE //If this is TRUE, the holder won't receive any messages when they fail to pick up ore through crossing it + +/obj/item/storage/bag/xenoarch/Initialize(mapload) + . = ..() + atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC + atom_storage.allow_quick_empty = TRUE + atom_storage.max_total_storage = 1000 + atom_storage.max_slots = 25 + atom_storage.numerical_stacking = FALSE + atom_storage.can_hold = typecacheof(list(/obj/item/xenoarch/strange_rock)) + +/obj/item/storage/bag/xenoarch/equipped(mob/user) + . = ..() + if(listeningTo == user) + return + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(pickup_rocks)) + listeningTo = user + +/obj/item/storage/bag/xenoarch/dropped(mob/user) + . = ..() + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + listeningTo = null + +/obj/item/storage/bag/xenoarch/proc/pickup_rocks(mob/living/user) + SIGNAL_HANDLER + var/show_message = FALSE + var/turf/tile = user.loc + if (!isturf(tile)) + return + + if(atom_storage) + for(var/A in tile) + if (!is_type_in_typecache(A, atom_storage.can_hold)) + continue + else if(atom_storage.attempt_insert(A, user)) + show_message = TRUE + else + if(!spam_protection) + to_chat(user, span_warning("Your [name] is full and can't hold any more!")) + spam_protection = TRUE + continue + if(show_message) + playsound(user, SFX_RUSTLE, 50, TRUE) + user.visible_message(span_notice("[user] scoops up the rocks beneath [user.p_them()]."), \ + span_notice("You scoop up the rocks beneath you with your [name].")) + spam_protection = FALSE + +/obj/item/storage/bag/xenoarch/adv + name = "advanced xenoarch mining satchel" + icon_state = "adv_satchel" + insert_speed = 0.1 SECONDS + +/obj/item/storage/bag/xenoarch/adv/Initialize(mapload) + . = ..() + atom_storage.max_slots = 50 + +/obj/structure/closet/xenoarch + name = "xenoarchaeology equipment locker" + icon_state = "science" + +/obj/structure/closet/xenoarch/PopulateContents() + . = ..() + new /obj/item/xenoarch/hammer/cm1(src) + new /obj/item/xenoarch/hammer/cm2(src) + new /obj/item/xenoarch/hammer/cm3(src) + new /obj/item/xenoarch/hammer/cm4(src) + new /obj/item/xenoarch/hammer/cm5(src) + new /obj/item/xenoarch/hammer/cm6(src) + new /obj/item/xenoarch/hammer/cm10(src) + new /obj/item/xenoarch/brush(src) + new /obj/item/xenoarch/tape_measure(src) + new /obj/item/xenoarch/handheld_scanner(src) + new /obj/item/storage/bag/xenoarch(src) + new /obj/item/storage/belt/utility/xenoarch(src) + new /obj/item/t_scanner/adv_mining_scanner(src) + new /obj/item/pickaxe(src) + new /obj/item/paper/fluff/xenoarch_guide(src) + +/obj/structure/closet/xenoarch/tribal_version + name = "dusty xenoarchaeology equipment locker" + +/obj/structure/closet/xenoarch/tribal_version/PopulateContents() + . = ..() + new /obj/item/xenoarch/handheld_recoverer(src) + +/obj/item/skillchip/xenoarch_magnifier + name = "R3T3N-T1VE skillchip" + desc = "This biochip integrates with user's brain to enable the mastery of a specific skill. Consult certified Nanotrasen neurosurgeon before use. \ + There's a little face etched into the back of the skillchip, with buck teeth and goofy-looking glasses." + auto_traits = list(TRAIT_XENOARCH_QUALIFIED) + skill_name = "Xenoarchaeological Analysis" + skill_description = "Allows for the more thorough magnification and notice of details on freshly-excavated xenoarchaeological garbage." + skill_icon = "magnifying-glass" + activate_message = span_notice("You feel the gleaned knowledge of a xenoarchaeological digsite internship reveal itself to your mind.") + deactivate_message = span_notice("The knowledge from a digsite internship fades away into jumbled coffee orders from ungrateful supervisors.") diff --git a/modular_doppler/xenoarch/icons/growing.dmi b/modular_doppler/xenoarch/icons/growing.dmi new file mode 100644 index 0000000000000..4bab24c4fc374 Binary files /dev/null and b/modular_doppler/xenoarch/icons/growing.dmi differ diff --git a/modular_doppler/xenoarch/icons/harvest.dmi b/modular_doppler/xenoarch/icons/harvest.dmi new file mode 100644 index 0000000000000..1bdf1e3ea825b Binary files /dev/null and b/modular_doppler/xenoarch/icons/harvest.dmi differ diff --git a/modular_doppler/xenoarch/icons/mining.dmi b/modular_doppler/xenoarch/icons/mining.dmi new file mode 100644 index 0000000000000..c0ff56deb3b22 Binary files /dev/null and b/modular_doppler/xenoarch/icons/mining.dmi differ diff --git a/modular_doppler/xenoarch/icons/ore_visuals.dmi b/modular_doppler/xenoarch/icons/ore_visuals.dmi new file mode 100644 index 0000000000000..ec45ede83c4a3 Binary files /dev/null and b/modular_doppler/xenoarch/icons/ore_visuals.dmi differ diff --git a/modular_doppler/xenoarch/icons/seeds.dmi b/modular_doppler/xenoarch/icons/seeds.dmi new file mode 100644 index 0000000000000..e1aa7739f7360 Binary files /dev/null and b/modular_doppler/xenoarch/icons/seeds.dmi differ diff --git a/modular_doppler/xenoarch/icons/smoothrocks.dmi b/modular_doppler/xenoarch/icons/smoothrocks.dmi new file mode 100644 index 0000000000000..a546f33946017 Binary files /dev/null and b/modular_doppler/xenoarch/icons/smoothrocks.dmi differ diff --git a/modular_doppler/xenoarch/icons/xenoarch_area.dmi b/modular_doppler/xenoarch/icons/xenoarch_area.dmi new file mode 100644 index 0000000000000..bc892c06fddc5 Binary files /dev/null and b/modular_doppler/xenoarch/icons/xenoarch_area.dmi differ diff --git a/modular_doppler/xenoarch/icons/xenoarch_items.dmi b/modular_doppler/xenoarch/icons/xenoarch_items.dmi new file mode 100644 index 0000000000000..99d8b2bf76b1a Binary files /dev/null and b/modular_doppler/xenoarch/icons/xenoarch_items.dmi differ diff --git a/modular_doppler/xenoarch/icons/xenoarch_machines.dmi b/modular_doppler/xenoarch/icons/xenoarch_machines.dmi new file mode 100644 index 0000000000000..f3183bec26eea Binary files /dev/null and b/modular_doppler/xenoarch/icons/xenoarch_machines.dmi differ diff --git a/modular_doppler/xenoarch/readme.md b/modular_doppler/xenoarch/readme.md new file mode 100644 index 0000000000000..a624339da7115 --- /dev/null +++ b/modular_doppler/xenoarch/readme.md @@ -0,0 +1,24 @@ +## Title: Xenoarchaeology + +MODULE ID: XENOARCH + +### Description: + +Xenoarch: Archaeology based around foreign bodies. Dig up encased relics of a time long past (or dig up our garbage on accident). + +### TG Proc Changes: + +N/A + +### Defines: + +N/A + +### Included files: + +- `code\__DEFINES\~doppler_defines\traits.dm` xenoarch magnification trait +- `code\__DEFINES\~doppler_defines\techweb_nodes.dm` techweb nodes + +### Credits: + +Jake Park diff --git a/tgstation.dme b/tgstation.dme index c5e2feed37199..f36213a170dc7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -397,10 +397,19 @@ #include "code\__DEFINES\traits\macros.dm" #include "code\__DEFINES\traits\sources.dm" #include "code\__DEFINES\~doppler_defines\enterprise_resource_planning.dm" +#include "code\__DEFINES\~doppler_defines\is_helpers.dm" #include "code\__DEFINES\~doppler_defines\keybindings.dm" #include "code\__DEFINES\~doppler_defines\living.dm" #include "code\__DEFINES\~doppler_defines\loadout.dm" +#include "code\__DEFINES\~doppler_defines\obj_flags_doppler.dm" +#include "code\__DEFINES\~doppler_defines\reagent_forging_tools.dm" +#include "code\__DEFINES\~doppler_defines\reskin_defines.dm" +#include "code\__DEFINES\~doppler_defines\signals.dm" +#include "code\__DEFINES\~doppler_defines\sound.dm" +#include "code\__DEFINES\~doppler_defines\span.dm" #include "code\__DEFINES\~doppler_defines\species.dm" +#include "code\__DEFINES\~doppler_defines\techweb_nodes.dm" +#include "code\__DEFINES\~doppler_defines\traits.dm" #include "code\__HELPERS\_auxtools_api.dm" #include "code\__HELPERS\_dreamluau.dm" #include "code\__HELPERS\_lists.dm" @@ -515,6 +524,7 @@ #include "code\__HELPERS\paths\sssp.dm" #include "code\__HELPERS\sorts\helpers.dm" #include "code\__HELPERS\sorts\sort_instance.dm" +#include "code\__HELPERS\~doppler_helpers\global_lists.dm" #include "code\_globalvars\_regexes.dm" #include "code\_globalvars\admin.dm" #include "code\_globalvars\arcade.dm" @@ -559,6 +569,9 @@ #include "code\_globalvars\lists\xenobiology.dm" #include "code\_globalvars\traits\_traits.dm" #include "code\_globalvars\traits\admin_tooling.dm" +#include "code\_globalvars\~doppler_globalvars\bitfields.dm" +#include "code\_globalvars\~doppler_globalvars\objective.dm" +#include "code\_globalvars\~doppler_globalvars\religion.dm" #include "code\_js\byjax.dm" #include "code\_js\menus.dm" #include "code\_onclick\adjacent.dm" @@ -6362,6 +6375,16 @@ #include "interface\fonts\spess_font.dm" #include "interface\fonts\tiny_unicode.dm" #include "interface\fonts\vcr_osd_mono.dm" +#include "modular_doppler\advanced_reskin\code\advanced_reskin.dm" +#include "modular_doppler\cryosleep\code\admin.dm" +#include "modular_doppler\cryosleep\code\ai.dm" +#include "modular_doppler\cryosleep\code\config.dm" +#include "modular_doppler\cryosleep\code\cryo_console_return.dm" +#include "modular_doppler\cryosleep\code\cryopod.dm" +#include "modular_doppler\cryosleep\code\job.dm" +#include "modular_doppler\cryosleep\code\jobs.dm" +#include "modular_doppler\cryosleep\code\mind.dm" +#include "modular_doppler\cryosleep\code\mood.dm" #include "modular_doppler\customization\code\accessory_overrides.dm" #include "modular_doppler\customization\code\accessory_overrides_lizard.dm" #include "modular_doppler\customization\code\accessory_overrides_moth.dm" @@ -6382,9 +6405,58 @@ #include "modular_doppler\enterprise_resource_planning\code\breasts.dm" #include "modular_doppler\enterprise_resource_planning\code\erp_prefs.dm" #include "modular_doppler\face_mouse_preferences\code\face_mouse_pref.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\clothing.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\clothing_vendor.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\greyscale_config.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\language.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\map_items.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\objects.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\organs.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\pet_commands.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\smelling_salts.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\spawner.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\special_metals.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\species.dm" +#include "modular_doppler\hearthkin\primitive_catgirls\code\translator.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\big_mortar.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\cauldron.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\cookware.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\cutting_board.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\millstone.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\plant_bag.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\stone_griddle.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\stone_oven.dm" +#include "modular_doppler\hearthkin\primitive_cooking_additions\code\stone_stove.dm" +#include "modular_doppler\hearthkin\primitive_production\code\antfarm.dm" +#include "modular_doppler\hearthkin\primitive_production\code\ceramics.dm" +#include "modular_doppler\hearthkin\primitive_production\code\farming.dm" +#include "modular_doppler\hearthkin\primitive_production\code\glassblowing.dm" +#include "modular_doppler\hearthkin\primitive_production\code\misc.dm" +#include "modular_doppler\hearthkin\primitive_production\code\production_skill.dm" +#include "modular_doppler\hearthkin\primitive_production\code\wormfarm.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\fencing.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\fuelwell.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\furniture.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\railroad.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\storage_structures.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\totally_thatch_roof.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\wall_torch.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\windows.dm" +#include "modular_doppler\hearthkin\primitive_structures\code\wooden_ladder.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\crafting.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\recipes.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\ammo\caseless\arrow.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\ammo\reusable\arrow.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\weapons\bow.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\weapons\shield.dm" +#include "modular_doppler\hearthkin\tribal_extended\code\weapons\sword.dm" #include "modular_doppler\icspawn\cconsultant_items.dm" #include "modular_doppler\icspawn\observer_spawn.dm" #include "modular_doppler\icspawn\spell.dm" +#include "modular_doppler\indicators\code\combat_indicator.dm" +#include "modular_doppler\indicators\code\emote_popup.dm" +#include "modular_doppler\indicators\code\sealed.dm" +#include "modular_doppler\indicators\code\ssd_indicator.dm" #include "modular_doppler\languages\language_datums.dm" #include "modular_doppler\loadout_categories\loadout_checkers.dm" #include "modular_doppler\loadout_categories\categories\belts.dm" @@ -6399,18 +6471,73 @@ #include "modular_doppler\loadout_categories\categories\pockets.dm" #include "modular_doppler\loadout_categories\categories\shoes.dm" #include "modular_doppler\loadout_categories\categories\weapons.dm" +#include "modular_doppler\modular_antagonist\code\antag_datum.dm" #include "modular_doppler\modular_cosmetics\code\jacket_pockets.dm" +#include "modular_doppler\modular_cosmetics\code\hands\rings.dm" #include "modular_doppler\modular_cosmetics\code\neck\collar.dm" +#include "modular_doppler\modular_cosmetics\code\storage\rings.dm" #include "modular_doppler\modular_cosmetics\code\suits\jacket.dm" #include "modular_doppler\modular_cosmetics\GAGS\greyscale_configs_neck.dm" +#include "modular_doppler\modular_crafting\code\crafting_extended.dm" +#include "modular_doppler\modular_crafting\code\sheet_types.dm" +#include "modular_doppler\modular_food_drinks_and_chems\chemistry_reagents.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\alcohol reagents.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drink_reagents.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drinks.dm" +#include "modular_doppler\modular_food_drinks_and_chems\food_and_drinks\drinks_recipes.dm" +#include "modular_doppler\modular_mob_spawn\code\mob_spawn.dm" +#include "modular_doppler\modular_sounds\code\sounds.dm" +#include "modular_doppler\modular_traits\code\neutral.dm" +#include "modular_doppler\modular_traits\code\organs.dm" +#include "modular_doppler\obj_flags_doppler\code\objs.dm" #include "modular_doppler\pixel_shift\living.dm" #include "modular_doppler\pixel_shift\living_movement.dm" #include "modular_doppler\pixel_shift\code\pixel_shift_component.dm" #include "modular_doppler\pixel_shift\code\pixel_shift_keybind.dm" #include "modular_doppler\pixel_shift\code\pixel_shift_mob.dm" +#include "modular_doppler\reagent_forging\code\anvil.dm" +#include "modular_doppler\reagent_forging\code\centrifuge.dm" +#include "modular_doppler\reagent_forging\code\crafting_bench.dm" +#include "modular_doppler\reagent_forging\code\crafting_bench_recipes.dm" +#include "modular_doppler\reagent_forging\code\forge.dm" +#include "modular_doppler\reagent_forging\code\forge_clothing.dm" +#include "modular_doppler\reagent_forging\code\forge_items.dm" +#include "modular_doppler\reagent_forging\code\forge_recipes.dm" +#include "modular_doppler\reagent_forging\code\forge_weapons.dm" +#include "modular_doppler\reagent_forging\code\reagent_component.dm" +#include "modular_doppler\reagent_forging\code\seedmesh.dm" +#include "modular_doppler\reagent_forging\code\smith_skill.dm" +#include "modular_doppler\reagent_forging\code\tool_override.dm" +#include "modular_doppler\reagent_forging\code\water_basin.dm" +#include "modular_doppler\religion\code\chaplain.dm" +#include "modular_doppler\religion\code\mind.dm" +#include "modular_doppler\religion\code\religious_sects.dm" #include "modular_doppler\sprite_accessories\code\hair.dm" +#include "modular_doppler\stone\code\ore_veins.dm" +#include "modular_doppler\stone\code\stone.dm" #include "modular_doppler\tableflip\tableflip.dm" +#include "modular_doppler\vending_machines\code\vendor_containers.dm" +#include "modular_doppler\vending_machines\code\vendor_food.dm" +#include "modular_doppler\vending_machines\code\vendor_snacks.dm" +#include "modular_doppler\vending_machines\code\vendors.dm" #include "modular_doppler\wargaming\code\game_kit.dm" #include "modular_doppler\wargaming\code\holograms.dm" #include "modular_doppler\wargaming\code\projectors.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\amauri.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\gelthi.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\jurlmah.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\nofruit.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\shand.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\surik.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\telriis.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\thaadra.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\vale.dm" +#include "modular_doppler\xenoarch\code\modules\hydroponics\vaporsac.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\designs_and_tech.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\glassblowing_integration.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\strange_rock.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\xenoarch_item.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\xenoarch_machine.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\xenoarch_reward.dm" +#include "modular_doppler\xenoarch\code\modules\research\xenoarch\xenoarch_tool.dm" // END_INCLUDE