-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathfeed.dm
250 lines (236 loc) · 10.9 KB
/
feed.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#define FEED_NOTICE_RANGE 2
#define FEED_DEFAULT_TIMER (10 SECONDS)
/datum/action/cooldown/bloodsucker/feed
name = "Feed"
desc = "Feed blood off of a living creature."
button_icon_state = "power_feed"
power_explanation = "Feed:\n\
Feed can't be used until you reach your first Bloodsucker level.\n\
Activate Feed while next to someone and you will begin to feed blood off of them.\n\
The time needed before you start feeding speeds up the higher level you are.\n\
Feeding off of someone while you have them aggressively grabbed will put them to sleep.\n\
While feeding, you can't speak, as your mouth is covered.\n\
Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction\n\
If you get too many Masquerade Infractions, you will break the Masquerade.\n\
If you are in desperate need of blood, mice can be fed off of, at a cost."
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
bloodcost = 0
cooldown_time = 15 SECONDS
///Amount of blood taken, reset after each Feed. Used for logging.
var/blood_taken = 0
///The amount of Blood a target has since our last feed, this loops and lets us not spam alerts of low blood.
var/warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL
///Reference to the target we've fed off of
var/datum/weakref/target_ref
///Are we feeding with passive grab or not?
var/silent_feed = TRUE
/datum/action/cooldown/bloodsucker/feed/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
if(target_ref) //already sucking blood.
return FALSE
if(!level_current)
owner.balloon_alert(owner, "too weak!")
to_chat(owner, span_warning("You can't use [src] until you level up."))
return FALSE
if(user.is_mouth_covered() && !isplasmaman(user))
owner.balloon_alert(owner, "mouth covered!")
return FALSE
if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_PAINFUL && owner.grab_state <= GRAB_PASSIVE)
owner.balloon_alert(owner, "can't silent feed!")
return FALSE
//Find target, it will alert what the problem is, if any.
if(!find_target())
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target)
if(!target)
return FALSE
if(!user.Adjacent(target))
return FALSE
return TRUE
/datum/action/cooldown/bloodsucker/feed/DeactivatePower()
var/mob/living/user = owner
var/mob/living/feed_target = target_ref?.resolve()
if(isnull(feed_target))
log_combat(user, user, "fed on blood (target not found)", addition="(and took [blood_taken] blood)")
else
log_combat(user, feed_target, "fed on blood", addition="(and took [blood_taken] blood)")
user.balloon_alert(owner, "feed stopped")
to_chat(user, span_notice("You slowly release [feed_target]."))
if(feed_target.stat == DEAD)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled)
bloodsuckerdatum_power.AddHumanityLost(10)
target_ref = null
warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL
blood_taken = 0
REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT)
REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT)
return ..()
/datum/action/cooldown/bloodsucker/feed/ActivatePower()
var/mob/living/feed_target = target_ref.resolve()
if(istype(feed_target, /mob/living/simple_animal/mouse || istype(feed_target, /mob/living/simple_animal/hostile/rat)))
to_chat(owner, span_notice("You recoil at the taste of a lesser lifeform."))
if(bloodsuckerdatum_power.my_clan.blood_drink_type != BLOODSUCKER_DRINK_INHUMANELY)
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
bloodsuckerdatum_power.AddHumanityLost(1)
bloodsuckerdatum_power.AddBloodVolume(25)
DeactivatePower()
feed_target.death()
return
var/feed_timer = clamp(round(FEED_DEFAULT_TIMER / (1.25 * level_current)), 1 SECONDS, FEED_DEFAULT_TIMER)
if(bloodsuckerdatum_power.frenzied)
feed_timer = 2 SECONDS
owner.balloon_alert(owner, "feeding off [feed_target]...")
if(!do_after(owner, feed_timer, feed_target, NONE, TRUE))
DeactivatePower()
return
if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
if(!IS_BLOODSUCKER(feed_target) && !IS_VASSAL(feed_target) && !IS_MONSTERHUNTER(feed_target))
feed_target.Unconscious((5 + level_current) SECONDS)
if(!feed_target.density)
feed_target.Move(owner.loc)
silent_feed = FALSE //no more mr nice guy
owner.visible_message(
span_warning("[owner] closes [owner.p_their()] mouth around [feed_target]'s neck!"),
span_warning("You sink your fangs into [feed_target]'s neck."))
else
// Only people who AREN'T the target will notice this action.
var/dead_message = feed_target.stat != DEAD ? " <i>[feed_target.p_they(TRUE)] looks dazed, and will not remember this.</i>" : ""
owner.visible_message(
span_notice("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \
span_notice("You slip your fangs into [feed_target]'s wrist.[dead_message]"), \
vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target)
//check if we were seen
for(var/mob/living/watchers in oviewers(FEED_NOTICE_RANGE) - feed_target)
if(!watchers.client)
continue
if(watchers.has_unlimited_silicon_privilege)
continue
if(watchers.stat >= DEAD)
continue
if(HAS_TRAIT(watchers, TRAIT_BLIND) || HAS_TRAIT(watchers, TRAIT_NEARSIGHT))
continue
if(IS_BLOODSUCKER(watchers) || IS_VASSAL(watchers) || HAS_TRAIT(watchers.mind, TRAIT_BLOODSUCKER_HUNTER))
continue
owner.balloon_alert(owner, "feed noticed!")
bloodsuckerdatum_power.give_masquerade_infraction()
break
ADD_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT)
ADD_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT)
return ..()
/datum/action/cooldown/bloodsucker/feed/process()
if(!active) //If we aren't active (running on SSfastprocess)
return ..() //Manage our cooldown timers
var/mob/living/user = owner
var/mob/living/feed_target = target_ref?.resolve()
if(!ContinueActive(user, feed_target))
if(!silent_feed)
user.visible_message(
span_warning("[user] is ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!"),
span_warning("Your teeth are ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!"))
// Deal Damage to Target (should have been more careful!)
if(iscarbon(feed_target))
var/mob/living/carbon/carbon_target = feed_target
carbon_target.bleed(15)
playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, TRUE)
if(ishuman(feed_target))
var/mob/living/carbon/human/target_user = feed_target
var/obj/item/bodypart/head_part = target_user.get_bodypart(BODY_ZONE_HEAD)
if(head_part)
head_part.generic_bleedstacks += 5
feed_target.add_splatter_floor(get_turf(feed_target))
user.add_mob_blood(feed_target) // Put target's blood on us. The donor goes in the ( )
feed_target.add_mob_blood(feed_target)
feed_target.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = CANT_WOUND)
INVOKE_ASYNC(feed_target, TYPE_PROC_REF(/mob, emote), "scream")
DeactivatePower()
return
var/feed_strength_mult = 0
if(bloodsuckerdatum_power.frenzied)
feed_strength_mult = 2
else if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
feed_strength_mult = 1
else
feed_strength_mult = 0.3
blood_taken += bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current)
if(feed_strength_mult > 5 && feed_target.stat < DEAD)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood)
if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY && !feed_target.mind)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
if(feed_target.stat >= DEAD)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead)
if(!IS_BLOODSUCKER(feed_target))
if(feed_target.blood_volume <= BLOOD_VOLUME_BAD(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_BAD(feed_target))
owner.balloon_alert(owner, "your victim's blood is fatally low!")
else if(feed_target.blood_volume <= BLOOD_VOLUME_OKAY(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_OKAY(feed_target))
owner.balloon_alert(owner, "your victim's blood is dangerously low.")
else if(feed_target.blood_volume <= BLOOD_VOLUME_SAFE(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_SAFE(feed_target))
owner.balloon_alert(owner, "your victim's blood is at an unsafe level.")
warning_target_bloodvol = feed_target.blood_volume
if(user.blood_volume >= bloodsuckerdatum_power.max_blood_volume)
DeactivatePower()
return
if(feed_target.blood_volume <= 0)
DeactivatePower()
return
owner.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
//play sound to target to show they're dying.
if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
feed_target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
/datum/action/cooldown/bloodsucker/feed/proc/find_target()
if(owner.pulling && isliving(owner.pulling))
if(!can_feed_from(owner.pulling, give_warnings = TRUE))
return FALSE
target_ref = WEAKREF(owner.pulling)
return TRUE
var/list/close_living_mobs = list()
var/list/close_dead_mobs = list()
for(var/mob/living/near_targets in oview(1, owner))
if(!owner.Adjacent(near_targets))
continue
if(near_targets.stat)
close_living_mobs |= near_targets
else
close_dead_mobs |= near_targets
//Check living first
for(var/mob/living/suckers in close_living_mobs)
if(can_feed_from(suckers))
target_ref = WEAKREF(suckers)
return TRUE
//If not, check dead
for(var/mob/living/suckers in close_dead_mobs)
if(can_feed_from(suckers))
target_ref = WEAKREF(suckers)
return TRUE
//No one to suck blood from.
return FALSE
/datum/action/cooldown/bloodsucker/feed/proc/can_feed_from(mob/living/target, give_warnings = FALSE)
if(istype(target, /mob/living/simple_animal/mouse) || istype(target, /mob/living/simple_animal/hostile/rat))
if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY)
if(give_warnings)
owner.balloon_alert(owner, "too disgusting!")
return FALSE
return TRUE
//Mice check done, only humans are otherwise allowed
if(!ishuman(target))
return FALSE
var/mob/living/carbon/human/target_user = target
if(!(target_user.dna?.species) || !(target_user.mob_biotypes & MOB_ORGANIC))
if(give_warnings)
owner.balloon_alert(owner, "no blood!")
return FALSE
if(!target_user.can_inject(owner, BODY_ZONE_HEAD))
if(give_warnings)
owner.balloon_alert(owner, "suit too thick!")
return FALSE
if((bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) && !target_user.mind && !bloodsuckerdatum_power.frenzied)
if(give_warnings)
owner.balloon_alert(owner, "cant drink from mindless!")
return FALSE
return TRUE
#undef FEED_NOTICE_RANGE
#undef FEED_DEFAULT_TIMER