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