/
charge.dm
287 lines (251 loc) · 11.1 KB
/
charge.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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/datum/action/cooldown/mob_cooldown/charge
name = "Charge"
button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Allows you to charge at a chosen position."
cooldown_time = 1.5 SECONDS
/// Delay before the charge actually occurs
var/charge_delay = 0.3 SECONDS
/// The amount of turfs we move past the target
var/charge_past = 2
/// The maximum distance we can charge
var/charge_distance = 50
/// The sleep time before moving in deciseconds while charging
var/charge_speed = 0.5
/// The damage the charger does when bumping into something
var/charge_damage = 30
/// If we destroy objects while charging
var/destroy_objects = TRUE
/// If the current move is being triggered by us or not
var/actively_moving = FALSE
/// List of charging mobs
var/list/charging = list()
/datum/action/cooldown/mob_cooldown/charge/Activate(atom/target_atom)
StartCooldown(360 SECONDS, 360 SECONDS)
charge_sequence(owner, target_atom, charge_delay, charge_past)
StartCooldown()
return TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
do_charge(owner, target_atom, charge_delay, charge_past)
/datum/action/cooldown/mob_cooldown/charge/proc/do_charge(atom/movable/charger, atom/target_atom, delay, past)
if(!target_atom || target_atom == owner)
return
var/chargeturf = get_turf(target_atom)
if(!chargeturf)
return
var/dir = get_dir(charger, target_atom)
var/turf/target = get_ranged_target_turf(chargeturf, dir, past)
if(!target)
return
if(charger in charging)
// Stop any existing charging, this'll clean things up properly
SSmove_manager.stop_looping(charger)
charging += charger
actively_moving = FALSE
SEND_SIGNAL(owner, COMSIG_STARTED_CHARGE)
RegisterSignal(charger, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump), TRUE)
RegisterSignal(charger, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move), TRUE)
RegisterSignal(charger, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), TRUE)
DestroySurroundings(charger)
charger.setDir(dir)
do_charge_indicator(charger, target)
SLEEP_CHECK_DEATH(delay, charger)
var/time_to_hit = min(get_dist(charger, target), charge_distance) * charge_speed
var/datum/move_loop/new_loop = SSmove_manager.home_onto(charger, target, delay = charge_speed, timeout = time_to_hit, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
if(!new_loop)
return
RegisterSignal(new_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move))
RegisterSignal(new_loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move))
RegisterSignal(new_loop, COMSIG_PARENT_QDELETING, PROC_REF(charge_end))
if(ismob(charger))
RegisterSignal(charger, COMSIG_MOB_STATCHANGE, PROC_REF(stat_changed))
// Yes this is disgusting. But we need to queue this stuff, and this code just isn't setup to support that right now. So gotta do it with sleeps
sleep(time_to_hit + charge_speed)
charger.setDir(dir)
return TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/pre_move(datum)
SIGNAL_HANDLER
// If you sleep in Move() you deserve what's coming to you
actively_moving = TRUE
/datum/action/cooldown/mob_cooldown/charge/proc/post_move(datum)
SIGNAL_HANDLER
actively_moving = FALSE
/datum/action/cooldown/mob_cooldown/charge/proc/charge_end(datum/move_loop/source)
SIGNAL_HANDLER
var/atom/movable/charger = source.moving
UnregisterSignal(charger, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_STATCHANGE))
SEND_SIGNAL(owner, COMSIG_FINISHED_CHARGE)
actively_moving = FALSE
charging -= charger
/datum/action/cooldown/mob_cooldown/charge/proc/stat_changed(mob/source, new_stat, old_stat)
SIGNAL_HANDLER
if(new_stat == DEAD)
SSmove_manager.stop_looping(source) //This will cause the loop to qdel, triggering an end to our charging
/datum/action/cooldown/mob_cooldown/charge/proc/do_charge_indicator(atom/charger, atom/charge_target)
var/turf/target_turf = get_turf(charge_target)
if(!target_turf)
return
new /obj/effect/temp_visual/dragon_swoop/bubblegum(target_turf)
var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(charger.loc, charger)
animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3)
/datum/action/cooldown/mob_cooldown/charge/proc/on_move(atom/source, atom/new_loc)
SIGNAL_HANDLER
if(!actively_moving)
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
new /obj/effect/temp_visual/decoy/fading(source.loc, source)
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
/datum/action/cooldown/mob_cooldown/charge/proc/on_moved(atom/source)
SIGNAL_HANDLER
playsound(source, 'sound/effects/meteorimpact.ogg', 200, TRUE, 2, TRUE)
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
/datum/action/cooldown/mob_cooldown/charge/proc/DestroySurroundings(atom/movable/charger)
if(!destroy_objects)
return
if(!isanimal(charger))
return
for(var/dir in GLOB.cardinals)
var/turf/next_turf = get_step(charger, dir)
if(!next_turf)
continue
if(next_turf.Adjacent(charger) && (iswallturf(next_turf) || ismineralturf(next_turf)))
if(!isanimal(charger))
SSexplosions.medturf += next_turf
continue
next_turf.attack_animal(charger)
continue
for(var/obj/object in next_turf.contents)
if(!object.Adjacent(charger))
continue
if(!ismachinery(object) && !isstructure(object))
continue
if(!object.density || object.IsObscured())
continue
if(!isanimal(charger))
SSexplosions.med_mov_atom += target
break
object.attack_animal(charger)
break
/datum/action/cooldown/mob_cooldown/charge/proc/on_bump(atom/movable/source, atom/target)
SIGNAL_HANDLER
if(owner == target)
return
if(destroy_objects)
if(isturf(target))
SSexplosions.medturf += target
if(isobj(target) && target.density)
SSexplosions.med_mov_atom += target
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
hit_target(source, target, charge_damage)
/datum/action/cooldown/mob_cooldown/charge/proc/hit_target(atom/movable/source, atom/target, damage_dealt)
if(!isliving(target))
return
var/mob/living/living_target = target
living_target.visible_message("<span class='danger'>[source] slams into [living_target]!</span>", "<span class='userdanger'>[source] tramples you into the ground!</span>")
source.forceMove(get_turf(living_target))
living_target.apply_damage(damage_dealt, BRUTE, wound_bonus = CANT_WOUND)
playsound(get_turf(living_target), 'sound/effects/meteorimpact.ogg', 100, TRUE)
shake_camera(living_target, 4, 3)
shake_camera(source, 2, 3)
/datum/action/cooldown/mob_cooldown/charge/basic_charge
name = "Basic Charge"
cooldown_time = 6 SECONDS
charge_delay = 1.5 SECONDS
charge_distance = 4
/// How long to shake for before charging
var/shake_duration = 1 SECONDS
/// Intensity of shaking animation
var/shake_pixel_shift = 2
/datum/action/cooldown/mob_cooldown/charge/basic_charge/do_charge_indicator(atom/charger, atom/charge_target)
charger.Shake(shake_pixel_shift, shake_pixel_shift, shake_duration)
/datum/action/cooldown/mob_cooldown/charge/basic_charge/hit_target(atom/movable/source, atom/target, damage_dealt)
var/mob/living/living_source
if(isliving(source))
living_source = source
if(!isliving(target))
if(!target.density || target.CanPass(source, get_dir(target, source)))
return
source.visible_message(span_danger("[source] smashes into [target]!"))
if(!living_source)
return
living_source.Stun(6, ignore_canstun = TRUE)
return
var/mob/living/living_target = target
if(ishuman(living_target))
var/mob/living/carbon/human/human_target = living_target
if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK) && living_source)
living_source.Stun(6, ignore_canstun = TRUE)
return
living_target.visible_message(span_danger("[source] charges on [living_target]!"), span_userdanger("[source] charges into you!"))
living_target.Knockdown(6)
/datum/action/cooldown/mob_cooldown/charge/triple_charge
name = "Triple Charge"
desc = "Allows you to charge three times at a chosen position."
charge_delay = 0.6 SECONDS
/datum/action/cooldown/mob_cooldown/charge/triple_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
for(var/i in 0 to 2)
do_charge(owner, target_atom, charge_delay - 2 * i, charge_past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge
name = "Hallucination Charge"
button_icon = 'icons/effects/bubblegum.dmi'
button_icon_state = "smack ya one"
desc = "Allows you to create hallucinations that charge around your target."
cooldown_time = 2 SECONDS
charge_delay = 0.6 SECONDS
/// The damage the hallucinations in our charge do
var/hallucination_damage = 15
/// Check to see if we are enraged, enraged ability does more
var/enraged = FALSE
/// Check to see if we should spawn blood
var/spawn_blood = FALSE
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
if(!enraged || prob(33))
hallucination_charge(target_atom, 6, 8, 0, 6, TRUE)
return
for(var/i in 0 to 2)
hallucination_charge(target_atom, 4, 9 - 2 * i, 0, 4, TRUE)
for(var/i in 0 to 2)
do_charge(owner, target_atom, charge_delay - 2 * i, charge_past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/do_charge(atom/movable/charger, atom/target_atom, delay, past)
. = ..()
if(charger != owner)
qdel(charger)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/proc/hallucination_charge(atom/target_atom, clone_amount, delay, past, radius, use_self)
var/starting_angle = rand(1, 360)
if(!radius)
return
var/angle_difference = 360 / clone_amount
var/self_placed = FALSE
for(var/i = 1 to clone_amount)
var/angle = (starting_angle + angle_difference * i)
var/turf/place = locate(target_atom.x + cos(angle) * radius, target_atom.y + sin(angle) * radius, target_atom.z)
if(!place)
continue
if(use_self && !self_placed)
owner.forceMove(place)
self_placed = TRUE
continue
var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/our_clone = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(place)
our_clone.appearance = owner.appearance
our_clone.name = "[owner]'s hallucination"
our_clone.alpha = 127.5
our_clone.move_through_mob = owner
our_clone.spawn_blood = spawn_blood
INVOKE_ASYNC(src, PROC_REF(do_charge), our_clone, target_atom, delay, past)
if(use_self)
do_charge(owner, target_atom, delay, past)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hit_target(atom/movable/source, atom/A, damage_dealt)
var/applied_damage = charge_damage
if(source != owner)
applied_damage = hallucination_damage
. = ..(source, A, applied_damage)
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround
name = "Surround Target"
button_icon = 'icons/turf/walls/wall.dmi'
button_icon_state = "wall-0"
desc = "Allows you to create hallucinations that charge around your target."
charge_delay = 0.6 SECONDS
charge_past = 2
/datum/action/cooldown/mob_cooldown/charge/hallucination_charge/hallucination_surround/charge_sequence(atom/movable/charger, atom/target_atom, delay, past)
for(var/i in 0 to 4)
hallucination_charge(target_atom, 2, 8, 2, 2, FALSE)
do_charge(owner, target_atom, charge_delay, charge_past)