1 /*
2  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
3  *  Copyright (C) 2000-2013  The Exult Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23 
24 #include <memory>
25 
26 #include "Audio.h"
27 #include "monsters.h"
28 #include "cheat.h"
29 #include "chunks.h"
30 #include "animate.h"
31 #include "effects.h"
32 #include "egg.h"
33 #include "exult.h"
34 #include "game.h"
35 #include "gameclk.h"
36 #include "gamewin.h"
37 #include "gamemap.h"
38 #include "npctime.h"
39 #include "paths.h"
40 #include "ucmachine.h"
41 #include "ucscriptop.h"
42 #include "ucsched.h"
43 #include "Gump_manager.h"
44 #include "databuf.h"
45 #include "shapeinf.h"
46 #include "weaponinf.h"
47 #include "ignore_unused_variable_warning.h"
48 
49 #ifdef USE_EXULTSTUDIO
50 #include "server.h"
51 #include "objserial.h"
52 #include "mouse.h"
53 #include "servemsg.h"
54 #endif
55 
56 using std::cout;
57 using std::cerr;
58 using std::endl;
59 using std::rand;
60 using std::ostream;
61 using std::string;
62 
63 Game_object_shared Egg_object::editing;
64 
65 /*
66  *  Timer for a missile egg (type-6 egg).
67  */
68 class Missile_launcher : public Time_sensitive, public Game_singletons {
69 	Egg_object *egg;        // Egg this came from.
70 	int weapon;         // Shape for weapon.
71 	int shapenum;           // Shape for missile.
72 	int dir;            // Direction (0-7).  (8==??).
73 	int delay;          // Delay (msecs) between launches.
74 	int range;
75 	bool chk_range;     // For party near and avatar near.
76 public:
Missile_launcher(Egg_object * e,int weap,int shnum,int di,int del)77 	Missile_launcher(Egg_object *e, int weap, int shnum, int di, int del)
78 		: egg(e), weapon(weap), shapenum(shnum), dir(di), delay(del), range(20) {
79 		const Weapon_info *winfo = ShapeID::get_info(weapon).get_weapon_info();
80 		// Guessing.
81 		if (winfo) {
82 			range = winfo->get_range();
83 		}
84 		int crit = e->get_criteria();
85 		chk_range = crit == Egg_object::party_near || crit == Egg_object::avatar_near;
86 	}
87 	void handle_event(unsigned long curtime, uintptr udata) override;
88 };
89 
90 /*
91  *  Launch a missile.
92  */
93 
handle_event(unsigned long curtime,uintptr udata)94 void Missile_launcher::handle_event(
95     unsigned long curtime,
96     uintptr udata
97 ) {
98 	Tile_coord src = egg->get_tile();
99 	// Is egg a ways off the screen?
100 	if (!gwin->get_win_tile_rect().enlarge(10).has_world_point(src.tx, src.ty))
101 		return;         // Return w'out adding back to queue.
102 	// Add back to queue for next time.
103 	if (chk_range) {
104 		Actor *main_actor = gwin->get_main_actor();
105 		if (!main_actor || main_actor->distance(egg) > range) {
106 			return;
107 		}
108 	}
109 	std::unique_ptr<Projectile_effect> proj{nullptr};
110 	if (dir < 8) {          // Direction given?
111 		// Get adjacent tile in direction.
112 		Tile_coord adj = src.get_neighbor(dir % 8);
113 		// Make it go (range) tiles.
114 		int dx = adj.tx - src.tx;
115 		int dy = adj.ty - src.ty;
116 		Tile_coord dest = src;
117 		dest.tx += range * dx;
118 		dest.ty += range * dy;
119 		proj = std::make_unique<Projectile_effect>(egg,
120 				dest, weapon, shapenum, shapenum);
121 	} else {            // Target a party member.
122 		Actor *party[9];
123 		int psize = gwin->get_party(party, 1);
124 		int cnt = psize;
125 		int n = rand() % psize; // Pick one at random.
126 		// Find one we can hit.
127 		for (int i = n; !proj && cnt; cnt--, i = (i + 1) % psize)
128 			if (Fast_pathfinder_client::is_straight_path(src,
129 			        party[i]->get_tile()))
130 				proj = std::make_unique<Projectile_effect>(
131 				    src, party[i], weapon, shapenum, shapenum);
132 	}
133 	if (proj)
134 		eman->add_effect(std::move(proj));
135 	if (chk_range)
136 		gwin->get_tqueue()->add(curtime + (delay > 0 ? delay : 1), this, udata);
137 }
138 
139 /*
140  *  Each egg type:
141  */
142 class Jukebox_egg : public Egg_object {
143 protected:
144 	unsigned char score;
145 	bool continuous;
146 public:
Jukebox_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1)147 	Jukebox_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
148 	            unsigned int tz, unsigned short itype,
149 	            unsigned char prob, uint16 d1)
150 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, 0),
151 		  score(d1 & 0xff), continuous(((d1 >> 8) & 1) != 0)
152 	{  }
hatch_now(Game_object * obj,bool must)153 	void hatch_now(Game_object *obj, bool must) override {
154 		ignore_unused_variable_warning(obj, must);
155 #ifdef DEBUG
156 		cout << "Audio parameters might be: " << (data1 & 0xff) <<
157 		     " and " << ((data1 >> 8) & 0x01) << endl;
158 #endif
159 		Audio::get_ptr()->start_music(score, continuous);
160 	}
161 };
162 
163 class Soundsfx_egg : public Jukebox_egg {
164 public:
Soundsfx_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1)165 	Soundsfx_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
166 	             unsigned int tz, unsigned short itype,
167 	             unsigned char prob, uint16 d1)
168 		: Jukebox_egg(shnum, frnum, tx, ty, tz, itype, prob, d1)
169 	{  }
hatch_now(Game_object * obj,bool must)170 	void hatch_now(Game_object *obj, bool must) override {
171 		ignore_unused_variable_warning(obj, must);
172 		Audio::get_ptr()->play_sound_effect(score, this, AUDIO_MAX_VOLUME,
173 		                                    continuous);
174 	}
175 };
176 
177 class Voice_egg : public Egg_object {
178 public:
Voice_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1)179 	Voice_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
180 	          unsigned int tz, unsigned short itype,
181 	          unsigned char prob, uint16 d1)
182 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, 0)
183 	{  }
hatch_now(Game_object * obj,bool must)184 	void hatch_now(Game_object *obj, bool must) override {
185 		ignore_unused_variable_warning(obj, must);
186 		ucmachine->do_speech(data1 & 0xff);
187 	}
188 };
189 
190 class Monster_egg : public Egg_object {
191 	unsigned short mshape;      // For monster.
192 	unsigned char mframe;
193 	unsigned char sched, align, cnt;
create_monster() const194 	void create_monster() const {
195 		Tile_coord dest = Map_chunk::find_spot(
196 		                      get_tile(), 5, mshape, 0, 1);
197 		if (dest.tx != -1) {
198 			Game_object_shared new_monster =
199 			    Monster_actor::create(mshape, dest, sched, align);
200 			Game_object *monster = new_monster.get();
201 			monster->change_frame(mframe);
202 			gwin->add_dirty(monster);
203 			gwin->add_nearby_npc(monster->as_npc());
204 		}
205 	}
206 public:
Monster_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2,uint16 d3)207 	Monster_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
208 	            unsigned int tz, unsigned short itype,
209 	            unsigned char prob, uint16 d1, uint16 d2, uint16 d3)
210 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2, d3),
211 		  sched(d1 >> 8), align(d1 & 3), cnt((d1 & 0xff) >> 2) {
212 
213 		if (d3 > 0) {       // Exult extension.
214 			mshape = d3;
215 			mframe = d2 & 0xff;
216 		} else {
217 			mshape = d2 & 1023;
218 			mframe = d2 >> 10;
219 		}
220 	}
hatch_now(Game_object * obj,bool must)221 	void hatch_now(Game_object *obj, bool must) override {
222 		ignore_unused_variable_warning(obj, must);
223 		const Shape_info &info = ShapeID::get_info(mshape);
224 		if (info.is_npc()) {
225 			if (gwin->armageddon)
226 				return;
227 			int num = cnt;
228 			if (num > 1)    // Randomize.
229 				num = 1 + (rand() % num);
230 			while (num--)
231 				create_monster();
232 		} else {        // Create item.
233 			Game_object_shared nobj = get_map()->create_ireg_object(info,
234 			                    mshape, mframe, get_tx(), get_ty(), get_lift());
235 			if (nobj->is_egg())
236 				chunk->add_egg(nobj->as_egg());
237 			else
238 				chunk->add(nobj.get());
239 			gwin->add_dirty(nobj.get());
240 			nobj->set_flag(Obj_flags::okay_to_take);
241 			// Objects are created temporary
242 			nobj->set_flag(Obj_flags::is_temporary);
243 		}
244 	}
245 };
246 
247 class Usecode_egg : public Egg_object {
248 	short fun;
249 	string fun_name;        // Actual name in usecode source.
250 public:
Usecode_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2,const char * fnm)251 	Usecode_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
252 	            unsigned int tz, unsigned short itype,
253 	            unsigned char prob, uint16 d1, uint16 d2, const char *fnm)
254 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2),
255 		  fun(d2) {
256 		set_quality(d1 & 0xff);
257 		Usecode_egg::set_str1(fnm);
258 	}
set_str1(const char * s)259 	void set_str1(const char *s) override {
260 		fun_name = s ? s : "";
261 		if (s && *s)
262 			fun = 0;    // Want to look this up.
263 	}
get_str1() const264 	const char *get_str1() const override {
265 		return fun_name.c_str();
266 	}
get_usecode() const267 	int get_usecode() const override {
268 	    return fun;
269 	}
set_usecode(int funid,const char * nm=nullptr)270 	bool set_usecode(int funid, const char *nm = nullptr) override {
271 	    fun = funid;
272 		fun_name = nm;
273 		return true;
274 	}
hatch_now(Game_object * obj,bool must)275 	void hatch_now(Game_object *obj, bool must) override {
276 		ignore_unused_variable_warning(obj);
277 		if (!fun && !fun_name.empty())
278 			fun = ucmachine->find_function(fun_name.c_str());
279 		if (must)       // From script?  Do immediately.
280 			ucmachine->call_usecode(fun, this,
281 			                        Usecode_machine::egg_proximity);
282 		else {          // Do on next animation frame.
283 			auto *scr = new Usecode_script(this);
284 			scr->add(Ucscript::usecode, fun);
285 			if (flags & (1 << static_cast<int>(once))) {
286 				// Don't remove until done.
287 				scr->add(Ucscript::remove);
288 				flags &= ~(1 << static_cast<int>(once));
289 			}
290 			scr->start(gwin->get_std_delay());
291 		}
292 	}
293 };
294 
295 class Missile_egg : public Egg_object {
296 	short weapon;
297 	unsigned char dir, delay;
298 	Missile_launcher *launcher;
299 public:
Missile_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2)300 	Missile_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
301 	            unsigned int tz, unsigned short itype,
302 	            unsigned char prob, uint16 d1, uint16 d2)
303 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2),
304 		  weapon(d1), dir(d2 & 0xff), delay(d2 >> 8), launcher(nullptr)
305 	{  }
~Missile_egg()306 	~Missile_egg() override {
307 		if (launcher) {
308 			gwin->get_tqueue()->remove(launcher);
309 			delete launcher;
310 		}
311 	}
remove_this(Game_object_shared * keep)312 	void remove_this(Game_object_shared *keep) override {
313 		if (launcher) {     // Stop missiles.
314 			gwin->get_tqueue()->remove(launcher);
315 			delete launcher;
316 			launcher = nullptr;
317 		}
318 		Egg_object::remove_this(keep);
319 	}
paint()320 	void paint() override {
321 		// Make sure launcher is active.
322 		if (launcher && !launcher->in_queue() &&
323 		    (criteria == party_near || criteria == avatar_near))
324 			gwin->get_tqueue()->add(0L, launcher);
325 		Egg_object::paint();
326 	}
set(int crit,int dist)327 	void set(int crit, int dist) override {
328 		if (crit == external_criteria && launcher) {    // Cancel trap.
329 			gwin->get_tqueue()->remove(launcher);
330 			delete launcher;
331 			launcher = nullptr;
332 		}
333 		Egg_object::set(crit, dist);
334 	}
hatch_now(Game_object * obj,bool must)335 	void hatch_now(Game_object *obj, bool must) override {
336 		ignore_unused_variable_warning(obj, must);
337 		const Shape_info &info = ShapeID::get_info(weapon);
338 		const Weapon_info *winf = info.get_weapon_info();
339 		int proj;
340 		if (winf && winf->get_projectile())
341 			proj = winf->get_projectile();
342 		else
343 			proj = 856; // Fireball.  Shouldn't get here.
344 		if (!launcher)
345 			launcher = new Missile_launcher(this, weapon,
346 			                                proj, dir, gwin->get_std_delay()*delay);
347 		if (!launcher->in_queue())
348 			gwin->get_tqueue()->add(0L, launcher);
349 	}
350 };
351 
352 
353 class Teleport_egg : public Egg_object {
354 	short mapnum;           // If not -1.
355 	unsigned short destx, desty, destz;
356 public:
Teleport_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2,uint16 d3)357 	Teleport_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
358 	             unsigned int tz, unsigned short itype,
359 	             unsigned char prob, uint16 d1, uint16 d2, uint16 d3)
360 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2, d3),
361 		  mapnum(-1) {
362 		if (type == intermap)
363 			mapnum = d1 & 0xff;
364 		else
365 			set_quality(d1 & 0xff); // Teleport egg.
366 		int schunk = d1 >> 8;
367 		destx = (schunk % 12) * c_tiles_per_schunk + (d2 & 0xff);
368 		desty = (schunk / 12) * c_tiles_per_schunk + (d2 >> 8);
369 		destz = d3 & 0xff;
370 	}
hatch_now(Game_object * obj,bool must)371 	void hatch_now(Game_object *obj, bool must) override {
372 		ignore_unused_variable_warning(must);
373 		Tile_coord pos(-1, -1, -1); // Get position to jump to.
374 		int eggnum = 255;
375 		if (mapnum == -1)
376 			eggnum = get_quality();
377 		if (eggnum == 255) {        // Jump to coords.
378 			pos = Tile_coord(destx, desty, destz);
379 		} else {
380 			Egg_vector vec; // Look for dest. egg (frame == 6).
381 			if (find_nearby_eggs(vec, 275, 256, eggnum, 6)) {
382 				Egg_object *path = vec[0];
383 				pos = path->get_tile();
384 			}
385 		}
386 		cout << "Should teleport to map " << mapnum <<
387 		     ", (" << pos.tx << ", " <<
388 		     pos.ty << ')' << endl;
389 		if (pos.tx != -1 && obj && obj->get_flag(Obj_flags::in_party))
390 			// Teleport everyone!!!
391 			gwin->teleport_party(pos, false, mapnum, false);
392 	}
393 };
394 
395 class Weather_egg : public Egg_object {
396 	unsigned char weather;      // 0-6
397 	unsigned char len;      // In game minutes.
398 public:
Weather_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2)399 	Weather_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
400 	            unsigned int tz, unsigned short itype,
401 	            unsigned char prob, uint16 d1, uint16 d2)
402 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2),
403 		  weather(d1 & 0xff), len(d1 >> 8) {
404 		if (!len)       // Means continuous.
405 			len = 120;  // How about a couple game hours?
406 	}
hatch_now(Game_object * obj,bool must)407 	void hatch_now(Game_object *obj, bool must) override {
408 		ignore_unused_variable_warning(obj, must);
409 		set_weather(weather, len, this);
410 	}
411 };
412 
413 class Button_egg : public Egg_object {
414 	unsigned char dist;
415 public:
Button_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2)416 	Button_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
417 	           unsigned int tz, unsigned short itype,
418 	           unsigned char prob, uint16 d1, uint16 d2)
419 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2),
420 		  dist(d1 & 0xff)
421 	{  }
hatch_now(Game_object * obj,bool must)422 	void hatch_now(Game_object *obj, bool must) override {
423 		ignore_unused_variable_warning(must);
424 		Egg_vector eggs;
425 		find_nearby_eggs(eggs, 275, dist);
426 		find_nearby_eggs(eggs, 200, dist);
427 		for (auto *egg : eggs) {
428 			if (egg != this &&
429 			        egg->criteria == external_criteria &&
430 			        // Attempting to fix problem in Silver Seed
431 			        !(egg->flags & (1 << static_cast<int>(hatched))))
432 				egg->hatch(obj, false);
433 		}
434 	}
435 };
436 
437 class Path_egg : public Egg_object {
438 public:
Path_egg(int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,uint16 d1,uint16 d2)439 	Path_egg(int shnum, int frnum, unsigned int tx, unsigned int ty,
440 	         unsigned int tz, unsigned short itype,
441 	         unsigned char prob, uint16 d1, uint16 d2)
442 		: Egg_object(shnum, frnum, tx, ty, tz, itype, prob, d1, d2) {
443 		set_quality(d1 & 0xff);
444 	}
445 };
446 
447 /*
448  *  Create an "egg" from Ireg data.
449  */
450 
create_egg(const unsigned char * entry,int entlen,bool animated,int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz)451 Egg_object_shared Egg_object::create_egg(
452     const unsigned char *entry,       // 12+ byte ireg entry.
453     int entlen,
454     bool animated,
455     int shnum,
456     int frnum,
457     unsigned int tx,
458     unsigned int ty,
459     unsigned int tz
460 ) {
461 	unsigned short type = entry[4] + 256 * entry[5];
462 	int prob = entry[6];        // Probability (1-100).
463 	int data1 = entry[7] + 256 * entry[8];
464 	int data2 = entry[10] + 256 * entry[11];
465 	int data3 = entlen >= 14 ? (entry[12] + 256 * entry[13]) : 0;
466 	return create_egg(animated, shnum, frnum, tx, ty, tz, type, prob,
467 	                  data1, data2, data3);
468 }
469 
create_egg(bool animated,int shnum,int frnum,unsigned int tx,unsigned int ty,unsigned int tz,unsigned short itype,unsigned char prob,short data1,short data2,short data3,const char * str1)470 Egg_object_shared Egg_object::create_egg(
471     bool animated,
472     int shnum, int frnum,
473     unsigned int tx, unsigned int ty, unsigned int tz,
474     unsigned short itype,       // Type + flags, etc.
475     unsigned char prob,
476     short data1, short data2, short data3,
477     const char *str1
478 ) {
479 	int type = itype & 0xf;
480 	// Teleport destination?
481 	if (type == teleport && frnum == 6 && shnum == 275)
482 		type = path;        // (Mountains N. of Vesper).
483 
484 	Egg_object_shared obj;
485 	switch (type) {     // The type:
486 	case monster:
487 		obj = std::make_shared<Monster_egg>(shnum, frnum, tx, ty, tz, itype, prob,
488 		                      data1, data2, data3);
489 		break;
490 	case jukebox:
491 		obj = std::make_shared<Jukebox_egg>(shnum, frnum, tx, ty, tz, itype, prob,
492 		                      data1);
493 		break;
494 	case soundsfx:
495 		obj = std::make_shared<Soundsfx_egg>(shnum, frnum, tx, ty, tz, itype, prob,
496 		                       data1);
497 		break;
498 	case voice:
499 		obj = std::make_shared<Voice_egg>(shnum, frnum, tx, ty, tz, itype, prob,
500 		                    data1);
501 		break;
502 	case usecode:
503 		obj = std::make_shared<Usecode_egg>(shnum, frnum, tx, ty, tz, itype, prob,
504 		                      data1, data2, str1);
505 		break;
506 	case missile:
507 		obj = std::make_shared<Missile_egg>(shnum, frnum, tx, ty, tz, itype, prob,
508 		                      data1, data2);
509 		break;
510 	case teleport:
511 	case intermap:
512 		obj = std::make_shared<Teleport_egg>(shnum, frnum, tx, ty, tz, itype, prob,
513 		                       data1, data2, data3);
514 		break;
515 	case weather:
516 		obj = std::make_shared<Weather_egg>(shnum, frnum, tx, ty, tz, itype, prob,
517 		                      data1, data2);
518 		break;
519 	case path:
520 		obj = std::make_shared<Path_egg>(shnum, frnum, tx, ty, tz, itype, prob,
521 		                   data1, data2);
522 		break;
523 	case button:
524 		obj = std::make_shared<Button_egg>(shnum, frnum, tx, ty, tz, itype, prob,
525 		                     data1, data2);
526 		break;
527 	default:
528 		cerr << "Illegal egg itype:  " << type << endl;
529 		obj = std::make_shared<Egg_object>(shnum, frnum, tx, ty, tz, itype,
530 			  									  prob, data1, data2, data3);
531 	}
532 	if (animated)
533 		obj->set_animator(new Frame_animator(obj.get()));
534 	return obj;
535 }
536 
537 /*
538  *  Paint at given spot in world.
539  */
540 
paint()541 void Egglike_game_object::paint(
542 ) {
543 	if (gwin->paint_eggs)
544 		Game_object::paint();
545 }
546 
547 /*
548  *  Can this be clicked on?
549  */
550 
is_findable()551 bool Egglike_game_object::is_findable(
552 ) {
553 	return gwin->paint_eggs && Ireg_game_object::is_findable();
554 }
555 
556 /*
557  *  Create an egg from IREG data.
558  */
559 
Egg_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft,unsigned short itype,unsigned char prob,short d1,short d2,short d3)560 Egg_object::Egg_object(
561     int shapenum, int framenum,
562     unsigned int tilex, unsigned int tiley,
563     unsigned int lft,
564     unsigned short itype,
565     unsigned char prob,
566     short d1, short d2, short d3
567 ) : Egglike_game_object(shapenum, framenum, tilex, tiley, lft),
568 	probability(prob), data1(d1), data2(d2), data3(d3),
569 	area(TileRect(0, 0, 0, 0)), animator(nullptr) {
570 	type = itype & 0xf;
571 	// Teleport destination?
572 	if (type == teleport && framenum == 6 && shapenum == 275)
573 		type = path;        // (Mountains N. of Vesper).
574 	criteria = (itype & (7 << 4)) >> 4;
575 	distance = (itype >> 10) & 0x1f;
576 	unsigned char noct = (itype >> 7) & 1;
577 	unsigned char do_once = (itype >> 8) & 1;
578 	// Missile eggs can be rehatched
579 	unsigned char htch = (type == missile) ? 0 : ((itype >> 9) & 1);
580 	solid_area = (criteria == something_on || criteria == cached_in ||
581 	              // Teleports need solid area.
582 	              type == teleport || type == intermap) ? 1 : 0;
583 	unsigned char ar = (itype >> 15) & 1;
584 	flags = (noct << nocturnal) + (do_once << once) +
585 	        (htch << hatched) + (ar << auto_reset);
586 	// Party_near & auto_reset don't mix
587 	//   well.
588 	if (criteria == party_near && (flags & (1 << auto_reset)))
589 		criteria = avatar_near;
590 }
591 
592 /*
593  *  Init. for a field.
594  */
595 
init_field(unsigned char ty)596 inline void Egg_object::init_field(
597     unsigned char ty        // Egg (field) type.
598 ) {
599 	type = ty;
600 	probability = 100;
601 	data1 = data2 = 0;
602 	area = TileRect(0, 0, 0, 0);
603 	criteria = party_footpad;
604 	distance = 0;
605 	solid_area = 0;
606 	animator = nullptr;
607 	flags = (1 << auto_reset);
608 }
609 
610 /*
611  *  Create an egg representing a field.
612  */
613 
Egg_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft,unsigned char ty)614 Egg_object::Egg_object(
615     int shapenum,
616     int framenum,
617     unsigned int tilex, unsigned int tiley,
618     unsigned int lft,
619     unsigned char ty        // Egg (field) type.
620 ) : Egglike_game_object(shapenum, framenum, tilex, tiley, lft) {
621 	init_field(ty);
622 }
623 
624 /*
625  *  Destructor:
626  */
627 
~Egg_object()628 Egg_object::~Egg_object(
629 ) {
630 	delete animator;
631 }
632 
633 /*
634  *  Set active area after being added to its chunk.
635  */
636 
set_area()637 void Egg_object::set_area(
638 ) {
639 	if (!probability || type == path) { // No chance of normal activation?
640 		area = TileRect(0, 0, 0, 0);
641 		return;
642 	}
643 	Tile_coord t = get_tile();  // Get absolute tile coords.
644 	switch (criteria) {     // Set up active area.
645 	case cached_in:         // Make it really large.
646 		area = TileRect(t.tx - 32, t.ty - 32, 64, 64);
647 		break;
648 	case avatar_footpad:
649 	case party_footpad: {
650 		const Shape_info &info = get_info();
651 		int frame = get_framenum();
652 		int xtiles = info.get_3d_xtiles(frame);
653 		int ytiles = info.get_3d_ytiles(frame);
654 		area = TileRect(t.tx - xtiles + 1, t.ty - ytiles + 1,
655 		                 xtiles, ytiles);
656 		break;
657 	}
658 	case avatar_far:        // Make it 1 tile bigger each dir.
659 		area = TileRect(t.tx - distance - 1, t.ty - distance - 1,
660 		                 2 * distance + 3, 2 * distance + 3);
661 		break;
662 	default: {
663 		int width = 2 * distance;
664 		width++;        // Added 8/1/01.
665 		if (distance <= 1) { // Small?
666 			// More guesswork:
667 			if (criteria == external_criteria)
668 				width += 2;
669 		}
670 		area = TileRect(t.tx - distance, t.ty - distance,
671 		                 width, width);
672 		break;
673 	}
674 	}
675 	// Don't go outside the world.
676 	TileRect world(0, 0, c_num_chunks * c_tiles_per_chunk,
677 	                c_num_chunks * c_tiles_per_chunk);
678 	area = area.intersect(world);
679 }
680 
681 /*
682  *  Can this be clicked on?
683  */
is_findable()684 bool Egg_object::is_findable() {
685 	if (animator)
686 		return Ireg_game_object::is_findable();
687 	else
688 		return Egglike_game_object::is_findable();
689 }
690 
691 /*
692  *  Change the criteria and distance.
693  */
694 
set(int crit,int dist)695 void Egg_object::set(
696     int crit,
697     int dist
698 ) {
699 	Map_chunk *echunk = get_chunk();
700 	echunk->remove_egg(this);   // Got to add it back.
701 	criteria = crit;
702 	distance = dist;
703 	echunk->add_egg(this);
704 }
705 
706 /*
707  *  Is the egg active when stepping onto a given spot, or placing an obj.
708  *  on the spot?
709  */
710 
is_active(Game_object * obj,int tx,int ty,int tz,int from_tx,int from_ty)711 bool Egg_object::is_active(
712     Game_object *obj,       // Object placed (or Actor).
713     int tx, int ty, int tz,     // Tile stepped onto.
714     int from_tx, int from_ty    // Tile stepped from.
715 ) {
716 	if (cheat.in_map_editor())
717 		return false;       // Disable in map-editor.
718 	if ((flags & (1 << static_cast<int>(hatched))) &&
719 	        !(flags & (1 << static_cast<int>(auto_reset))))
720 		return false;     // For now... Already hatched.
721 	if (flags & (1 << static_cast<int>(nocturnal))) {
722 		// Nocturnal.
723 		int hour = gclock->get_hour();
724 		if (!(hour >= 21 || hour <= 4))
725 			return false; // It's not night.
726 	}
727 	auto cri = static_cast<Egg_criteria>(get_criteria());
728 
729 	int deltaz = tz - get_lift();
730 	switch (cri) {
731 	case cached_in: {       // Anywhere in square.
732 		// This seems to be true for SI in general. It has the side effect
733 		// of "fixing" Fawn Tower goblins.
734 		// It does NOT happen in BG, though.
735 		if (GAME_SI && deltaz / 5 != 0 && type == monster) {
736 			// Mark hatched if not auto-reset.
737 			if (!(flags & (1 << static_cast<int>(auto_reset))))
738 				flags |= (1 << static_cast<int>(hatched));
739 			return false;
740 		}
741 		if (obj != gwin->get_main_actor() || !area.has_world_point(tx, ty))
742 			return false;   // Not in square.
743 		if (!(flags & (1 << static_cast<int>(hatched))))
744 			return true;   // First time.
745 		// Must have autoreset.
746 		// Just activate when reentering.
747 		return !area.has_world_point(from_tx, from_ty);
748 	}
749 	case avatar_near:
750 		if (obj != gwin->get_main_actor())
751 			return false;
752 #ifdef DEBUG
753 		print_debug();
754 #endif
755 		// fall through
756 	case party_near:        // Avatar or party member.
757 		if (!obj->get_flag(Obj_flags::in_party))
758 			return false;
759 		if (type == teleport || // Teleports:  Any tile, exact lift.
760 		        type == intermap)
761 			return deltaz == 0 && area.has_world_point(tx, ty);
762 		else if (type == jukebox || type == soundsfx || type == voice)
763 			// Guessing. Fixes shrine of Spirituality and Sacrifice.
764 			return area.has_world_point(tx, ty);
765 		return (deltaz / 2 == 0 ||
766 		        // Using trial&error here:
767 		        (Game::get_game_type() == SERPENT_ISLE &&
768 		         type != missile) ||
769 		        (type == missile && deltaz / 5 == 0)) &&
770 		        // New tile is in, old is out.
771 		        area.has_world_point(tx, ty) &&
772 		        !area.has_world_point(from_tx, from_ty);
773 	case avatar_far: {      // New tile is outside, old is inside.
774 		if (obj != gwin->get_main_actor() || !area.has_world_point(tx, ty))
775 			return false;
776 		TileRect inside(area.x + 1, area.y + 1,
777 		                 area.w - 2, area.h - 2);
778 		return inside.has_world_point(from_tx, from_ty) &&
779 		       !inside.has_world_point(tx, ty);
780 	}
781 	case avatar_footpad:
782 		return obj == gwin->get_main_actor() && deltaz == 0 &&
783 		       area.has_world_point(tx, ty);
784 	case party_footpad:
785 		// Our field eggs need to hurt every actor
786 		if (type >= fire_field)
787 			return area.has_world_point(tx, ty) && deltaz == 0 &&
788 				   obj->as_actor();
789 		else
790 			return area.has_world_point(tx, ty) && deltaz == 0 &&
791 			       obj->get_flag(Obj_flags::in_party);;
792 	case something_on:
793 		return          // Guessing.  At SI end, deltaz == -1.
794 		    deltaz / 4 == 0 && area.has_world_point(tx, ty) && !obj->as_actor();
795 	case external_criteria:
796 	default:
797 		return false;
798 	}
799 }
800 
801 /*
802  *  Animate.
803  */
set_animator(Animator * a)804 void Egg_object::set_animator(Animator *a) {
805 	delete animator;
806 	animator = a;
807 }
808 
809 /*
810  *  Stop animation.
811  */
812 
stop_animation()813 void Egg_object::stop_animation(
814 ) {
815 	if (animator)
816 		animator->stop_animation();
817 }
818 
819 /*
820  *  Paint at given spot in world.
821  */
822 
paint()823 void Egg_object::paint(
824 ) {
825 	if (animator) {
826 		animator->want_animation(); // Be sure animation is on.
827 		Ireg_game_object::paint();  // Always paint these.
828 	} else
829 		Egglike_game_object::paint();
830 }
831 
832 /*
833  *  Run usecode when double-clicked.
834  */
835 
activate(int)836 void Egg_object::activate(
837     int /* event */
838 ) {
839 	if (!edit())
840 		hatch(nullptr, false);
841 	if (animator)
842 		flags &= ~(1 << static_cast<int>(hatched)); // Moongate:  reset always.
843 }
844 
845 /*
846  *  Edit in ExultStudio.
847  *
848  *  Output: True if map-editing & ES is present.
849  */
850 
edit()851 bool Egg_object::edit(
852 ) {
853 #ifdef USE_EXULTSTUDIO
854 	if (client_socket >= 0 &&   // Talking to ExultStudio?
855 	        cheat.in_map_editor()) {
856 		editing.reset();
857 		Tile_coord t = get_tile();
858 		// Usecode function name.
859 		string str1 = get_str1();
860 		if (Egg_object_out(client_socket, this, t.tx, t.ty, t.tz,
861 		                   get_shapenum(), get_framenum(),
862 		                   type, criteria, probability, distance,
863 		                   (flags >> nocturnal) & 1, (flags >> once) & 1,
864 		                   (flags >> hatched) & 1, (flags >> auto_reset) & 1,
865 		                   data1, data2, data3, str1) != -1) {
866 			cout << "Sent egg data to ExultStudio" << endl;
867 			editing = shared_from_this();
868 		} else
869 			cout << "Error sending egg data to ExultStudio" << endl;
870 		return true;
871 	}
872 #endif
873 	return false;
874 }
875 
876 
877 /*
878  *  Message to update from ExultStudio.
879  */
880 
update_from_studio(unsigned char * data,int datalen)881 void Egg_object::update_from_studio(
882     unsigned char *data,
883     int datalen
884 ) {
885 #ifdef USE_EXULTSTUDIO
886 	int x;
887 	int y;           // Mouse click for new egg.
888 	Egg_object *oldegg;
889 	int tx;
890 	int ty;
891 	int tz;
892 	int shape;
893 	int frame;
894 	int type;
895 	int criteria;
896 	int probability;
897 	int distance;
898 	bool nocturnal;
899 	bool once;
900 	bool hatched;
901 	bool auto_reset;
902 	int data1;
903 	int data2;
904 	int data3;
905 	string str1;
906 	if (!Egg_object_in(data, datalen, oldegg, tx, ty, tz, shape, frame,
907 	                   type, criteria, probability, distance,
908 	                   nocturnal, once, hatched, auto_reset,
909 	                   data1, data2, data3, str1)) {
910 		cout << "Error decoding egg" << endl;
911 		return;
912 	}
913 	if (oldegg && oldegg != editing.get()) {
914 		cout << "Egg from ExultStudio is not being edited" << endl;
915 		return;
916 	}
917 	// Keeps NPC alive until end of function
918 	Game_object_shared keep = std::move(editing);
919 	if (!oldegg) {          // Creating a new one?  Get loc.
920 		if (!Get_click(x, y, Mouse::hand, nullptr)) {
921 			if (client_socket >= 0)
922 				Exult_server::Send_data(client_socket,
923 				                        Exult_server::cancel);
924 			return;
925 		}
926 		if (shape == -1)
927 			shape = 275;    // FOR NOW.
928 	} else if (shape == -1)
929 		shape = oldegg->get_shapenum();
930 	if (frame == -1)
931 		switch (type) {
932 			// (These aren't perfect.)
933 		case monster:
934 			frame = 0;
935 			break;
936 		case jukebox:
937 			frame = 2;
938 			break;
939 		case soundsfx:
940 			frame = 1;
941 			break;
942 		case voice:
943 			frame = 3;
944 			break;
945 		case weather:
946 			frame = 4;
947 			break;
948 		case intermap:
949 		case teleport:
950 			frame = 5;
951 			break;
952 		case path:
953 			frame = 6;
954 			break;
955 		case missile:
956 			shape = 200;
957 			if ((data2 & 0xFF) < 8)
958 				frame = 2 + ((data2 & 0xFF) / 2);
959 			else
960 				frame = 1;
961 			break;
962 		default:
963 			frame = 7;
964 			break;
965 		}
966 
967 	const Shape_info &info = ShapeID::get_info(shape);
968 	bool anim = info.is_animated() || info.has_sfx();
969 	Egg_object_shared egg = create_egg(anim, shape, frame, tx, ty, tz, type,
970 	                             probability, data1, data2, data3, str1.c_str());
971 	if (!oldegg) {
972 		int lift;       // Try to drop at increasing hts.
973 		for (lift = 0; lift < 12; lift++)
974 			if (gwin->drop_at_lift(egg.get(), x, y, lift) == 1)
975 				break;
976 		if (lift == 12) {
977 			if (client_socket >= 0)
978 				Exult_server::Send_data(client_socket,
979 				                        Exult_server::cancel);
980 			egg = nullptr;
981 			return;
982 		}
983 		if (client_socket >= 0)
984 			Exult_server::Send_data(client_socket,
985 			                        Exult_server::user_responded);
986 	} else {
987 		Tile_coord pos = oldegg->get_tile();
988 		egg->move(pos.tx, pos.ty, pos.tz);
989 	}
990 	gwin->add_dirty(egg.get());
991 	egg->criteria = criteria & 7;
992 	egg->distance = distance & 31;
993 	egg->probability = probability;
994 	egg->flags = ((nocturnal ? 1 : 0) << Egg_object::nocturnal) +
995 	             ((once ? 1 : 0) << Egg_object::once) +
996 	             ((hatched ? 1 : 0) << Egg_object::hatched) +
997 	             ((auto_reset ? 1 : 0) << Egg_object::auto_reset);
998 
999 	if (oldegg)
1000 		oldegg->remove_this();
1001 	Map_chunk *echunk = egg->get_chunk();
1002 	echunk->remove_egg(egg.get());    // Got to add it back.
1003 	echunk->add_egg(egg.get());
1004 	cout << "Egg updated" << endl;
1005 #else
1006 	ignore_unused_variable_warning(data, datalen);
1007 #endif
1008 }
1009 
1010 /*
1011  *  Hatch egg.
1012  */
1013 
hatch(Game_object * obj,bool must)1014 void Egg_object::hatch(
1015     Game_object *obj,       // Object (actor) that came near it.
1016     bool must           // If 1, skip dice roll & execute
1017     //   usecode eggs immediately.
1018 ) {
1019 #ifdef DEBUG
1020 	print_debug();
1021 #endif
1022 	/*
1023 	  MAJOR HACK!
1024 	  This is an attempt at a work-around of a potential bug in the original
1025 	  Serpent Isle. See SourceForge bug #879253
1026 
1027 	  Prevent the Serpent Staff egg from hatching only once
1028 	*/
1029 	Tile_coord eggpos = get_tile();
1030 	if (GAME_SI && eggpos.tx == 1287 && eggpos.ty == 2568 && eggpos.tz == 0) {
1031 		flags &= ~(1 << static_cast<int>(hatched));
1032 	}
1033 	// Fixing some stairs in SS bug # 3115182 by making them auto_reset
1034 	if (GAME_SS && type == teleport &&
1035 	        ((eggpos.tx == 2844 && eggpos.ty == 2934 && eggpos.tz == 2) ||
1036 	         (eggpos.tx == 2843 && eggpos.ty == 2934 && eggpos.tz == 1) ||
1037 	         (eggpos.tx == 3015 && eggpos.ty == 2840 && eggpos.tz == 0) ||
1038 	         (eggpos.tx == 2950 && eggpos.ty == 2887 && eggpos.tz == 0) ||
1039 	         (eggpos.tx == 2859 && eggpos.ty == 3048 && eggpos.tz == 2) ||
1040 	         (eggpos.tx == 2884 && eggpos.ty == 3047 && eggpos.tz == 2) ||
1041 	         (eggpos.tx == 2983 && eggpos.ty == 2887 && eggpos.tz == 0)))
1042 		flags |= (1 << static_cast<int>(auto_reset));
1043 
1044 	/* end hack */
1045 
1046 
1047 	int roll = must ? 0 : 1 + rand() % 100;
1048 	if (roll <= probability) {
1049 		// Time to hatch the egg.
1050 		// Watch it in case it gets deleted.
1051 		Game_object_weak watch = weak_from_this();
1052 		hatch_now(obj, must);
1053 		if (watch.expired()) {
1054 			// We have been deleted, so just leave.
1055 			return;
1056 		}
1057 		if (flags & (1 << static_cast<int>(once))) {
1058 		    remove_this(nullptr);
1059 			return;
1060 		}
1061 	}
1062 	// Flag it as done, whether or not it has been hatched.
1063 	flags |= (1 << static_cast<int>(hatched));
1064 }
1065 
1066 /*
1067  *  Print debug information.
1068  */
1069 
print_debug()1070 void Egg_object::print_debug(
1071 ) {
1072 	cout << "Egg type is " << static_cast<int>(type) << ", prob = " <<
1073 	     static_cast<int>(probability) <<
1074 	     ", distance = " << static_cast<int>(distance) << ", crit = " <<
1075 	     static_cast<int>(criteria) << ", once = " <<
1076 	     ((flags & (1 << static_cast<int>(once))) != 0) << ", hatched = " <<
1077 	     ((flags & (1 << static_cast<int>(hatched))) != 0) <<
1078 	     ", areset = " <<
1079 	     ((flags & (1 << static_cast<int>(auto_reset))) != 0) << ", data1 = " << data1
1080 	     << ", data2 = " << data2
1081 	     << ", data3 = " << data3 << endl;
1082 }
1083 
1084 /*
1085  *  Set the weather (static).
1086  */
1087 
set_weather(int weather,int len,Game_object * egg)1088 void Egg_object::set_weather(
1089     int weather,            // 0-6.
1090     int len,            // In game minutes (I think).
1091     Game_object *egg        // Egg this came from, or null.
1092 ) {
1093 	if (!len)           // Means continuous.
1094 		len = 6000;     // Confirmed from originals.
1095 	int cur = eman->get_weather();
1096 	cout << "Current weather is " << cur << "; setting " << weather
1097 	     << endl;
1098 	// Experimenting.
1099 	if (weather != 4 && (weather == 3 || cur != weather))
1100 		eman->remove_weather_effects();
1101 
1102 	switch (weather) {
1103 	case 0:     // Back to normal.
1104 		eman->remove_weather_effects();
1105 		break;
1106 	case 1:     // Snow.
1107 		eman->add_effect(std::make_unique<Snowstorm_effect>(len, 0, egg));
1108 		break;
1109 	case 2:     // Storm.
1110 		eman->add_effect(std::make_unique<Storm_effect>(len, 0, egg));
1111 		break;
1112 	case 3:     // (On Ambrosia).
1113 		eman->remove_weather_effects();
1114 		eman->add_effect(std::make_unique<Sparkle_effect>(len, 0, egg));
1115 		break;
1116 	case 4:     // Fog.
1117 		eman->add_effect(std::make_unique<Fog_effect>(len, 0, egg));
1118 		break;
1119 	case 5:     // Overcast.
1120 	case 6:     // Clouds.
1121 		eman->add_effect(std::make_unique<Clouds_effect>(len, 0, egg, weather));
1122 		break;
1123 	default:
1124 		break;
1125 	}
1126 }
1127 
1128 /*
1129  *  Move to a new absolute location.  This should work even if the old
1130  *  location is invalid (cx=cy=255).
1131  */
1132 
move(int newtx,int newty,int newlift,int newmap)1133 void Egg_object::move(
1134     int newtx,
1135     int newty,
1136     int newlift,
1137     int newmap
1138 ) {
1139 	// Figure new chunk.
1140 	int newcx = newtx / c_tiles_per_chunk;
1141 	int newcy = newty / c_tiles_per_chunk;
1142 	Game_map *eggmap = newmap >= 0 ? gwin->get_map(newmap) : get_map();
1143 	if (!eggmap) eggmap = gmap;
1144 	Map_chunk *newchunk = eggmap->get_chunk_safely(newcx, newcy);
1145 	if (!newchunk)
1146 		return;         // Bad loc.
1147 	Game_object_shared keep;
1148 	remove_this(&keep);         // Remove from old.
1149 	set_lift(newlift);      // Set new values.
1150 	set_shape_pos(newtx % c_tiles_per_chunk, newty % c_tiles_per_chunk);
1151 	newchunk->add_egg(this);    // Updates cx, cy.
1152 	gwin->add_dirty(this);      // And repaint new area.
1153 }
1154 
1155 /*
1156  *  This is needed since it calls remove_egg().
1157  */
1158 
remove_this(Game_object_shared * keep)1159 void Egg_object::remove_this(
1160     Game_object_shared *keep     // Non-null to not delete.
1161 ) {
1162     if (keep)
1163 	    *keep = shared_from_this();
1164 	if (get_owner())        // Watch for this.
1165 		get_owner()->remove(this);
1166 	else {
1167 		if (chunk) {
1168 			gwin->add_dirty(this);  // (Make's ::move() simpler.).
1169 			chunk->remove_egg(this);
1170 		}
1171 	}
1172 }
1173 
1174 /*
1175  *  Write out.
1176  */
1177 
write_ireg(ODataSource * out)1178 void Egg_object::write_ireg(
1179     ODataSource *out
1180 ) {
1181 	unsigned char buf[30];      // 12-14 byte entry.
1182 	int sz = data3 > 0 ? 14 : 12;
1183 	uint8 *ptr = write_common_ireg(sz, buf);
1184 	unsigned short tword = type & 0xf; // Set up 'type' word.
1185 	tword |= ((criteria & 7) << 4);
1186 	tword |= (((flags >> nocturnal) & 1) << 7);
1187 	tword |= (((flags >> once) & 1) << 8);
1188 	tword |= (((flags >> hatched) & 1) << 9);
1189 	tword |= ((distance & 0x1f) << 10);
1190 	tword |= (((flags >> auto_reset) & 1) << 15);
1191 	Write2(ptr, tword);
1192 	*ptr++ = probability;
1193 	Write2(ptr, data1);
1194 	*ptr++ = nibble_swap(get_lift());
1195 	Write2(ptr, data2);
1196 	if (data3 > 0)
1197 		Write2(ptr, data3);
1198 	out->write(reinterpret_cast<char *>(buf), ptr - buf);
1199 	const char *str1 = get_str1();
1200 	if (*str1)          // This will be usecode fun. name.
1201 		Game_map::write_string(out, str1);
1202 	// Write scheduled usecode.
1203 	Game_map::write_scheduled(out, this);
1204 }
1205 
1206 // Get size of IREG. Returns -1 if can't write to buffer
get_ireg_size()1207 int Egg_object::get_ireg_size() {
1208 	// These shouldn't ever happen, but you never know
1209 	if (gumpman->find_gump(this) || Usecode_script::find(this))
1210 		return -1;
1211 	const char *str1 = get_str1();
1212 	return 8 + get_common_ireg_size() + ((data3 > 0) ? 2 : 0)
1213 	       + (*str1 ? Game_map::write_string(nullptr, str1) : 0);
1214 }
1215 
1216 /*
1217  *  Create.
1218  */
Field_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft,unsigned char ty)1219 Field_object::Field_object(int shapenum, int framenum, unsigned int tilex,
1220                            unsigned int tiley, unsigned int lft, unsigned char ty)
1221 	: Egg_object(shapenum, framenum, tilex, tiley, lft, ty) {
1222 	const Shape_info &info = get_info();
1223 	if (info.is_animated())
1224 		set_animator(new Field_frame_animator(this));
1225 }
1226 
1227 /*
1228  *  Apply field.
1229  *
1230  *  Output: True to delete field.
1231  */
1232 
field_effect(Actor * actor)1233 bool Field_object::field_effect(
1234     Actor *actor
1235 ) {
1236 	if (!actor)
1237 		return false;
1238 	bool del = false;       // Only delete poison, sleep fields.
1239 	int frame = get_framenum();
1240 	switch (type) {
1241 	case poison_field:
1242 		if (rand() % 2 && !actor->get_flag(Obj_flags::poisoned)) {
1243 			actor->set_flag(Obj_flags::poisoned);
1244 			del = true;
1245 		}
1246 		break;
1247 	case sleep_field:
1248 		if (rand() % 2 && !actor->get_flag(Obj_flags::asleep)) {
1249 			actor->set_flag(Obj_flags::asleep);
1250 			del = true;
1251 		}
1252 		break;
1253 	case campfire_field:
1254 		// Campfire (Fire in SI) doesn't hurt when burnt out
1255 		if (frame == 0)
1256 			return false;
1257 		// FALLTHROUGH
1258 	case fire_field:
1259 		actor->reduce_health(2 + rand() % 3, Weapon_data::fire_damage);
1260 		// But no sleeping here.
1261 		if (actor->get_flag(Obj_flags::asleep) && !actor->is_knocked_out())
1262 			actor->clear_flag(Obj_flags::asleep);
1263 		break;
1264 	case caltrops_field:
1265 		if (actor->get_effective_prop(Actor::intelligence) < rand() % 40)
1266 			//actor->reduce_health(2 + rand()%3, Weapon_info::normal_damage);
1267 			// Caltrops don't seem to cause much damage.
1268 			actor->reduce_health(1 + rand() % 2, Weapon_data::normal_damage);
1269 		return false;
1270 	}
1271 
1272 	if (!del && animator)
1273 		// Tell animator to keep checking.
1274 		animator->activate_animator();
1275 	return del;
1276 }
1277 
1278 /*
1279  *  Render.
1280  */
1281 
paint()1282 void Field_object::paint(
1283 ) {
1284 	if (animator)
1285 		animator->want_animation(); // Be sure animation is on.
1286 	Ireg_game_object::paint();  // Always paint these.
1287 }
1288 
1289 /*
1290  *  Run usecode when double-clicked or when activated by proximity.
1291  *  (Generally, nothing will happen.)
1292  */
1293 
activate(int event)1294 void Field_object::activate(
1295     int event
1296 ) {
1297 	// Field_frame_animator calls us with
1298 	//   event==0 to check for damage.
1299 	if (event != Usecode_machine::npc_proximity) {
1300 		Ireg_game_object::activate(event);
1301 		return;
1302 	}
1303 	Actor_vector npcs;      // Find all nearby NPC's.
1304 	gwin->get_nearby_npcs(npcs);
1305 	npcs.push_back(gwin->get_main_actor()); // Include Avatar.
1306 	TileRect eggfoot = get_footprint();
1307 	// Clear flag to check.
1308 	if (animator)
1309 		animator->deactivate_animator();
1310 	for (auto *actor : npcs) {
1311 		if (actor->is_dead() || Game_object::distance(actor) > 4)
1312 			continue;
1313 		if (actor->get_footprint().intersects(eggfoot))
1314 			Field_object::hatch(actor);
1315 	}
1316 }
1317 
1318 /*
1319  *  Someone stepped on it.
1320  */
1321 
hatch(Game_object * obj,bool)1322 void Field_object::hatch(
1323     Game_object *obj,       // Object (actor) that came near it.
1324     bool /* must */         // If 1, skip dice roll.
1325 ) {
1326 	if (field_effect(obj->as_actor()))// Apply field.
1327 		remove_this(nullptr);     // Delete sleep/poison if applied.
1328 }
1329 
1330 /*
1331  *  Write out.  These are stored as normal game objects.
1332  */
1333 
write_ireg(ODataSource * out)1334 void Field_object::write_ireg(
1335     ODataSource *out
1336 ) {
1337 	Ireg_game_object::write_ireg(out);
1338 }
1339 
1340 // Get size of IREG. Returns -1 if can't write to buffer
get_ireg_size()1341 int Field_object::get_ireg_size() {
1342 	return Ireg_game_object::get_ireg_size();
1343 }
1344 
1345 
1346 /*
1347  *  It's a Mirror
1348  */
1349 
Mirror_object(int shapenum,int framenum,unsigned int tilex,unsigned int tiley,unsigned int lft)1350 Mirror_object::Mirror_object(int shapenum, int framenum, unsigned int tilex,
1351                              unsigned int tiley, unsigned int lft) :
1352 	Egg_object(shapenum, framenum, tilex, tiley, lft, Egg_object::mirror_object) {
1353 	solid_area = 1;
1354 }
1355 
activate(int event)1356 void Mirror_object::activate(int event) {
1357 	Ireg_game_object::activate(event);
1358 }
1359 
hatch(Game_object * obj,bool must)1360 void Mirror_object::hatch(Game_object *obj, bool must) {
1361 	ignore_unused_variable_warning(obj, must);
1362 	// These are broken, so dont touch
1363 	if ((get_framenum() % 3) == 2)  return;
1364 
1365 	int wanted_frame = get_framenum() / 3;
1366 	wanted_frame *= 3;
1367 
1368 	// Find upperleft or our area
1369 	Tile_coord t = get_tile();
1370 
1371 	// To left or above?
1372 	if (get_shapenum() == 268) { // Left
1373 		t.tx++;
1374 		t.ty--;
1375 	} else {            // Above
1376 		t.tx--;
1377 		t.ty++;
1378 	}
1379 
1380 	// We just want to know if the area is blocked
1381 	int nl = 0;
1382 	if (Map_chunk::is_blocked(1, t.tz, t.tx, t.ty, 2, 2, nl, MOVE_WALK, 0)) {
1383 		wanted_frame++;
1384 	}
1385 
1386 	// Only if it changed update the shape
1387 	if (get_framenum() != wanted_frame)
1388 		change_frame(wanted_frame);
1389 }
1390 
1391 // Can it be activated?
is_active(Game_object * obj,int tx,int ty,int tz,int from_tx,int from_ty)1392 bool Mirror_object::is_active(Game_object *obj, int tx, int ty, int tz, int from_tx, int from_ty) {
1393 	ignore_unused_variable_warning(obj, tx, ty, tz, from_tx, from_ty);
1394 	// These are broken, so dont touch
1395 	int frnum = get_framenum();
1396 	if (frnum % 3 == 2)  return false;
1397 	if (frnum >= 3 && GAME_BG)  // Demon mirror in FOV.
1398 		return false;
1399 
1400 	return true;
1401 }
1402 
1403 // Set up active area.
set_area()1404 void Mirror_object::set_area() {
1405 	// These are broken, so dont touch
1406 	if ((get_framenum() % 3) == 2) area = TileRect(0, 0, 0, 0);
1407 
1408 	Tile_coord t = get_tile();  // Get absolute tile coords.
1409 
1410 	// To left or above?
1411 	if (get_shapenum() == 268) area = TileRect(t.tx - 1, t.ty - 3, 6, 6);
1412 	else  area = TileRect(t.tx - 3 , t.ty - 1, 6, 6);
1413 }
1414 
paint()1415 void Mirror_object::paint() {
1416 	Ireg_game_object::paint();  // Always paint these.
1417 }
1418 
1419 /*
1420  *  Write out.  These are stored as normal game objects.
1421  */
1422 
write_ireg(ODataSource * out)1423 void Mirror_object::write_ireg(ODataSource *out) {
1424 	Ireg_game_object::write_ireg(out);
1425 }
1426 
1427 // Get size of IREG. Returns -1 if can't write to buffer
get_ireg_size()1428 int Mirror_object::get_ireg_size() {
1429 	// TODO!!!!!!!
1430 	return Ireg_game_object::get_ireg_size();
1431 }
1432