diff --git a/code/__defines/xenoarcheaology.dm b/code/__defines/xenoarcheaology.dm index b6af0d0681..b8b781795f 100644 --- a/code/__defines/xenoarcheaology.dm +++ b/code/__defines/xenoarcheaology.dm @@ -33,7 +33,8 @@ #define ARCHAEO_REMAINS_ROBOT 33 #define ARCHAEO_REMAINS_XENO 34 #define ARCHAEO_GASMASK 35 -#define MAX_ARCHAEO 35 +#define ARCHAEO_ALIEN_ITEM 36 +#define MAX_ARCHAEO 36 #define DIGSITE_GARDEN 1 #define DIGSITE_ANIMAL 2 diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 35ef300978..015bff5672 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -10,6 +10,7 @@ name = "" icon = 'icons/mob/screen1.dmi' layer = 20.0 + appearance_flags = TILE_BOUND|PIXEL_SCALE|NO_CLIENT_COLOR unacidable = 1 var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. var/datum/hud/hud = null // A reference to the owner HUD, if any. diff --git a/code/game/gamemodes/changeling/powers/enrage.dm b/code/game/gamemodes/changeling/powers/enrage.dm new file mode 100644 index 0000000000..00b2193c86 --- /dev/null +++ b/code/game/gamemodes/changeling/powers/enrage.dm @@ -0,0 +1,33 @@ +/datum/power/changeling/enrage + name = "Enrage" + desc = "We evolve modifications to our mind and body, allowing us to call on intense periods of rage for our benefit." + helptext = "Berserks us, giving massive bonuses to fighting in close quarters for thirty seconds, and losing the ability to \ + be accurate at ranged while active. Afterwards, we will suffer extreme amounts of exhaustion for a period of two minutes, \ + during which we will be much weaker and slower than before. We cannot berserk again while exhausted. This ability requires \ + a significant amount of nutrition to use, and cannot be used if too hungry. Using this ability will end most forms of disables." + enhancedtext = "The length of exhaustion after berserking is reduced to one minute, from two, and requires half as much nutrition." + ability_icon_state = "ling_berserk" + genomecost = 2 + allowduringlesserform = 1 + verbpath = /mob/living/proc/changeling_berserk + +// Makes the ling very upset. +/mob/living/proc/changeling_berserk() + set category = "Changeling" + set name = "Enrage (30)" + set desc = "Causes you to go Berserk." + + var/datum/changeling/changeling = changeling_power(30,0,100) + if(!changeling) + return 0 + + var/modifier_to_use = /datum/modifier/berserk/changeling + if(src.mind.changeling.recursive_enhancement) + modifier_to_use = /datum/modifier/berserk/changeling/recursive + to_chat(src, "We optimize our levels of anger, which will avoid excessive stress on ourselves.") + + if(add_modifier(modifier_to_use, 30 SECONDS)) + changeling.chem_charges -= 30 + + feedback_add_details("changeling_powers","EN") + return 1 \ No newline at end of file diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 71e5170852..6500a4ba60 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -234,7 +234,9 @@ // apparently called whenever an item is removed from a slot, container, or anything else. /obj/item/proc/dropped(mob/user as mob) ..() - if(zoom) zoom() //binoculars, scope, etc + if(zoom) + zoom() //binoculars, scope, etc + appearance_flags &= ~NO_CLIENT_COLOR // called just as an item is picked up (loc is not yet changed) /obj/item/proc/pickup(mob/user) @@ -259,8 +261,11 @@ // note this isn't called during the initial dressing of a player /obj/item/proc/equipped(var/mob/user, var/slot) layer = 20 - if(user.client) user.client.screen |= src - if(user.pulling == src) user.stop_pulling() + if(user.client) + user.client.screen |= src + if(user.pulling == src) + user.stop_pulling() + appearance_flags |= NO_CLIENT_COLOR return //Defines which slots correspond to which slot flags diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 09c97fdcf1..a8f0e6801e 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -31,6 +31,12 @@ return var/weapon_attack_speed = user.get_attack_speed(I) / 10 var/weapon_damage = I.force + var/modified_damage_percent = 1 + + for(var/datum/modifier/M in user.modifiers) + if(!isnull(M.outgoing_melee_damage_percent)) + weapon_damage *= M.outgoing_melee_damage_percent + modified_damage_percent *= M.outgoing_melee_damage_percent if(istype(I, /obj/item/weapon/gun)) var/obj/item/weapon/gun/G = I @@ -53,7 +59,7 @@ qdel(P) var/DPS = weapon_damage / weapon_attack_speed - to_chat(user, "Damage: [weapon_damage]") + to_chat(user, "Damage: [weapon_damage][modified_damage_percent != 1 ? " (Modified by [modified_damage_percent*100]%)":""]") to_chat(user, "Attack Speed: [weapon_attack_speed]/s") to_chat(user, "\The [I] does [DPS] damage per second.") if(DPS > 0) diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index eea135212d..ac82df4ddc 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -6,6 +6,7 @@ * Pumpkin head * Kitty ears * Holiday hats + Crown of Wrath */ /* @@ -207,4 +208,68 @@ desc = "It's a festive christmas hat, in green!" icon_state = "santahatgreen" item_state_slots = list(slot_r_hand_str = "santahatgreen", slot_l_hand_str = "santahatgreen") - body_parts_covered = 0 \ No newline at end of file + body_parts_covered = 0 + +/* + * Xenoarch/Surface Loot Hats + */ + +// Triggers an effect when the wearer is 'in grave danger'. +// Causes brainloss when it happens. +/obj/item/clothing/head/psy_crown + name = "broken crown" + desc = "A crown-of-thorns with a missing gem." + var/tension_threshold = 150 + var/cooldown = null // world.time of when this was last triggered. + var/cooldown_duration = 3 MINUTES // How long the cooldown should be. + var/flavor_equip = null // Message displayed to someone who puts this on their head. Drones don't get a message. + var/flavor_unequip = null // Ditto, but for taking it off. + var/flavor_drop = null // Ditto, but for dropping it. + var/flavor_activate = null // Ditto, for but activating. + var/brainloss_cost = 3 // Whenever it activates, inflict this much brainloss on the wearer, as its not good for the mind to wear things that manipulate it. + +/obj/item/clothing/head/psy_crown/proc/activate_ability(var/mob/living/wearer) + cooldown = world.time + cooldown_duration + to_chat(wearer, flavor_activate) + to_chat(wearer, "The inside of your head hurts...") + wearer.adjustBrainLoss(brainloss_cost) + +/obj/item/clothing/head/psy_crown/equipped(var/mob/living/carbon/human/H) + ..() + if(istype(H) && H.head == src && H.is_sentient()) + processing_objects += src + to_chat(H, flavor_equip) + +/obj/item/clothing/head/psy_crown/dropped(var/mob/living/carbon/human/H) + ..() + processing_objects -= src + if(H.is_sentient()) + if(loc == H) // Still inhand. + to_chat(H, flavor_unequip) + else + to_chat(H, flavor_drop) + +/obj/item/clothing/head/psy_crown/Destroy() + processing_objects -= src + return ..() + +/obj/item/clothing/head/psy_crown/process() + if(isliving(loc)) + var/mob/living/L = loc + if(world.time >= cooldown && L.is_sentient() && L.get_tension() >= tension_threshold) + activate_ability(L) + + +/obj/item/clothing/head/psy_crown/wrath + name = "red crown" + desc = "A crown-of-thorns set with a red gemstone that seems to glow unnaturally. It feels rather disturbing to touch." + description_info = "This has a chance to cause the wearer to become extremely angry when in extreme danger." + icon_state = "wrathcrown" + flavor_equip = "You feel a bit angrier after putting on this crown." + flavor_unequip = "You feel calmer after removing the crown." + flavor_drop = "You feel much calmer after letting go of the crown." + flavor_activate = "An otherworldly feeling seems to enter your mind, and it ignites your mind in fury!" + +/obj/item/clothing/head/psy_crown/wrath/activate_ability(var/mob/living/wearer) + ..() + wearer.add_modifier(/datum/modifier/berserk, 30 SECONDS) \ No newline at end of file diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm index 7164396f29..7c005838d1 100644 --- a/code/modules/mob/_modifiers/modifiers.dm +++ b/code/modules/mob/_modifiers/modifiers.dm @@ -19,6 +19,7 @@ var/light_range = null // How far the light for the above var goes. Not implemented yet. var/light_intensity = null // Ditto. Not implemented yet. var/mob_overlay_state = null // Icon_state for an overlay to apply to a (human) mob while this exists. This is actually implemented. + var/client_color = null // If set, the client will have the world be shown in this color, from their perspective. // Now for all the different effects. // Percentage modifiers are expressed as a multipler. (e.g. +25% damage should be written as 1.25) @@ -43,6 +44,7 @@ var/metabolism_percent // Adjusts the mob's metabolic rate, which affects reagent processing. Won't affect mobs without reagent processing. var/icon_scale_percent // Makes the holder's icon get scaled up or down. var/attack_speed_percent // Makes the holder's 'attack speed' (click delay) shorter or longer. + var/pain_immunity // Makes the holder not care about pain while this is on. Only really useful to human mobs. /datum/modifier/New(var/new_holder, var/new_origin) holder = new_holder @@ -52,6 +54,11 @@ origin = weakref(holder) ..() +// Checks if the modifier should be allowed to be applied to the mob before attaching it. +// Override for special criteria, e.g. forbidding robots from receiving it. +/datum/modifier/proc/can_apply(var/mob/living/L) + return TRUE + // Checks to see if this datum should continue existing. /datum/modifier/proc/check_if_valid() if(expire_at && expire_at < world.time) // Is our time up? @@ -66,8 +73,14 @@ holder.update_modifier_visuals() if(icon_scale_percent) // Correct the scaling. holder.update_transform() + if(client_color) + holder.update_client_color() qdel(src) +// Override this for special effects when it gets added to the mob. +/datum/modifier/proc/on_applied() + return + // Override this for special effects when it gets removed. /datum/modifier/proc/on_expire() return @@ -114,15 +127,21 @@ // If we're at this point, the mob doesn't already have it, or it does but stacking is allowed. var/datum/modifier/mod = new modifier_type(src, origin) + if(!mod.can_apply(src)) + qdel(mod) + return if(expire_at) mod.expire_at = world.time + expire_at if(mod.on_created_text) to_chat(src, mod.on_created_text) modifiers.Add(mod) + mod.on_applied() if(mod.mob_overlay_state) update_modifier_visuals() if(mod.icon_scale_percent) update_transform() + if(mod.client_color) + update_client_color() return mod diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm new file mode 100644 index 0000000000..0a02f6b096 --- /dev/null +++ b/code/modules/mob/_modifiers/modifiers_misc.dm @@ -0,0 +1,182 @@ +// File for modifier defines that don't fit anywhere else. Since this is a misc file, it's doomed to get filled with everything like all the others. + + +/* + Berserk is a modifier that grants vastly increased melee capability for a short period of time, easily allowing the +holder to do more than twice the damage they would normally do, as it both increases outgoing melee damage, and +reduces their attack delay. The screen will also turn deep red and the holder will get larger in size. + +The modifier also gives some defenses, in that it gives additional max health (this can bite them in the ass later since +its not permanent), makes them move slightly faster, cancels disabling effects when it triggers, and reduces the power of +future disables by a very large amount. It also suppresses pain until it expires, however berserking while under massive pain +is likely to cause them to pass out when exhaustion hits. + +Due to the intense rage felt by the holder, the focus needed to use ranged weapons is lost, making accuracy with them +massively reduced. The holder also feels less of a need to evade attacks and will be easier to hit. + +Berserk can be extended by having another instance try to affect the holder while in the middle of a berserk. + +After the modifier expires, a second modifier representing exhaustion is placed, which inflicts massive maluses and prevents +further berserks until it expires. This generally means that someone getting caught while exhausted will be much easier to fight, +as they will be much slower, attack slower, do less melee damage, be easier to hit, and disabling effects will affect them harder. + +Berserking causes the holder to feel hungrier. If they are starving, this modifier cannot be applied. Diona cannot +be berserked, or those who are suffering from exhaustion. Non-Drone Synthetics that receive the berserk modifier will +instead get a version that has no benefits, but will not cost nutrition or cause exhaustion. Drones cannot receive berserk, as +they are emotionless automatrons. + +Berserk is a somewhat rare modifier to obtain freely (and for good reason), however here are ways to see it in action; +- Red Slimes will berserk if they go rabid. +- Red slime core reactions will berserk slimes that can see the user in addition to making them go rabid. +- Red slime core reactions will berserk prometheans that can see the user. +- Bears will berserk when losing a fight. +- Changelings can evolve a 2 point ability to use a changeling-specific variant of Berserk, that replaces the text with a 'we' variant. +Recursive Enhancement allows the changeling to instead used an improved variant that features less exhaustion time and less nutrition drain. +- Xenoarch artifacts may have forced berserking as one of their effects. This is especially fun if an artifact that makes hostile mobs is nearby. +Will cause three brainloss in those affected due to the artifact meddling with their mind. +- A rare alien artifact might be found on the Surface, or obtained from Xenoarch, that causes berserking when it thinks +the wearer is in danger, however in addition to the usual drawbacks, each use causes three brainloss in the user, due to how +the artifact triggers the rage. + +*/ + +/datum/modifier/berserk + name = "berserk" + desc = "You are filled with an overwhelming rage." + client_color = "#FF0000" // Make everything red! + mob_overlay_state = "berserk" + + on_created_text = "You feel an intense and overwhelming rage overtake you as you go berserk!" + on_expired_text = "The blaze of rage inside you has ran out." + stacks = MODIFIER_STACK_EXTEND + + // The good stuff. + slowdown = -1 // Move a bit faster. + attack_speed_percent = 0.66 // Attack at 2/3 the normal delay. + outgoing_melee_damage_percent = 1.5 // 50% more damage from melee. + max_health_percent = 1.5 // More health as a buffer, however the holder might fall into crit after this expires if they're mortally wounded. + disable_duration_percent = 0.25 // Disables only last 25% as long. + icon_scale_percent = 1.2 // Look scarier. + pain_immunity = TRUE // Avoid falling over from shock (at least until it expires). + + // The less good stuff. + accuracy = -5 // Aiming requires focus. + accuracy_dispersion = 3 // Ditto. + evasion = -3 // Too angry to dodge. + + var/nutrition_cost = 150 + var/exhaustion_duration = 2 MINUTES // How long the exhaustion modifier lasts after it expires. Set to 0 to not apply one. + var/last_shock_stage = 0 + + +// For changelings. +/datum/modifier/berserk/changeling + on_created_text = "We feel an intense and overwhelming rage overtake us as we go berserk!" + on_expired_text = "The blaze of rage inside us has ran out." + +// For changelings who bought the Recursive Enhancement evolution. +/datum/modifier/berserk/changeling/recursive + exhaustion_duration = 1 MINUTE + nutrition_cost = 75 + + +/datum/modifier/berserk/on_applied() + if(ishuman(holder)) // Most other mobs don't really use nutrition and can't get it back. + holder.nutrition = max(0, holder.nutrition - nutrition_cost) + holder.visible_message("\The [holder] descends into an all consuming rage!") + + // End all stuns. + holder.SetParalysis(0) + holder.SetStunned(0) + holder.SetWeakened(0) + holder.setHalLoss(0) + holder.lying = 0 + holder.update_canmove() + + // Temporarily end pain. + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + last_shock_stage = H.shock_stage + H.shock_stage = 0 + +/datum/modifier/berserk/on_expire() + if(exhaustion_duration > 0 && holder.stat != DEAD) + holder.add_modifier(/datum/modifier/berserk_exhaustion, exhaustion_duration) + + if(prob(last_shock_stage)) + to_chat(holder, "You pass out from the pain you were suppressing.") + holder.Paralyse(5) + + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + H.shock_stage = last_shock_stage + +/datum/modifier/berserk/can_apply(var/mob/living/L) + if(L.stat) + to_chat(L, "You can't be unconscious or dead to berserk.") + return FALSE // It would be weird to see a dead body get angry all of a sudden. + + if(!L.is_sentient()) + return FALSE // Drones don't feel anything. + + if(L.has_modifier_of_type(/datum/modifier/berserk_exhaustion)) + to_chat(L, "You recently berserked, and cannot do so again while exhausted.") + return FALSE // On cooldown. + + if(L.isSynthetic()) + L.add_modifier(/datum/modifier/berserk_synthetic, 30 SECONDS) + return FALSE // Borgs can get angry but their metal shell can't be pushed harder by just being mad. Same for Posibrains. + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + if(H.species.name == "Diona") + to_chat(L, "You feel strange for a moment, but it passes.") + return FALSE // Happy trees aren't affected by blood rages. + + if(L.nutrition < nutrition_cost) + to_chat(L, "You are too hungry to berserk.") + return FALSE // Too hungry to enrage. + + return ..() + +/datum/modifier/berserk/tick() + if(holder.stat == DEAD) + expire(silent = TRUE) + + +// Applied when berserk expires. Acts as a downside as well as the cooldown for berserk. +/datum/modifier/berserk_exhaustion + name = "exhaustion" + desc = "You recently exerted yourself extremely hard, and need a rest." + + on_created_text = "You feel extremely exhausted." + on_expired_text = "You feel less exhausted now." + stacks = MODIFIER_STACK_EXTEND + + slowdown = 2 + attack_speed_percent = 1.5 + outgoing_melee_damage_percent = 0.6 + disable_duration_percent = 1.5 + evasion = -2 + +/datum/modifier/berserk_exhaustion/on_applied() + holder.visible_message("\The [holder] looks exhausted.") + + +// Synth version with no benefits due to a loss of focus inside a metal shell, which can't be pushed harder just be being mad. +// Fortunately there is no exhaustion or nutrition cost. +/datum/modifier/berserk_synthetic + name = "recklessness" + desc = "You are filled with an overwhelming rage, however your metal shell prevents taking advantage of this." + client_color = "#FF0000" // Make everything red! + mob_overlay_state = "berserk" + + on_created_text = "You feel an intense and overwhelming rage overtake you as you go berserk! \ + Unfortunately, your lifeless body cannot benefit from this. You feel reckless..." + on_expired_text = "The blaze of rage inside your mind has ran out." + stacks = MODIFIER_STACK_EXTEND + + // Just being mad isn't gonna overclock your body when you're a beepboop. + accuracy = -5 // Aiming requires focus. + accuracy_dispersion = 3 // Ditto. + evasion = -3 // Too angry to dodge. diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm index c839b506fa..e166573811 100644 --- a/code/modules/mob/living/bot/bot.dm +++ b/code/modules/mob/living/bot/bot.dm @@ -296,6 +296,9 @@ /mob/living/bot/proc/explode() qdel(src) +/mob/living/bot/is_sentient() + return FALSE + /******************************************************************/ // Navigation procs // Used for A-star pathfinding diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index ff2513bdc7..da715fbe96 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1506,12 +1506,20 @@ /mob/living/carbon/human/can_feel_pain(var/obj/item/organ/check_organ) if(isSynthetic()) return 0 + for(var/datum/modifier/M in modifiers) + if(M.pain_immunity == TRUE) + return 0 if(check_organ) if(!istype(check_organ)) return 0 return check_organ.organ_can_feel_pain() return !(species.flags & NO_PAIN) +/mob/living/carbon/human/is_sentient() + if(get_FBP_type() == FBP_DRONE) + return FALSE + return ..() + /mob/living/carbon/human/is_muzzled() return (wear_mask && (istype(wear_mask, /obj/item/clothing/mask/muzzle) || istype(src.wear_mask, /obj/item/weapon/grenade))) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 4bea07f479..15de0b93d9 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1013,6 +1013,9 @@ default behaviour is: /mob/living/proc/equip_post_job() return +// Used to check if something is capable of thought, in the traditional sense. +/mob/living/proc/is_sentient() + return TRUE /mob/living/update_transform() // First, get the correct size. @@ -1025,4 +1028,40 @@ default behaviour is: var/matrix/M = matrix() M.Scale(desired_scale) M.Translate(0, 16*(desired_scale-1)) - src.transform = M \ No newline at end of file + animate(src, transform = M, time = 10) + +// This handles setting the client's color variable, which makes everything look a specific color. +// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. +/mob/living/update_client_color() + if(!client) + return + + var/list/colors_to_blend = list() + for(var/datum/modifier/M in modifiers) + if(!isnull(M.client_color)) + colors_to_blend += M.client_color + + if(colors_to_blend.len) + var/final_color + if(colors_to_blend.len == 1) // If it's just one color we can skip all of this work. + final_color = colors_to_blend[1] + + else // Otherwise we need to do some messy additive blending. + var/R = 0 + var/G = 0 + var/B = 0 + + for(var/C in colors_to_blend) + var/RGB = hex2rgb(C) + R = between(0, R + RGB[1], 255) + G = between(0, G + RGB[2], 255) + B = between(0, B + RGB[3], 255) + final_color = rgb(R,G,B) + + if(final_color) + var/old_color = client.color // Don't know if BYOND has an internal optimization to not care about animate() calls that effectively do nothing. + if(final_color != old_color) // Gonna do a check just incase. + animate(client, color = final_color, time = 10) + + else // No colors, so remove the client's color. + animate(client, color = null, time = 10) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b48a1609d9..c8afb9b0d1 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -791,5 +791,10 @@ var/list/ai_verbs_default = list( if(rig) rig.force_rest(src) +/mob/living/silicon/ai/is_sentient() + // AI cores don't store what brain was used to build them so we're just gonna assume they can think to some degree. + // If that is ever fixed please update this proc. + return TRUE + #undef AI_CHECK_WIRELESS #undef AI_CHECK_RADIO diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm index 93605228c0..872d5302c4 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone.dm @@ -67,6 +67,9 @@ var/list/mob_hat_cache = list() hat.loc = get_turf(src) ..() +/mob/living/silicon/robot/drone/is_sentient() + return FALSE + /mob/living/silicon/robot/drone/construction icon_state = "constructiondrone" law_type = /datum/ai_laws/construction_drone diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 574a4a0a7f..4b492a9498 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -13,6 +13,7 @@ handle_actions() handle_instability() // For some reason borg Life() doesn't call ..() + handle_modifiers() handle_light() if(client) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index b2a509bb7c..5eac71276f 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1090,3 +1090,6 @@ src << "Hack attempt detected." return 1 return + +/mob/living/silicon/robot/is_sentient() + return braintype != "Drone" \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/aliens/hivebot.dm b/code/modules/mob/living/simple_animal/aliens/hivebot.dm index aefe91b371..0a7fcd7613 100644 --- a/code/modules/mob/living/simple_animal/aliens/hivebot.dm +++ b/code/modules/mob/living/simple_animal/aliens/hivebot.dm @@ -1,6 +1,6 @@ // Hivebots are tuned towards how many default lasers are needed to kill them. // As such, if laser damage is ever changed, you should change this define. -#define LASERS_TO_KILL *40 +#define LASERS_TO_KILL *30 // Default hivebot is melee, and a bit more meaty, so it can meatshield for their ranged friends. /mob/living/simple_animal/hostile/hivebot diff --git a/code/modules/mob/living/simple_animal/animals/bear.dm b/code/modules/mob/living/simple_animal/animals/bear.dm index ea8398afc2..29c4fd9219 100644 --- a/code/modules/mob/living/simple_animal/animals/bear.dm +++ b/code/modules/mob/living/simple_animal/animals/bear.dm @@ -11,8 +11,8 @@ intelligence_level = SA_ANIMAL cooperative = 1 - maxHealth = 60 - health = 60 + maxHealth = 120 + health = 120 turns_per_move = 5 see_in_dark = 6 stop_when_pulled = 0 @@ -43,9 +43,20 @@ meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat - var/stance_step = 0 +// var/stance_step = 0 -/mob/living/simple_animal/hostile/bear/handle_stance() +/mob/living/simple_animal/hostile/bear/handle_stance(var/new_stance) + // Below was a bunch of code that made this specific mob be 'alert' and will hurt you when it gets closer. + // It's commented out because it made infinite loops and the AI is going to be moved/rewritten sometime soon (famous last words) + // and it would be better if this 'alert before attacking' behaviour was on the parent instead of a specific type of mob anyways. + + // Instead we're just gonna get angry if we're dying. + ..(new_stance) + if(stance == STANCE_ATTACK || stance == STANCE_ATTACKING) + if((health / maxHealth) <= 0.5) // At half health, and fighting someone currently. + add_modifier(/datum/modifier/berserk, 30 SECONDS) + + /* switch(stance) if(STANCE_TIRED) stop_automated_movement = 1 @@ -87,6 +98,7 @@ return else ..() + */ /mob/living/simple_animal/hostile/bear/update_icons() ..() @@ -103,7 +115,7 @@ . = ..() if(.) custom_emote(1,"stares alertly at [.]") - handle_stance(STANCE_ALERT) +// handle_stance(STANCE_ALERT) /mob/living/simple_animal/hostile/bear/PunchTarget() if(!Adjacent(target_mob)) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 338b66e43f..0379c5d056 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1474,6 +1474,9 @@ return set_target(new_target) +/mob/living/simple_animal/is_sentient() + return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC + //Commands, reactions, etc /mob/living/simple_animal/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) ..() diff --git a/code/modules/mob/living/simple_animal/slime/subtypes.dm b/code/modules/mob/living/simple_animal/slime/subtypes.dm index b7610d466b..486c04866f 100644 --- a/code/modules/mob/living/simple_animal/slime/subtypes.dm +++ b/code/modules/mob/living/simple_animal/slime/subtypes.dm @@ -469,6 +469,10 @@ enrage() // How dare you try to control the red slime. say("Grrr...!") +/mob/living/simple_animal/slime/red/enrage() + ..() + add_modifier(/datum/modifier/berserk, 30 SECONDS) + /mob/living/simple_animal/slime/green desc = "This slime is radioactive." diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index e29f6cdffa..51f3e4f22a 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -46,5 +46,6 @@ client.perspective = MOB_PERSPECTIVE reload_fullscreen() // Reload any fullscreen overlays this mob has. add_click_catcher() + update_client_color() //set macro to normal incase it was overriden (like cyborg currently does) winset(src, null, "mainwindow.macro=macro hotkey_toggle.is-checked=false input.focus=true input.background-color=#D3B5B5") diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index f911ba94c5..a40d71af83 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1117,4 +1117,12 @@ mob/proc/yank_out_object() if(!check_has_body_select()) return var/obj/screen/zone_sel/selector = mob.zone_sel - selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) \ No newline at end of file + selector.set_selected_zone(next_in_list(mob.zone_sel.selecting,zones)) + +// This handles setting the client's color variable, which makes everything look a specific color. +// This proc is here so it can be called without needing to check if the client exists, or if the client relogs. +// This is for inheritence since /mob/living will serve most cases. If you need ghosts to use this you'll have to implement that yourself. +/mob/proc/update_client_color() + if(client && client.color) + animate(client, color = null, time = 10) + return \ No newline at end of file diff --git a/code/modules/tension/tension.dm b/code/modules/tension/tension.dm new file mode 100644 index 0000000000..913f1d2dff --- /dev/null +++ b/code/modules/tension/tension.dm @@ -0,0 +1,131 @@ +// This code is used to give a very rough estimate of how screwed an individual player might be at any given moment when fighting monsters. +// You could use this to have an effect trigger when someone is in serious danger, or as a means for an AI to guess which mob needs to die first. +// The idea and the code structure was taken from Dungeon Crawl Stone Soup. + +/atom/movable/proc/get_threat(var/mob/living/threatened) + return 0 + + +/atom/movable/proc/guess_threat_level(var/mob/living/threatened) + return 0 + +/mob/living/simple_animal + var/threat_level = null // Set this if you want an explicit danger rating. + +/mob/living/simple_animal/guess_threat_level(var/mob/living/threatened) + if(threat_level) // If they have a predefined number, use it. + return threat_level + // Otherwise we need to guess how scary this thing is. + var/threat_guess = 0 + + // First lets consider their attack ability. + var/potential_damage = 0 + if(!ranged) //Melee damage. + potential_damage = (melee_damage_lower + melee_damage_upper) / 2 + else + if(projectiletype) + var/obj/item/projectile/P = new projectiletype(src) + if(P.nodamage || P.taser_effect) // Tasers are somewhat less scary. + potential_damage = P.agony / 2 + else + potential_damage = P.damage + if(P.damage_type == HALLOSS) // Not sure if any projectiles do this, but can't be too safe. + potential_damage /= 2 + // Rubber bullets, I guess. + potential_damage += P.agony / 2 + + if(rapid) // This makes them shoot three times per cycle. + potential_damage *= 3 + + qdel(P) + threat_guess += potential_damage + + // Then consider their defense. + threat_guess += getMaxHealth() / 5 // 100 health translates to 20 threat. + + return threat_guess + +/mob/living/get_threat(var/mob/living/threatened) + if(stat) + return 0 + + +/mob/living/simple_animal/get_threat(var/mob/living/threatened) + . = ..() + + if(!hostile) + return 0 // Can't hurt anyone. + + if(incapacitated(INCAPACITATION_DISABLED)) + return 0 // Can't currently hurt you if it's stunned. + + var/friendly = threatened.faction == faction + + var/threat = guess_threat_level() + + // Hurt entities contribute less tension. + threat *= health + threat /= getMaxHealth() + + // Allies reduce tension instead of adding. + if(friendly) + threat = -threat + + else + if(threatened.invisibility > see_invisible) + threat /= 2 // Target cannot be seen by src. + if(invisibility > threatened.see_invisible) + threat *= 2 // Target cannot see src. + + // Handle statuses. + if(confused) + threat /= 2 + + if(has_modifier_of_type(/datum/modifier/berserk)) + threat *= 2 + + // Handle ability to harm. + // Being five tiles away from some spiders is a lot less scary than being in melee range of five spiders at once. + if(!ranged) + threat /= max(get_dist(src, threatened), 1) + + return threat + + + +// Gives a rough idea of how much danger someone is in. Meant to be used for PvE things since PvP has too many unknown variables. +/mob/living/proc/get_tension() + var/tension = 0 + var/list/potential_threats = list() + + // First, get everything threatening to us. + for(var/thing in view(src)) + if(isliving(thing)) + potential_threats += thing + if(istype(thing, /obj/machinery/porta_turret)) + potential_threats += thing + + var/danger = FALSE + // Now to get all the threats. + for(var/atom/movable/AM in potential_threats) + var/tension_from_AM = AM.get_threat(src) + tension += tension_from_AM + if(tension_from_AM > 0) + danger = TRUE + + if(!danger) + return 0 + + // Tension is roughly doubled when about to fall into crit. + var/max_health = getMaxHealth() + tension *= 2 * max_health / (health + max_health) + + // Being unable to act is really tense. + if(incapacitated(INCAPACITATION_DISABLED) && !lying) + tension *= 10 + return tension + + if(confused) + tension *= 2 + + return tension diff --git a/code/modules/xenoarcheaology/artifacts/artifact.dm b/code/modules/xenoarcheaology/artifacts/artifact.dm index 2bb6434553..2db3107c01 100644 --- a/code/modules/xenoarcheaology/artifacts/artifact.dm +++ b/code/modules/xenoarcheaology/artifacts/artifact.dm @@ -48,6 +48,17 @@ if(prob(25)) my_effect.trigger = pick(TRIGGER_WATER, TRIGGER_ACID, TRIGGER_VOLATILE, TRIGGER_TOXIN) +/obj/machinery/artifact/proc/choose_effect() + var/effect_type = input(usr, "What type do you want?", "Effect Type") as null|anything in typesof(/datum/artifact_effect) - /datum/artifact_effect + if(effect_type) + my_effect = new effect_type(src) + if(alert(usr, "Do you want a secondary effect?", "Second Effect", "No", "Yes") == "Yes") + var/second_effect_type = input(usr, "What type do you want as well?", "Second Effect Type") as null|anything in typesof(/datum/artifact_effect) - list(/datum/artifact_effect, effect_type) + secondary_effect = new second_effect_type(src) + else + secondary_effect = null + + /obj/machinery/artifact/process() var/turf/L = loc if(!istype(L)) // We're inside a container or on null turf, either way stop processing effects diff --git a/code/modules/xenoarcheaology/effects/berserk.dm b/code/modules/xenoarcheaology/effects/berserk.dm new file mode 100644 index 0000000000..a221c9104c --- /dev/null +++ b/code/modules/xenoarcheaology/effects/berserk.dm @@ -0,0 +1,43 @@ +/datum/artifact_effect/berserk + name = "berserk" + effect_type = EFFECT_PSIONIC + +/datum/artifact_effect/berserk/proc/apply_berserk(var/mob/living/L) + if(!istype(L)) + return FALSE + + if(!L.is_sentient()) + return FALSE // Drons are presumably deaf to any psionic things. + + if(L.add_modifier(/datum/modifier/berserk, 30 SECONDS)) + to_chat(L, "An otherworldly feeling seems to enter your mind, and it ignites your mind in fury!") + L.adjustBrainLoss(3) // Playing with berserking alien psychic artifacts isn't good for the mind. + to_chat(L, "The inside of your head hurts...") + return TRUE + else + if(L.has_modifier_of_type(/datum/modifier/berserk)) // Already angry. + to_chat(L, "An otherworldly feeling seems to enter your mind again, and it fans your inner flame, extending your rage.") + else // Exhausted or something. + to_chat(L, "An otherworldly feeling seems to enter your mind, and you briefly feel an intense anger, but \ + it quickly passes.") + return FALSE + +/datum/artifact_effect/berserk/DoEffectTouch(var/mob/toucher) + if(toucher && isliving(toucher)) + apply_berserk(toucher) + return TRUE + +/datum/artifact_effect/berserk/DoEffectAura() + if(holder) + var/turf/T = get_turf(holder) + for(var/mob/living/L in range(src.effectrange,T)) + if(prob(10)) + apply_berserk(L) + return TRUE + +/datum/artifact_effect/berserk/DoEffectPulse() + if(holder) + var/turf/T = get_turf(holder) + for(var/mob/living/L in range(src.effectrange,T)) + apply_berserk(L) + return TRUE \ No newline at end of file diff --git a/code/modules/xenoarcheaology/finds/find_spawning.dm b/code/modules/xenoarcheaology/finds/find_spawning.dm index f6a4200654..051a67c542 100644 --- a/code/modules/xenoarcheaology/finds/find_spawning.dm +++ b/code/modules/xenoarcheaology/finds/find_spawning.dm @@ -409,6 +409,37 @@ new_item = new /obj/item/clothing/mask/gas/poltergeist(src.loc) else new_item = new /obj/item/clothing/mask/gas(src.loc) + if(36) + // Alien stuff. + apply_prefix = FALSE + apply_material_decorations = FALSE + + var/list/alien_stuff = list( + /obj/item/device/multitool/alien, + /obj/item/stack/cable_coil/alien, + /obj/item/weapon/crowbar/alien, + /obj/item/weapon/screwdriver/alien, + /obj/item/weapon/weldingtool/alien, + /obj/item/weapon/wirecutters/alien, + /obj/item/weapon/wrench/alien, + /obj/item/weapon/surgical/FixOVein/alien, + /obj/item/weapon/surgical/bone_clamp/alien, + /obj/item/weapon/surgical/cautery/alien, + /obj/item/weapon/surgical/circular_saw/alien, + /obj/item/weapon/surgical/hemostat/alien, + /obj/item/weapon/surgical/retractor/alien, + /obj/item/weapon/surgical/scalpel/alien, + /obj/item/weapon/surgical/surgicaldrill/alien, + /obj/item/weapon/cell/device/weapon/recharge/alien, + /obj/item/clothing/suit/armor/alien, + /obj/item/clothing/head/helmet/alien, + /obj/item/clothing/head/psy_crown/wrath + ) + + var/new_type = pick(alien_stuff) + new_item = new new_type(src.loc) + item_type = new_item.name + var/decorations = "" if(apply_material_decorations) source_material = pick("cordite","quadrinium",DEFAULT_WALL_MATERIAL,"titanium","aluminium","ferritic-alloy","plasteel","duranium") diff --git a/code/modules/xenoarcheaology/finds/finds_defines.dm b/code/modules/xenoarcheaology/finds/finds_defines.dm index 060119c7ce..29516d763a 100644 --- a/code/modules/xenoarcheaology/finds/finds_defines.dm +++ b/code/modules/xenoarcheaology/finds/finds_defines.dm @@ -76,6 +76,7 @@ var/global/list/finds_as_strings = list( 100;ARCHAEO_TELEBEACON, 100;ARCHAEO_TOOL, 100;ARCHAEO_STOCKPARTS, + 100;ARCHAEO_ALIEN_ITEM, 75;ARCHAEO_SHARD, 75;ARCHAEO_RODS, 75;ARCHAEO_UNKNOWN, @@ -111,6 +112,7 @@ var/global/list/finds_as_strings = list( 50;ARCHAEO_CULTROBES, 50;ARCHAEO_CULTBLADE, 50;ARCHAEO_GASMASK, + 50;ARCHAEO_ALIEN_ITEM, 25;ARCHAEO_HANDCUFFS, 25;ARCHAEO_BEARTRAP, 25;ARCHAEO_TOOL) diff --git a/code/modules/xenobio/items/extracts.dm b/code/modules/xenobio/items/extracts.dm index be22c32851..7b7a04170a 100644 --- a/code/modules/xenobio/items/extracts.dm +++ b/code/modules/xenobio/items/extracts.dm @@ -564,12 +564,19 @@ if(S.stat || S.docile || S.rabid) continue + S.add_modifier(/datum/modifier/berserk, 30 SECONDS) + if(S.client) // Player slimes always have free will. - to_chat(S, "An intense wave of rage almost overcomes you, but you remain in control of yourself.") + to_chat(S, "An intense wave of rage is felt from inside, but you remain in control of yourself.") continue S.enrage() + for(var/mob/living/carbon/human/H in view(get_turf(holder.my_atom))) + if(H.species.name == "Promethean") + H.add_modifier(/datum/modifier/berserk, 30 SECONDS) + to_chat(H, "An intense wave of rage is felt from inside, but you remain in control of yourself.") + log_and_message_admins("Red extract reaction (enrage) has been activated in [get_area(holder.my_atom)]. Last fingerprints: [holder.my_atom.fingerprintslast]") playsound(get_turf(holder.my_atom), 'sound/effects/phasein.ogg', 75, 1) diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index 9c09ef2993..8992f9d2c6 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/modifier_effects.dmi b/icons/mob/modifier_effects.dmi index eca5286d9b..947c6532f9 100644 Binary files a/icons/mob/modifier_effects.dmi and b/icons/mob/modifier_effects.dmi differ diff --git a/icons/mob/screen_spells.dmi b/icons/mob/screen_spells.dmi index 2e3aab8545..de2e1354c3 100644 Binary files a/icons/mob/screen_spells.dmi and b/icons/mob/screen_spells.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index ac6467fb6c..ff20e5913e 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/polaris.dme b/polaris.dme index 8608d15fda..3006bbcc3a 100644 --- a/polaris.dme +++ b/polaris.dme @@ -405,6 +405,7 @@ #include "code\game\gamemodes\changeling\powers\endoarmor.dm" #include "code\game\gamemodes\changeling\powers\enfeebling_string.dm" #include "code\game\gamemodes\changeling\powers\engorged_glands.dm" +#include "code\game\gamemodes\changeling\powers\enrage.dm" #include "code\game\gamemodes\changeling\powers\epinephrine_overdose.dm" #include "code\game\gamemodes\changeling\powers\escape_restraints.dm" #include "code\game\gamemodes\changeling\powers\extract_dna_sting.dm" @@ -1642,6 +1643,7 @@ #include "code\modules\mob\update_icons.dm" #include "code\modules\mob\_modifiers\cloning.dm" #include "code\modules\mob\_modifiers\modifiers.dm" +#include "code\modules\mob\_modifiers\modifiers_misc.dm" #include "code\modules\mob\_modifiers\traits.dm" #include "code\modules\mob\_modifiers\traits_phobias.dm" #include "code\modules\mob\dead\death.dm" @@ -2259,6 +2261,7 @@ #include "code\modules\tables\rack.dm" #include "code\modules\tables\tables.dm" #include "code\modules\tables\update_triggers.dm" +#include "code\modules\tension\tension.dm" #include "code\modules\turbolift\_turbolift.dm" #include "code\modules\turbolift\turbolift.dm" #include "code\modules\turbolift\turbolift_areas.dm" @@ -2301,6 +2304,7 @@ #include "code\modules\xenoarcheaology\artifacts\gigadrill.dm" #include "code\modules\xenoarcheaology\artifacts\replicator.dm" #include "code\modules\xenoarcheaology\effects\badfeeling.dm" +#include "code\modules\xenoarcheaology\effects\berserk.dm" #include "code\modules\xenoarcheaology\effects\cellcharge.dm" #include "code\modules\xenoarcheaology\effects\celldrain.dm" #include "code\modules\xenoarcheaology\effects\cold.dm"