mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-10 07:41:16 +01:00
479ac46740
## About The Pull Request Goliaths no longer instantly stun the mobs they hit with their tentacle for full 10 seconds, instead leashing them to the spot they were grabbed at or dragging them towards themselves. The ranged tetris-piece attack has been changed to a full cross which tethers the target preventing them from moving away more than 1 tile, while the line and the ring attacks tether the mob directly to the goliath itself and drag them in. https://github.com/user-attachments/assets/0b566a12-17ae-4a8a-97ac-5734c89c83d4 The tentacles can be manually removed after standing for 6 seconds, or by hitting them for 75 damage total (3 PKC swings, or 5 bayonet hits). They also naturally retract after 10/15 (cross/line and ring) seconds like before. Goliaths themselves have received a massive speed boost, going from 3 second movement delay to 1.2 seconds (150% buff) and no longer can friendly fire with their tentacles (and ancient goliaths have their trophy drop guaranteed) Brimdemons, lobstrocities, watchers and legions also received some changes: - Brimdemons no longer can wound with their beams (they were basically guaranteed to land a nasty burn wound with the initial blast), but are now affected by laser armor and their beam DOT (not the initial 25 burn damage upon firing) has been increased from 5 to 7. Their blasts can now be interrupted by hitting them from their side or from their back - Lobstrocities have had their retreat distance decreased from 8 to 6, making them much less likely to randomly lose aggro on the miner (their aggro range is 9 tiles, meaning that even a single tile of movement on miner's part will result in lobsters losing interest in them when on CD) - Watchers now try to stick to 3-5 (previously 4-6) tiles of distance between themselves and their target, and legions try to maintain 4-6 tiles of distance as opposed to running away completely. This should make fighting both of them more interesting and engaging, and make dealing with them during vents less cancerous. Sprites for the tentacle item and mob overlay are by thgvr from https://github.com/shiptest-ss13/Shiptest/pull/2432 ## Why It's Good For The Game Goliaths are extremely outdated in their attack design, 10 second hardstuns are basically a guaranteed death if there are any other mobs around and aren't very engaging to fight on their own as they are very easy to evade with 3 second movement delays. Change to lobstrocities should just make their AI less jank, and watcher/legion range changes should make them less slippery when fighting with PKC or zero range PKA, or during vent defense. ## Changelog 🆑 SmArtKar, thgvr balance: Goliaths no longer hardstun, but instead bind and drag their targets in with their tentacles. They are, however, much faster now. balance: Brimdemon beams can no longer wound, but deal a bit more DOT damage. Their beams can also be interrupted by hitting them from the side or back in melee. balance: Watchers and legions now try to maintain a few tiles of distance from their targets instead of retreating. fix: Lobstrocity AI should no longer sometimes flee out of their aggro range when retreating. /🆑
408 lines
15 KiB
Plaintext
408 lines
15 KiB
Plaintext
|
|
/obj
|
|
abstract_type = /obj
|
|
animate_movement = SLIDE_STEPS
|
|
speech_span = SPAN_ROBOT
|
|
var/obj_flags = CAN_BE_HIT
|
|
|
|
/// Extra examine line to describe controls, such as right-clicking, left-clicking, etc.
|
|
var/desc_controls
|
|
|
|
/// The context returned when an attack against this object doesn't deal any traditional damage to the object.
|
|
var/no_damage_feedback = "without leaving a mark"
|
|
/// Icon to use as a 32x32 preview in crafting menus and such
|
|
var/icon_preview
|
|
var/icon_state_preview
|
|
/// The vertical pixel_z offset applied when the object is anchored on a tile with table
|
|
/// Ignored when set to 0 - to avoid shifting directional wall-mounted objects above tables
|
|
var/anchored_tabletop_offset = 0
|
|
|
|
var/damtype = BRUTE
|
|
var/force = 0
|
|
|
|
/// How good a given object is at causing wounds on carbons. Higher values equal better shots at creating serious wounds.
|
|
var/wound_bonus = 0
|
|
/// If this attacks a human with no wound armor on the affected body part, add this to the wound mod. Some attacks may be significantly worse at wounding if there's even a slight layer of armor to absorb some of it vs bare flesh
|
|
var/exposed_wound_bonus = 0
|
|
|
|
/// A multiplier to an object's force when used against a structure, vehicle, machine, or robot.
|
|
/// Use [/obj/proc/get_demolition_modifier] to get the value.
|
|
var/demolition_mod = 1
|
|
|
|
/// Cached custom fire overlay
|
|
var/custom_fire_overlay
|
|
/// Particles this obj uses when burning, if any
|
|
var/burning_particles
|
|
|
|
var/drag_slowdown // Amount of multiplicative slowdown applied if pulled. >1 makes you slower, <1 makes you faster.
|
|
|
|
/// Map tag for something. Tired of it being used on snowflake items. Moved here for some semblance of a standard.
|
|
/// Next pr after the network fix will have me refactor door interactions, so help me god.
|
|
var/id_tag = null
|
|
|
|
/// The sound this obj makes when something is buckled to it
|
|
var/buckle_sound = null
|
|
|
|
/// The sound this obj makes when something is unbuckled from it
|
|
var/unbuckle_sound = null
|
|
|
|
uses_integrity = TRUE
|
|
|
|
/obj/vv_edit_var(vname, vval)
|
|
if(vname == NAMEOF(src, obj_flags))
|
|
if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/// A list of all /obj by their id_tag
|
|
GLOBAL_LIST_EMPTY(objects_by_id_tag)
|
|
|
|
/obj/Initialize(mapload)
|
|
. = ..()
|
|
|
|
check_on_table()
|
|
|
|
if (id_tag)
|
|
GLOB.objects_by_id_tag[id_tag] = src
|
|
if(opacity)
|
|
SScameras.update_visibility(src)
|
|
|
|
/obj/Destroy(force)
|
|
if(!ismachinery(src))
|
|
STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists
|
|
if(opacity)
|
|
SScameras.update_visibility(src)
|
|
SStgui.close_uis(src)
|
|
GLOB.objects_by_id_tag -= id_tag
|
|
. = ..()
|
|
|
|
/obj/attacked_by(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers)
|
|
if(!attacking_item.force)
|
|
return 0
|
|
|
|
var/demo_mod = attacking_item.get_demolition_modifier(src)
|
|
var/total_force = CALCULATE_FORCE(attacking_item, attack_modifiers) * demo_mod
|
|
var/damage = take_damage(total_force, attacking_item.damtype, MELEE, TRUE, get_dir(src, user), attacking_item.armour_penetration)
|
|
|
|
if(!LAZYACCESS(attack_modifiers, SILENCE_DEFAULT_MESSAGES))
|
|
// Sanity in case one is null for some reason
|
|
var/picked_index = rand(max(length(attacking_item.attack_verb_simple), length(attacking_item.attack_verb_continuous)))
|
|
|
|
var/message_verb_continuous = "attacks"
|
|
var/message_verb_simple = "attack"
|
|
// Sanity in case one is... longer than the other?
|
|
if (picked_index && length(attacking_item.attack_verb_continuous) >= picked_index)
|
|
message_verb_continuous = attacking_item.attack_verb_continuous[picked_index]
|
|
if (picked_index && length(attacking_item.attack_verb_simple) >= picked_index)
|
|
message_verb_simple = attacking_item.attack_verb_simple[picked_index]
|
|
|
|
if(demo_mod > 1 && prob(damage * 5))
|
|
if(HAS_TRAIT(src, TRAIT_INVERTED_DEMOLITION))
|
|
message_verb_simple = "shred"
|
|
message_verb_continuous = "shreds"
|
|
else
|
|
message_verb_simple = "pulverise"
|
|
message_verb_continuous = "pulverises"
|
|
|
|
if(demo_mod < 1)
|
|
message_verb_simple = "ineffectively " + message_verb_simple
|
|
message_verb_continuous = "ineffectively " + message_verb_continuous
|
|
|
|
user.visible_message(
|
|
span_danger("[user] [message_verb_continuous] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"),
|
|
span_danger("You [message_verb_simple] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"),
|
|
null,
|
|
COMBAT_MESSAGE_RANGE,
|
|
)
|
|
|
|
log_combat(user, src, "attacked", attacking_item)
|
|
return damage
|
|
|
|
/obj/assume_air(datum/gas_mixture/giver)
|
|
if(loc)
|
|
return loc.assume_air(giver)
|
|
else
|
|
return null
|
|
|
|
/obj/remove_air(amount)
|
|
if(loc)
|
|
return loc.remove_air(amount)
|
|
else
|
|
return null
|
|
|
|
/obj/return_air()
|
|
if(loc)
|
|
return loc.return_air()
|
|
else
|
|
return null
|
|
|
|
/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request)
|
|
//Return: (NONSTANDARD)
|
|
// null if object handles breathing logic for lifeform
|
|
// datum/air_group to tell lifeform to process using that breath return
|
|
//DEFAULT: Take air from turf to give to have mob process
|
|
|
|
if(breath_request>0)
|
|
var/datum/gas_mixture/environment = return_air()
|
|
var/breath_percentage = BREATH_VOLUME / environment.return_volume()
|
|
return remove_air(environment.total_moles() * breath_percentage)
|
|
else
|
|
return null
|
|
|
|
/obj/attack_ghost(mob/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
SEND_SIGNAL(src, COMSIG_ATOM_UI_INTERACT, user)
|
|
ui_interact(user)
|
|
|
|
/obj/singularity_pull(atom/singularity, current_size)
|
|
..()
|
|
if(move_resist == INFINITY)
|
|
return
|
|
if(!anchored || current_size >= STAGE_FIVE)
|
|
step_towards(src, singularity)
|
|
|
|
/obj/get_dumping_location()
|
|
return get_turf(src)
|
|
|
|
/obj/vv_get_dropdown()
|
|
. = ..()
|
|
VV_DROPDOWN_OPTION("", "--- /obj ---")
|
|
VV_DROPDOWN_OPTION(VV_HK_MASS_DEL_TYPE, "Delete all of type")
|
|
VV_DROPDOWN_OPTION(VV_HK_OSAY, "Object Say")
|
|
|
|
/obj/vv_do_topic(list/href_list)
|
|
. = ..()
|
|
|
|
if(!.)
|
|
return
|
|
|
|
if(href_list[VV_HK_OSAY])
|
|
return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/object_say, src)
|
|
|
|
if(href_list[VV_HK_MASS_DEL_TYPE])
|
|
if(!check_rights(R_DEBUG|R_SERVER))
|
|
return
|
|
var/action_type = tgui_alert(usr, "Strict type ([type]) or type and all subtypes?",,list("Strict type","Type and subtypes","Cancel"))
|
|
if(action_type == "Cancel" || !action_type)
|
|
return
|
|
if(tgui_alert(usr, "Are you really sure you want to delete all objects of type [type]?",,list("Yes","No")) != "Yes")
|
|
return
|
|
if(tgui_alert(usr, "Second confirmation required. Delete?",,list("Yes","No")) != "Yes")
|
|
return
|
|
var/O_type = type
|
|
switch(action_type)
|
|
if("Strict type")
|
|
var/i = 0
|
|
for(var/obj/Obj in world)
|
|
if(Obj.type == O_type)
|
|
i++
|
|
qdel(Obj)
|
|
CHECK_TICK
|
|
if(!i)
|
|
to_chat(usr, "No objects of this type exist")
|
|
return
|
|
log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) ")
|
|
message_admins(span_notice("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted) "))
|
|
if("Type and subtypes")
|
|
var/i = 0
|
|
for(var/obj/Obj in world)
|
|
if(istype(Obj,O_type))
|
|
i++
|
|
qdel(Obj)
|
|
CHECK_TICK
|
|
if(!i)
|
|
to_chat(usr, "No objects of this type exist")
|
|
return
|
|
log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ")
|
|
message_admins(span_notice("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) "))
|
|
|
|
/obj/examine(mob/user)
|
|
. = ..()
|
|
if(desc_controls)
|
|
. += span_notice(desc_controls)
|
|
|
|
/obj/examine_tags(mob/user)
|
|
. = ..()
|
|
if(obj_flags & UNIQUE_RENAME)
|
|
.["renameable"] = "Use a pen on it to rename it or change its description."
|
|
if(obj_flags & CONDUCTS_ELECTRICITY)
|
|
.["conductive"] = "It appears to be a good conductor of electricity."
|
|
|
|
/obj/analyzer_act(mob/living/user, obj/item/analyzer/tool)
|
|
if(atmos_scan(user=user, target=src, silent=FALSE))
|
|
return TRUE
|
|
return ..()
|
|
|
|
/obj/proc/plunger_act(obj/item/plunger/attacking_plunger, mob/living/user, reinforced)
|
|
return SEND_SIGNAL(src, COMSIG_PLUNGER_ACT, attacking_plunger, user, reinforced)
|
|
|
|
// Should move all contained objects to its location.
|
|
/obj/proc/dump_contents()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
CRASH("Unimplemented.")
|
|
|
|
/obj/handle_ricochet(obj/projectile/proj)
|
|
. = ..()
|
|
if(. && receive_ricochet_damage_coeff)
|
|
take_damage(proj.damage * receive_ricochet_damage_coeff, proj.damage_type, proj.armor_flag, 0, REVERSE_DIR(proj.dir), proj.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet
|
|
|
|
/// Handles exposing an object to reagents.
|
|
/obj/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
|
|
. = ..()
|
|
if(. & COMPONENT_NO_EXPOSE_REAGENTS)
|
|
return
|
|
|
|
SEND_SIGNAL(source, COMSIG_REAGENTS_EXPOSE_OBJ, src, reagents, methods, volume_modifier, show_message)
|
|
for(var/datum/reagent/reagent as anything in reagents)
|
|
var/reac_volume = reagents[reagent]
|
|
. |= reagent.expose_obj(src, reac_volume, methods, show_message)
|
|
|
|
/// Attempt to freeze this obj if possible. returns TRUE if it succeeded, FALSE otherwise.
|
|
/obj/proc/freeze()
|
|
if(HAS_TRAIT(src, TRAIT_FROZEN))
|
|
return FALSE
|
|
if(resistance_flags & FREEZE_PROOF)
|
|
return FALSE
|
|
|
|
AddElement(/datum/element/frozen)
|
|
return TRUE
|
|
|
|
/// Unfreezes this obj if its frozen
|
|
/obj/proc/unfreeze()
|
|
SEND_SIGNAL(src, COMSIG_OBJ_UNFREEZE)
|
|
|
|
/// If we can unwrench this object; returns SUCCESSFUL_UNFASTEN and FAILED_UNFASTEN, which are both TRUE, or CANT_UNFASTEN, which isn't.
|
|
/obj/proc/can_be_unfasten_wrench(mob/user, silent)
|
|
if(!(isfloorturf(loc) || isindestructiblefloor(loc)) && !anchored)
|
|
to_chat(user, span_warning("[src] needs to be on the floor to be secured!"))
|
|
return FAILED_UNFASTEN
|
|
return SUCCESSFUL_UNFASTEN
|
|
|
|
/// Try to unwrench an object in a WONDERFUL DYNAMIC WAY
|
|
/obj/proc/default_unfasten_wrench(mob/user, obj/item/wrench, time = 2 SECONDS)
|
|
if(wrench.tool_behaviour != TOOL_WRENCH)
|
|
return CANT_UNFASTEN
|
|
|
|
var/turf/ground = get_turf(src)
|
|
if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))
|
|
to_chat(user, span_notice("You fail to secure [src]."))
|
|
return CANT_UNFASTEN
|
|
var/can_be_unfasten = can_be_unfasten_wrench(user)
|
|
if(!can_be_unfasten || can_be_unfasten == FAILED_UNFASTEN)
|
|
return can_be_unfasten
|
|
if(time)
|
|
to_chat(user, span_notice("You begin [anchored ? "un" : ""]securing [src]..."))
|
|
wrench.play_tool_sound(src, 50)
|
|
var/prev_anchored = anchored
|
|
//as long as we're the same anchored state and we're either on a floor or are anchored, toggle our anchored state
|
|
if(!wrench.use_tool(src, user, time, extra_checks = CALLBACK(src, PROC_REF(unfasten_wrench_check), prev_anchored, user)))
|
|
return FAILED_UNFASTEN
|
|
if(!anchored && ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))
|
|
to_chat(user, span_notice("You fail to secure [src]."))
|
|
return CANT_UNFASTEN
|
|
to_chat(user, span_notice("You [anchored ? "un" : ""]secure [src]."))
|
|
set_anchored(!anchored)
|
|
check_on_table()
|
|
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
|
|
return SUCCESSFUL_UNFASTEN
|
|
|
|
/// For the do_after, this checks if unfastening conditions are still valid
|
|
/obj/proc/unfasten_wrench_check(prev_anchored, mob/user)
|
|
if(anchored != prev_anchored)
|
|
return FALSE
|
|
if(can_be_unfasten_wrench(user, TRUE) != SUCCESSFUL_UNFASTEN) //if we aren't explicitly successful, cancel the fuck out
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Adjusts the vertical pixel_z offset when the object is anchored on a tile with table
|
|
/obj/proc/check_on_table()
|
|
if(anchored_tabletop_offset == 0)
|
|
return
|
|
if(istype(src, /obj/structure/table))
|
|
return
|
|
|
|
if(anchored && locate(/obj/structure/table) in loc)
|
|
pixel_z = anchored_tabletop_offset
|
|
else
|
|
pixel_z = initial(pixel_z)
|
|
|
|
/obj/apply_single_mat_effect(datum/material/material, mat_amount, multiplier)
|
|
. = ..()
|
|
if(material_flags & MATERIAL_AFFECT_STATISTICS)
|
|
change_material_strength(material, mat_amount, multiplier)
|
|
|
|
/obj/remove_single_mat_effect(datum/material/material, mat_amount, multiplier)
|
|
. = ..()
|
|
if(material_flags & MATERIAL_AFFECT_STATISTICS)
|
|
change_material_strength(material, mat_amount, multiplier, remove = TRUE)
|
|
|
|
/// Changes force and throwforce of an item based on its properties. Split into a separate proc as to allow items to change theirs based on sharpness and behavior
|
|
/obj/proc/change_material_strength(datum/material/material, mat_amount, multiplier, remove = FALSE)
|
|
var/density = material.get_property(MATERIAL_DENSITY)
|
|
var/hardness = material.get_property(MATERIAL_HARDNESS)
|
|
var/flexibility = material.get_property(MATERIAL_FLEXIBILITY)
|
|
// Dense and hard objects make for good melee weapons, bendy ones not so much
|
|
var/force_mod = (1 + (density - 4) * 0.05 + (hardness - 4) * 0.05) * (1 - flexibility * 0.1)
|
|
// Hardness doesn't matter much when we're just whacking someone in the back of the head
|
|
var/throwforce_mod = 1 + (density - 4) * 0.1 - flexibility * 0.1
|
|
|
|
if (!remove)
|
|
force *= GET_MATERIAL_MODIFIER(force_mod, multiplier)
|
|
throwforce *= GET_MATERIAL_MODIFIER(throwforce_mod, multiplier)
|
|
else
|
|
force /= GET_MATERIAL_MODIFIER(force_mod, multiplier)
|
|
throwforce /= GET_MATERIAL_MODIFIER(throwforce_mod, multiplier)
|
|
|
|
/// Returns modifier to how much damage this object does to a target considered vulnerable to "demolition" (other objects, robots, etc)
|
|
/obj/proc/get_demolition_modifier(obj/target)
|
|
if(HAS_TRAIT(target, TRAIT_IGNORE_DEMOLITION))
|
|
return 1
|
|
if(HAS_TRAIT(target, TRAIT_INVERTED_DEMOLITION))
|
|
return (1 / demolition_mod)
|
|
return demolition_mod
|
|
|
|
/// Checks performed by a renamable object(through UNIQUE_RENAME obj_flag) before renaming begins.
|
|
/obj/proc/rename_checks(mob/living/user)
|
|
return TRUE
|
|
|
|
/// Returns the final name of the object, and does any side effects of renaming, such as sounds.
|
|
/obj/proc/nameformat(input, mob/living/user)
|
|
return input
|
|
|
|
/// Same as nameformat, but for desc.
|
|
/obj/proc/descformat(input, mob/living/user)
|
|
return input
|
|
|
|
/// Called when UNIQUE_RENAME is reset
|
|
/obj/proc/rename_reset()
|
|
return
|
|
|
|
/**
|
|
* Used to deliver a shock to a mob from this object
|
|
* The target must be adjacent to this object, or else the shock will fail
|
|
*
|
|
* * shocking - who are we zapping
|
|
* * chance - probability the shock succeeds
|
|
* defaults to 100 (guaranteed to shock)
|
|
* * shock_source - used for determining where to get the power to zap them.
|
|
* can be an apc, a cable, an area, a cell, or even a powernet datum
|
|
* subtypes may override this proc to pass this up to the parent
|
|
* defaults to our cell or our current area/apc
|
|
* * siemens_coeff - multiplier to how much shock is delivered
|
|
* default to a 1x modifier
|
|
*
|
|
* Returns TRUE if the shock was successfully delivered
|
|
* Returns FALSE if the shock failed for any reason
|
|
*/
|
|
/obj/proc/shock(mob/living/shocking, chance = 100, shock_source, siemens_coeff = 1)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(!isliving(shocking))
|
|
return FALSE
|
|
if(!prob(chance))
|
|
return FALSE // you lucked out, no shock for you
|
|
|
|
do_sparks(5, TRUE, src)
|
|
return electrocute_mob(shocking, shock_source || get_cell() || get_area(src), src, siemens_coeff, TRUE)
|