3 * Summary: Slow projectiles, done as monsters.
4 * Written by: Adam Borowski
9 #include "mon-project.h"
23 #include "mgen_data.h"
24 #include "mon-place.h"
25 #include "mon-stuff.h"
32 static void _fuzz_direction(monster
& mon
, int pow
);
34 bool cast_iood(actor
*caster
, int pow
, bolt
*beam
)
36 int mtarg
= mgrd(beam
->target
);
37 if (beam
->target
== you
.pos())
41 for (int i
=0; i
< 10 && mind
== -1; i
++)
43 mind
= mons_place(mgen_data(MONS_ORB_OF_DESTRUCTION
,
44 (caster
->atype() == ACT_PLAYER
) ? BEH_FRIENDLY
:
45 ((monster
*)caster
)->friendly() ? BEH_FRIENDLY
: BEH_HOSTILE
,
56 mpr("Failed to spawn projectile.", MSGCH_WARN
);
57 /*canned_msg(MSG_NOTHING_HAPPENS);*/
61 monster
& mon
= menv
[mind
];
62 const coord_def pos
= caster
->pos();
64 dprf("beam (%d,%d)+t*(%d,%d) ray (%f,%f)+t*(%f,%f)",
65 pos
.x
, pos
.y
, beam
->target
.x
- pos
.x
, beam
->target
.y
- pos
.y
,
66 beam
->ray
.r
.start
.x
- 0.5, beam
->ray
.r
.start
.y
- 0.5,
67 beam
->ray
.r
.dir
.x
, beam
->ray
.r
.dir
.y
);
68 mon
.props
["iood_x"].get_float() = beam
->ray
.r
.start
.x
- 0.5;
69 mon
.props
["iood_y"].get_float() = beam
->ray
.r
.start
.y
- 0.5;
70 mon
.props
["iood_vx"].get_float() = beam
->ray
.r
.dir
.x
;
71 mon
.props
["iood_vy"].get_float() = beam
->ray
.r
.dir
.y
;
72 mon
.props
["iood_kc"].get_byte() = (caster
->atype() == ACT_PLAYER
) ? KC_YOU
:
73 ((monster
*)caster
)->friendly() ? KC_FRIENDLY
: KC_OTHER
;
74 mon
.props
["iood_pow"].get_short() = pow
;
75 mon
.flags
&= ~MF_JUST_SUMMONED
;
76 mon
.props
["iood_caster"].get_string() = caster
->as_monster()
77 ? caster
->name(DESC_PLAIN
, true)
79 mon
.props
["iood_mid"].get_int() = caster
->mid
;
81 _fuzz_direction(mon
, pow
);
83 // Move away from the caster's square.
85 // We need to take at least one full move (for the above), but let's
86 // randomize it and take more so players won't get guaranteed instant
88 mon
.lose_energy(EUT_MOVE
, 2, random2(3)+2);
92 static void _normalize(float &x
, float &y
)
94 const float d
= sqrt(x
*x
+ y
*y
);
101 // angle measured in chord length
102 static bool _in_front(float vx
, float vy
, float dx
, float dy
, float angle
)
104 return ((dx
-vx
)*(dx
-vx
) + (dy
-vy
)*(dy
-vy
) <= (angle
*angle
));
107 static void _iood_dissipate(monster
& mon
, bool msg
= true)
110 simple_monster_message(&mon
, " dissipates.");
111 dprf("iood: dissipating");
112 monster_die(&mon
, KILL_DISMISSED
, NON_MONSTER
);
115 static void _fuzz_direction(monster
& mon
, int pow
)
117 const float x
= mon
.props
["iood_x"];
118 const float y
= mon
.props
["iood_y"];
119 float vx
= mon
.props
["iood_vx"];
120 float vy
= mon
.props
["iood_vy"];
126 const float off
= (coinflip() ? -1 : 1) * 0.25;
127 float tan
= (random2(31) - 15) * 0.019; // approx from degrees
129 if (wearing_amulet(AMU_INACCURACY
))
132 // Cast either from left or right hand.
133 mon
.props
["iood_x"] = x
+ vy
*off
;
134 mon
.props
["iood_y"] = y
- vx
*off
;
135 // And off the direction a bit.
136 mon
.props
["iood_vx"] = vx
+ vy
*tan
;
137 mon
.props
["iood_vy"] = vy
- vx
*tan
;
140 // Alas, too much differs to reuse beam shield blocks :(
141 static bool _iood_shielded(monster
& mon
, actor
&victim
)
143 if (!victim
.shield() || victim
.incapacitated())
146 const int to_hit
= 15 + mon
.props
["iood_pow"].get_short()/12;
147 const int con_block
= random2(to_hit
+ victim
.shield_block_penalty());
148 const int pro_block
= victim
.shield_bonus();
149 dprf("iood shield: pro %d, con %d", pro_block
, con_block
);
150 return (pro_block
>= con_block
);
153 static bool _iood_hit(monster
& mon
, const coord_def
&pos
, bool big_boom
= false)
156 beam
.name
= "orb of destruction";
157 beam
.flavour
= BEAM_NUKE
;
158 beam
.attitude
= mon
.attitude
;
160 actor
*caster
= actor_by_mid(mon
.props
["iood_mid"].get_int());
161 if (!caster
) // caster is dead/gone, blame the orb itself (as its
162 caster
= &mon
; // friendliness is correct)
163 beam
.set_agent(caster
);
165 beam
.glyph
= dchar_glyph(DCHAR_FIRED_BURST
);
169 beam
.hit
= AUTOMATIC_HIT
;
170 beam
.source_name
= mon
.props
["iood_caster"].get_string();
172 int pow
= mon
.props
["iood_pow"].get_short();
173 pow
= stepdown_value(pow
, 30, 30, 200, -1);
174 const int dist
= mon
.props
["iood_distance"].get_int();
177 pow
= pow
* (dist
*2+3) / 10;
178 beam
.damage
= dice_def(9, pow
/ 4);
181 beam
.name
= "wavering " + beam
.name
;
183 beam
.hit_verb
= "weakly hits";
187 monster_die(&mon
, KILL_DISMISSED
, NON_MONSTER
);
190 beam
.explode(true, false);
197 // returns true if the orb is gone
198 bool iood_act(monster
& mon
, bool no_trail
)
200 ASSERT(mons_is_projectile(mon
.type
));
202 float x
= mon
.props
["iood_x"];
203 float y
= mon
.props
["iood_y"];
204 float vx
= mon
.props
["iood_vx"];
205 float vy
= mon
.props
["iood_vy"];
207 dprf("iood_act: pos=(%d,%d) rpos=(%f,%f) v=(%f,%f) foe=%d",
208 mon
.pos().x
, mon
.pos().y
,
209 x
, y
, vx
, vy
, mon
.foe
);
211 if (!vx
&& !vy
) // not initialized
213 _iood_dissipate(mon
);
219 const actor
*foe
= mon
.get_foe();
220 // If the target is gone, the orb continues on a ballistic course since
221 // picking a new one would require intelligence.
225 const coord_def target
= foe
->pos();
226 float dx
= target
.x
- x
;
227 float dy
= target
.y
- y
;
231 // Moving diagonally when the orb is just about to hit you
234 // (from 1 to 2) would be a guaranteed escape. This may be
235 // realistic (strafing!), but since the game has no non-cheesy
236 // means of waiting a small fraction of a turn, we don't want it.
237 const int old_t_pos
= mon
.props
["iood_tpos"].get_short();
238 const coord_def
rpos(static_cast<int>(round(x
)), static_cast<int>(round(y
)));
239 if (old_t_pos
&& old_t_pos
!= (256 * target
.x
+ target
.y
)
240 && (rpos
- target
).rdist() <= 1
241 // ... but following an orb is ok.
242 && _in_front(vx
, vy
, dx
, dy
, 1.5)) // ~97 degrees
247 mon
.props
["iood_tpos"].get_short() = 256 * target
.x
+ target
.y
;
249 if (!_in_front(vx
, vy
, dx
, dy
, 0.3)) // ~17 degrees
253 ax
= vy
, ay
= -vx
, dprf("iood: veering left");
255 ax
= -vy
, ay
= vx
, dprf("iood: veering right");
260 dprf("iood: keeping course");
263 mon
.props
["iood_vx"] = vx
;
264 mon
.props
["iood_vy"] = vy
;
272 mon
.props
["iood_x"] = x
;
273 mon
.props
["iood_y"] = y
;
274 mon
.props
["iood_distance"].get_int()++;
276 const coord_def
pos(static_cast<int>(round(x
)), static_cast<int>(round(y
)));
279 _iood_dissipate(mon
);
283 if (mon
.props
["iood_kc"].get_byte() == KC_YOU
284 && (you
.pos() - pos
).rdist() > LOS_RADIUS
)
285 { // not actual vision, because of the smoke trail
286 _iood_dissipate(mon
);
290 if (pos
== mon
.pos())
294 place_cloud(CLOUD_MAGIC_TRAIL
, mon
.pos(), 2 + random2(3), &mon
);
296 actor
*victim
= actor_at(pos
);
297 if (cell_is_solid(pos
) || victim
)
299 if (cell_is_solid(pos
))
301 if (you
.see_cell(pos
))
302 mprf("%s hits %s", mon
.name(DESC_CAP_THE
, true).c_str(),
303 feature_description(pos
, false, DESC_NOCAP_A
).c_str());
306 monster
* mons
= (victim
&& victim
->atype() == ACT_MONSTER
) ?
307 (monster
*) victim
: 0;
309 if (mons
&& mons_is_projectile(victim
->type
))
311 if (mon
.observable())
312 mpr("The orbs collide in a blinding explosion!");
314 noisy(40, pos
, "You hear a loud magical explosion!");
315 monster_die(mons
, KILL_DISMISSED
, NON_MONSTER
);
316 _iood_hit(mon
, pos
, true);
320 if (mons
&& mons
->submerged())
322 // Try to swap with the submerged creature.
323 if (mons
->is_habitable(mon
.pos()))
325 dprf("iood: Swapping with a submerged monster.");
326 mons
->set_position(mon
.pos());
327 mon
.set_position(pos
);
328 mgrd(mons
->pos()) = mons
->mindex();
329 mgrd(pos
) = mon
.mindex();
333 else // if swap fails, move ahead
335 dprf("iood: Boosting above a submerged monster (can't swap).");
336 mon
.lose_energy(EUT_MOVE
);
341 if (victim
&& _iood_shielded(mon
, *victim
))
343 item_def
*shield
= victim
->shield();
344 if (!shield_reflects(*shield
))
346 if (victim
->atype() == ACT_PLAYER
)
348 mprf("You block %s.", mon
.name(DESC_NOCAP_THE
, true).c_str());
352 simple_monster_message(mons
, (" blocks "
353 + mon
.name(DESC_NOCAP_THE
, true) + ".").c_str());
355 victim
->shield_block_succeeded(&mon
);
356 _iood_dissipate(mon
);
360 if (victim
->atype() == ACT_PLAYER
)
362 mprf("Your %s reflects %s!",
363 shield
->name(DESC_PLAIN
).c_str(),
364 mon
.name(DESC_NOCAP_THE
, true).c_str());
365 ident_reflector(shield
);
367 else if (you
.see_cell(pos
))
369 if (victim
->observable())
371 mprf("%s reflects %s with %s %s!",
372 victim
->name(DESC_CAP_THE
, true).c_str(),
373 mon
.name(DESC_NOCAP_THE
, true).c_str(),
374 mon
.pronoun(PRONOUN_NOCAP_POSSESSIVE
).c_str(),
375 shield
->name(DESC_PLAIN
).c_str());
376 ident_reflector(shield
);
380 mprf("%s bounces off thin air!",
381 mon
.name(DESC_CAP_THE
, true).c_str());
384 victim
->shield_block_succeeded(&mon
);
386 mon
.props
["iood_vx"] = vx
= -vx
;
387 mon
.props
["iood_vy"] = vy
= -vy
;
389 // Need to get out of the victim's square.
391 // If you're next to the caster and both of you wear shields of
392 // reflection, this can lead to a brief game of ping-pong, but
393 // rapidly increasing shield penalties will make it short.
394 mon
.lose_energy(EUT_MOVE
);
398 // Yay for inconsistencies in beam-vs-player and beam-vs-monsters.
400 mprf("%s hits you!", mon
.name(DESC_CAP_THE
, true).c_str());
402 if (_iood_hit(mon
, pos
))
406 if (!mon
.move_to_pos(pos
))
408 _iood_dissipate(mon
);
412 // move_to_pos() just trashed the coords, set them again
413 mon
.props
["iood_x"] = x
;
414 mon
.props
["iood_y"] = y
;
419 // Reduced copy of iood_act to move the orb while the player
420 // is off-level. Just goes straight and dissipates instead of
422 static bool _iood_catchup_move(monster
& mon
)
424 float x
= mon
.props
["iood_x"];
425 float y
= mon
.props
["iood_y"];
426 float vx
= mon
.props
["iood_vx"];
427 float vy
= mon
.props
["iood_vy"];
429 if (!vx
&& !vy
) // not initialized
431 _iood_dissipate(mon
, false);
440 mon
.props
["iood_x"] = x
;
441 mon
.props
["iood_y"] = y
;
442 mon
.props
["iood_distance"].get_int()++;
444 const coord_def
pos(static_cast<int>(round(x
)), static_cast<int>(round(y
)));
447 _iood_dissipate(mon
, true);
451 if (pos
== mon
.pos())
454 actor
*victim
= actor_at(pos
);
455 if (cell_is_solid(pos
) || victim
)
457 // Just dissipate instead of hitting something.
458 _iood_dissipate(mon
, true);
462 if (!mon
.move_to_pos(pos
))
464 _iood_dissipate(mon
);
468 // move_to_pos() just trashed the coords, set them again
469 mon
.props
["iood_x"] = x
;
470 mon
.props
["iood_y"] = y
;
475 void iood_catchup(monster
* mons
, int pturns
)
477 monster
& mon
= *mons
;
478 ASSERT(mons_is_projectile(mon
.type
));
480 const int moves
= pturns
* mon
.speed
/ BASELINE_DELAY
;
484 _iood_dissipate(mon
, false);
488 if (mon
.props
["iood_kc"].get_byte() == KC_YOU
)
490 // Left player's vision.
491 _iood_dissipate(mon
, false);
496 for (int i
= 0; i
< moves
; ++i
)
497 if (_iood_catchup_move(mon
))