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"