1 /*
2 * Npctime.cc - Timed-even handlers for NPC's.
3 *
4 * Copyright (C) 2000-2013 The Exult Team
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include "npctime.h"
26 #include "gamewin.h"
27 #include "gamemap.h"
28 #include "gameclk.h"
29 #include "actors.h"
30 #include "items.h"
31 #include "schedule.h"
32 #include "game.h"
33 #include "ignore_unused_variable_warning.h"
34
35 using std::rand;
36
37 extern bool god_mode;
38
39 /*
40 * Base class for keeping track of things like poison, protection, hunger.
41 */
42 class Npc_timer : public Time_sensitive, public Game_singletons {
43 protected:
44 Npc_timer_list *list; // Where NPC stores ->this.
45 uint32 get_minute(); // Get game minutes.
46 public:
47 Npc_timer(Npc_timer_list *l, int start_delay = 0);
48 ~Npc_timer() override;
49 };
50
51 /*
52 * Handle starvation.
53 */
54 class Npc_hunger_timer : public Npc_timer {
55 uint32 last_time; // Last game minute when penalized.
56 public:
Npc_hunger_timer(Npc_timer_list * l)57 Npc_hunger_timer(Npc_timer_list *l) : Npc_timer(l, 5000) {
58 last_time = get_minute();
59 }
60 ~Npc_hunger_timer() override;
61 // Handle events:
62 void handle_event(unsigned long curtime, uintptr udata) override;
63 };
64
65 /*
66 * Handle poison.
67 */
68 class Npc_poison_timer : public Npc_timer {
69 uint32 end_time; // Time when it wears off.
70 public:
71 Npc_poison_timer(Npc_timer_list *l);
72 ~Npc_poison_timer() override;
73 // Handle events:
74 void handle_event(unsigned long curtime, uintptr udata) override;
75 };
76
77 /*
78 * Handle sleep.
79 */
80 class Npc_sleep_timer : public Npc_timer {
81 uint32 end_time; // Time when it wears off.
82 public:
Npc_sleep_timer(Npc_timer_list * l)83 Npc_sleep_timer(Npc_timer_list *l) : Npc_timer(l) {
84 // Lasts 5-10 seconds..
85 end_time = Game::get_ticks() + 5000 + rand() % 5000;
86 }
~Npc_sleep_timer()87 ~Npc_sleep_timer() override {
88 list->sleep = nullptr;
89 }
90 // Handle events:
91 void handle_event(unsigned long curtime, uintptr udata) override;
92 };
93
94 /*
95 * Invisibility timer.
96 */
97 class Npc_invisibility_timer : public Npc_timer {
98 uint32 end_time; // Time when it wears off.
99 public:
Npc_invisibility_timer(Npc_timer_list * l)100 Npc_invisibility_timer(Npc_timer_list *l) : Npc_timer(l) {
101 // Lasts 60-80 seconds..
102 end_time = Game::get_ticks() + 60000 + rand() % 20000;
103 }
~Npc_invisibility_timer()104 ~Npc_invisibility_timer() override {
105 list->invisibility = nullptr;
106 }
107 // Handle events:
108 void handle_event(unsigned long curtime, uintptr udata) override;
109 };
110
111 /*
112 * Protection timer.
113 */
114 class Npc_protection_timer : public Npc_timer {
115 uint32 end_time; // Time when it wears off.
116 public:
Npc_protection_timer(Npc_timer_list * l)117 Npc_protection_timer(Npc_timer_list *l) : Npc_timer(l) {
118 // Lasts 60-80 seconds..
119 end_time = Game::get_ticks() + 60000 + rand() % 20000;
120 }
~Npc_protection_timer()121 ~Npc_protection_timer() override {
122 list->protection = nullptr;
123 }
124 // Handle events:
125 void handle_event(unsigned long curtime, uintptr udata) override;
126 };
127
128 /*
129 * Timer for flags that don't need any other checks.
130 */
131 class Npc_flag_timer : public Npc_timer {
132 int flag; // Flag # in Obj_flags.
133 uint32 end_time; // Time when it wears off.
134 Npc_flag_timer **listloc; // Where it's stored in Npc_timer_list.
135 public:
Npc_flag_timer(Npc_timer_list * l,int f,Npc_flag_timer ** loc)136 Npc_flag_timer(Npc_timer_list *l, int f, Npc_flag_timer **loc)
137 : Npc_timer(l), flag(f), listloc(loc) {
138 // Lasts 60-120 seconds..
139 end_time = Game::get_ticks() + 60000 + rand() % 60000;
140 }
~Npc_flag_timer()141 ~Npc_flag_timer() override {
142 *listloc = nullptr;
143 }
144 // Handle events:
145 void handle_event(unsigned long curtime, uintptr udata) override;
146 };
147
148
149 /*
150 * Delete list.
151 */
152
~Npc_timer_list()153 Npc_timer_list::~Npc_timer_list(
154 ) {
155 delete hunger;
156 delete poison;
157 delete sleep;
158 delete invisibility;
159 delete protection;
160 delete might;
161 delete curse;
162 delete charm;
163 delete paralyze;
164 }
165
166 /*
167 * Start hunger (if not already there).
168 */
169
start_hunger()170 void Npc_timer_list::start_hunger(
171 ) {
172 if (!hunger) // Not already there?
173 hunger = new Npc_hunger_timer(this);
174 }
175
176 /*
177 * Start poison.
178 */
179
start_poison()180 void Npc_timer_list::start_poison(
181 ) {
182 delete poison;
183 poison = new Npc_poison_timer(this);
184 }
185
186 /*
187 * Start sleep.
188 */
189
start_sleep()190 void Npc_timer_list::start_sleep(
191 ) {
192 delete sleep;
193 sleep = new Npc_sleep_timer(this);
194 }
195
196 /*
197 * Start invisibility.
198 */
199
start_invisibility()200 void Npc_timer_list::start_invisibility(
201 ) {
202 delete invisibility;
203 invisibility = new Npc_invisibility_timer(this);
204 }
205
206 /*
207 * Start protection.
208 */
209
start_protection()210 void Npc_timer_list::start_protection(
211 ) {
212 delete protection;
213 protection = new Npc_protection_timer(this);
214 }
215
216
217 /*
218 * Start might.
219 */
220
start_might()221 void Npc_timer_list::start_might(
222 ) {
223 delete might;
224 might = new Npc_flag_timer(this, Obj_flags::might, &might);
225 }
226
227 /*
228 * Start curse.
229 */
230
start_curse()231 void Npc_timer_list::start_curse(
232 ) {
233 delete curse;
234 curse = new Npc_flag_timer(this, Obj_flags::cursed, &curse);
235 }
236
237 /*
238 * Start charm.
239 */
240
start_charm()241 void Npc_timer_list::start_charm(
242 ) {
243 delete charm;
244 charm = new Npc_flag_timer(this, Obj_flags::charmed, &charm);
245 }
246
247 /*
248 * Start paralyze.
249 */
250
start_paralyze()251 void Npc_timer_list::start_paralyze(
252 ) {
253 delete paralyze;
254 paralyze = new Npc_flag_timer(this, Obj_flags::paralyzed, ¶lyze);
255 }
256
257 /*
258 * Get game minute from start.
259 */
260
get_minute()261 uint32 Npc_timer::get_minute(
262 ) {
263 return 60 * gclock->get_total_hours() + gclock->get_minute();
264 }
265
266 /*
267 * Start timer.
268 */
269
Npc_timer(Npc_timer_list * l,int start_delay)270 Npc_timer::Npc_timer(
271 Npc_timer_list *l,
272 int start_delay // Time in msecs. before starting.
273 ) : list(l) {
274 gwin->get_tqueue()->add(Game::get_ticks() + start_delay, this);
275 }
276
277 /*
278 * Be sure we're no longer in the time queue.
279 */
280
~Npc_timer()281 Npc_timer::~Npc_timer(
282 ) {
283 if (in_queue()) {
284 Time_queue *tq = Game_window::get_instance()->get_tqueue();
285 tq->remove(this);
286 }
287 }
288
289 /*
290 * Done with hunger timer.
291 */
292
~Npc_hunger_timer()293 Npc_hunger_timer::~Npc_hunger_timer(
294 ) {
295 list->hunger = nullptr;
296 }
297
298 /*
299 * Time to penalize for hunger.
300 */
301
handle_event(unsigned long curtime,uintptr udata)302 void Npc_hunger_timer::handle_event(
303 unsigned long curtime,
304 uintptr udata
305 ) {
306 ignore_unused_variable_warning(udata);
307 Actor *npc = list->npc;
308 int food = npc->get_property(static_cast<int>(Actor::food_level));
309 // No longer a party member?
310 if (!npc->is_in_party() ||
311 // or no longer hungry?
312 food > 9 ||
313 npc->is_dead()) { // Obviously.
314 delete this;
315 return;
316 }
317 uint32 minute = get_minute();
318 // Once/minute.
319 if (minute != last_time) {
320 if (!npc->is_knocked_out()) {
321 if ((rand() % 3) == 0)
322 npc->say_hunger_message();
323 if (food <= 0 && (rand() % 5) < 2)
324 npc->reduce_health(1, Weapon_data::sonic_damage);
325 }
326 last_time = minute;
327 }
328 gwin->get_tqueue()->add(curtime + 5000, this);
329 }
330
331 /*
332 * Initialize poison timer.
333 */
334
Npc_poison_timer(Npc_timer_list * l)335 Npc_poison_timer::Npc_poison_timer(
336 Npc_timer_list *l
337 ) : Npc_timer(l, 5000) {
338 // Lasts 1-3 minutes.
339 end_time = Game::get_ticks() + 60000 + rand() % 120000;
340 }
341
342 /*
343 * Done with poison timer.
344 */
345
~Npc_poison_timer()346 Npc_poison_timer::~Npc_poison_timer(
347 ) {
348 list->poison = nullptr;
349 }
350
351 /*
352 * Time to penalize for poison, or see if it's worn off.
353 */
354
handle_event(unsigned long curtime,uintptr udata)355 void Npc_poison_timer::handle_event(
356 unsigned long curtime,
357 uintptr udata
358 ) {
359 ignore_unused_variable_warning(udata);
360 Actor *npc = list->npc;
361 if (curtime >= end_time || // Long enough? Or cured?
362 !npc->get_flag(Obj_flags::poisoned) ||
363 npc->is_dead()) { // Obviously.
364 npc->clear_flag(Obj_flags::poisoned);
365 delete this;
366 return;
367 }
368 int penalty = rand() % 3;
369 npc->reduce_health(penalty, Weapon_data::sonic_damage);
370
371 // npc->set_property(static_cast<int>(Actor::health),
372 // npc->get_property(static_cast<int>(Actor::health)) - penalty);
373 // Check again in 10-20 secs.
374 gwin->get_tqueue()->add(curtime + 10000 + rand() % 10000, this);
375 }
376
377 /*
378 * Time to see if we should wake up.
379 */
380
handle_event(unsigned long curtime,uintptr udata)381 void Npc_sleep_timer::handle_event(
382 unsigned long curtime,
383 uintptr udata
384 ) {
385 ignore_unused_variable_warning(udata);
386 Actor *npc = list->npc;
387 if (npc->get_property(static_cast<int>(Actor::health)) <= 0) {
388 if (npc->is_in_party() || gmap->is_chunk_read(npc->get_cx(), npc->get_cy())) {
389 // 1 in 6 every half minute = approx. 1 HP every 3 min.
390 if (rand() % 6 == 0)
391 npc->mend_wounds(false);
392 } else { // If not nearby, and not in party, just set health and mana to full.
393 npc->set_property(static_cast<int>(Actor::health),
394 npc->get_property(static_cast<int>(Actor::strength)));
395 npc->set_property(static_cast<int>(Actor::mana),
396 npc->get_property(static_cast<int>(Actor::magic)));
397 }
398 }
399 // Don't wake up someone beaten into unconsciousness.
400 if (npc->get_property(static_cast<int>(Actor::health)) >= 1
401 && (curtime >= end_time || // Long enough? Or cured?
402 !npc->get_flag(Obj_flags::asleep))
403 ) {
404 // Avoid waking sleeping people.
405 if (npc->get_schedule_type() == Schedule::sleep)
406 npc->clear_sleep();
407 else if (!npc->is_dead()) { // Don't wake the dead.
408 npc->clear_flag(Obj_flags::asleep);
409 int frnum = npc->get_framenum();
410 if ((frnum & 0xf) == Actor::sleep_frame &&
411 // Slimes don't change.
412 !npc->get_info().has_strange_movement())
413 // Stand up.
414 npc->change_frame(
415 Actor::standing | (frnum & 0x30));
416 }
417 delete this;
418 return;
419 }
420 // Check again every half a game minute.
421 gwin->get_tqueue()->add(curtime + (ticks_per_minute * gwin->get_std_delay()) / 2, this);
422 }
423
424 /*
425 * Check for a given ring.
426 */
427
Wearing_ring(Actor * actor,int shnum,int frnum)428 inline int Wearing_ring(
429 Actor *actor,
430 int shnum, // Ring shape to look for.
431 int frnum
432 ) {
433 // See if wearing ring.
434 Game_object *ring = actor->get_readied(lfinger);
435 if (ring && ring->get_shapenum() == shnum &&
436 ring->get_framenum() == frnum)
437 return 1;
438 ring = actor->get_readied(rfinger);
439 if (ring && ring->get_shapenum() == shnum &&
440 ring->get_framenum() == frnum)
441 return 1;
442 return 0;
443 }
444
445 /*
446 * See if invisibility wore off.
447 */
448
handle_event(unsigned long curtime,uintptr udata)449 void Npc_invisibility_timer::handle_event(
450 unsigned long curtime,
451 uintptr udata
452 ) {
453 ignore_unused_variable_warning(udata);
454 Actor *npc = list->npc;
455 if (Wearing_ring(npc, 296, 0)) { // (Works for SI and BG.)
456 // Wearing invisibility ring.
457 delete this; // Don't need timer.
458 return;
459 }
460 if (curtime >= end_time || // Long enough? Or cleared.
461 !npc->get_flag(Obj_flags::invisible)) {
462 npc->clear_flag(Obj_flags::invisible);
463 if (!npc->is_dead())
464 gwin->add_dirty(npc);
465 delete this;
466 return;
467 }
468 // Check again in 2 secs.
469 gwin->get_tqueue()->add(curtime + 2000, this);
470 }
471
472 /*
473 * See if protection wore off.
474 */
475
handle_event(unsigned long curtime,uintptr udata)476 void Npc_protection_timer::handle_event(
477 unsigned long curtime,
478 uintptr udata
479 ) {
480 ignore_unused_variable_warning(udata);
481 Actor *npc = list->npc;
482 if (Wearing_ring(npc, 297, 0)) { // ++++SI has an Amulet.
483 // Wearing protection ring.
484 delete this; // Don't need timer.
485 return;
486 }
487 if (curtime >= end_time || // Long enough? Or cleared.
488 !npc->get_flag(Obj_flags::protection)) {
489 npc->clear_flag(Obj_flags::protection);
490 if (!npc->is_dead())
491 gwin->add_dirty(npc);
492 delete this;
493 return;
494 }
495 // Check again in 2 secs.
496 gwin->get_tqueue()->add(curtime + 2000, this);
497 }
498
499 /*
500 * Might/curse/charm/paralyze wore off.
501 */
502
handle_event(unsigned long curtime,uintptr udata)503 void Npc_flag_timer::handle_event(
504 unsigned long curtime,
505 uintptr udata
506 ) {
507 ignore_unused_variable_warning(udata);
508 Actor *npc = list->npc;
509 if (curtime >= end_time || // Long enough? Or cleared.
510 !npc->get_flag(flag)) {
511 npc->clear_flag(flag);
512 delete this;
513 } else // Check again in 10 secs.
514 gwin->get_tqueue()->add(curtime + 10000, this);
515 }
516
517