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, &paralyze);
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