add unfinished bmpImage implementation
[openc2e.git] / Agent.cpp
blob8968194148cc98d3b5ce20b22c5114d5803cf86f
1 /*
2 * Agent.cpp
3 * openc2e
5 * Created by Alyssa Milburn on Tue May 25 2004.
6 * Copyright (c) 2004 Alyssa Milburn. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
20 #include "Agent.h"
21 #include "MetaRoom.h"
22 #include "World.h"
23 #include "Engine.h"
24 #include <iostream>
25 #include "caosVM.h"
26 #include "AudioBackend.h"
27 #include <boost/format.hpp>
28 #include "Room.h"
29 #include "Vehicle.h"
30 #include "AgentHelpers.h"
31 #include "creaturesImage.h"
33 void Agent::core_init() {
34 initialized = false;
35 lifecount = 0;
38 Agent::Agent(unsigned char f, unsigned char g, unsigned short s, unsigned int p) :
39 vm(0), zorder(p), timerrate(0), visible(true) {
40 core_init();
42 setClassifier(f, g, s);
44 lastScript = -1;
46 velx.setFloat(0.0f);
47 vely.setFloat(0.0f);
49 wasmoved = false;
51 if (engine.version == 2) {
52 accg = 10;
53 aero = 20;
54 rest = 40;
56 size = 127; // TODO: correct default?
57 thrt = 0;
58 // TODO: it looks like grav should be 0, but make sure!
59 } else if (engine.version > 2) {
60 accg = 0.3f;
61 aero = 0;
62 elas = 0;
64 perm = 50; // TODO: correct default?
65 falling = true;
68 range = 500;
70 // TODO: is this the correct default?
71 clac[0] = 0; // message# for activate 1
72 if (engine.version < 3) {
73 // TODO: is this the correct default? (this is equivalent to bhvr click == 0)
74 clac[0] = -1; clac[1] = -1; clac[2] = -1;
76 clik = -1;
78 dying = false;
79 unid = -1;
81 paused = displaycore = false;
83 cr_can_push = cr_can_pull = cr_can_stop = cr_can_hit = cr_can_eat = cr_can_pickup = false; // TODO: check this
84 imsk_key_down = imsk_key_up = imsk_mouse_move = imsk_mouse_down = imsk_mouse_up = imsk_mouse_wheel = imsk_translated_char = false;
86 emitca_index = -1; emitca_amount = 0.0f;
88 objp.setAgent(0); // not strictly necessary
91 void Agent::finishInit() {
92 // lc2e, at least, seems to position agents centered on (-9876,-9876) to begin with
93 // TODO: where should we place agents in other games? is this code right at all anyway?
94 // (bear in mind that there are no parts present for some C1/C2 agents when finishInit is called, atm)
95 if (engine.version > 2 && world.gametype != "sm") { // TODO: need to think about sm-specific code some more
96 x = -9876.0f + (getWidth() / 2.0f); y = -9876.0f + (getHeight() / 2.0f);
99 // shared_from_this() can only be used if these is at least one extant
100 // shared_ptr which owns this
101 world.agents.push_front(boost::shared_ptr<Agent>(this));
102 agents_iter = world.agents.begin();
104 if (findScript(10))
105 queueScript(10); // constructor
107 initialized = true;
110 void Agent::zotstack() {
111 // Zap the VM stack.
112 for (std::list<caosVM *>::iterator i = vmstack.begin(); i != vmstack.end(); i++) {
113 world.freeVM(*i);
115 vmstack.clear();
118 void Agent::moveTo(float _x, float _y, bool force) {
119 // Move ourselves to the specified location.
120 wasmoved = true;
122 // if we're being carried and aren't being forced to move (prbly by our carrier), forget it
123 if (carriedby && !force) return;
125 // TODO: what if we move while being carried? doomy explosions ensue, that's what!
126 float xoffset = _x - x;
127 float yoffset = _y - y;
129 x = _x; y = _y;
131 // handle wraparound
132 // TODO: this is perhaps non-ideal
133 if (engine.version < 3 && xoffset != 0.0f) {
134 // TODO: it'd be nice to handle multiple metarooms
135 MetaRoom *m = world.map.getFallbackMetaroom();
136 assert(m);
138 if (x < m->x()) {
139 x += m->width();
140 } else if (x > m->x() + m->width()) {
141 x -= m->width();
145 for (std::vector<AgentRef>::iterator i = floated.begin(); i != floated.end(); i++) {
146 assert(*i);
147 (*i)->moveTo((*i)->x + xoffset, (*i)->y + yoffset);
150 adjustCarried(xoffset, yoffset);
153 void Agent::floatTo(AgentRef a) {
154 std::vector<AgentRef>::iterator i = std::find(floated.begin(), floated.end(), a);
155 assert(i == floated.end()); // loops are bad, mmkay
157 if (floatable()) floatRelease();
158 floatingagent = a;
159 if (floatable()) floatSetup();
162 void Agent::floatTo(float x, float y) {
163 if (floatingagent) {
164 moveTo(floatingagent->x + x, floatingagent->y + y);
165 } else {
166 moveTo(world.camera.getX() + x, world.camera.getY() + y);
170 void Agent::floatSetup() {
171 if (floatingagent)
172 floatingagent->addFloated(this);
173 else
174 world.camera.addFloated(this);
177 void Agent::floatRelease() {
178 if (floatingagent) {
179 floatingagent->delFloated(this);
180 } else
181 world.camera.delFloated(this);
184 void Agent::addFloated(AgentRef a) {
185 assert(a);
186 assert(a != floatingagent); // loops are bad, mmkay
187 floated.push_back(a);
190 void Agent::delFloated(AgentRef a) {
191 assert(a);
192 std::vector<AgentRef>::iterator i = std::find(floated.begin(), floated.end(), a);
193 //if (i == floated.end()) return;
194 assert(i != floated.end());
195 floated.erase(i);
198 shared_ptr<script> Agent::findScript(unsigned short event) {
199 return world.scriptorium.getScript(family, genus, species, event);
202 #include "PointerAgent.h"
203 #include "CreatureAgent.h"
204 #include "Creature.h"
205 bool Agent::fireScript(unsigned short event, Agent *from, caosVar one, caosVar two) {
206 // Start running the specified script on the VM of this agent, with FROM set to the provided agent.
208 if (dying) return false;
210 CreatureAgent *c = 0;
211 if (event <= 3 || event == 4 || event == 12 || event == 13 || event == 14)
212 c = dynamic_cast<CreatureAgent *>(from);
214 switch (event) {
215 case 0: // deactivate
216 if (c && !cr_can_stop) return false;
217 // TODO: not sure if this is the right place to do this.
218 actv.setInt(event);
219 break;
220 case 1: // activate 1
221 if (c && !cr_can_push) return false;
222 // TODO: not sure if this is the right place to do this.
223 actv.setInt(event);
224 break;
225 case 2: // activate 2
226 if (c && !cr_can_pull) return false;
227 // TODO: not sure if this is the right place to do this.
228 actv.setInt(event);
229 break;
230 case 3: // hit
231 if (c && !cr_can_hit) return false;
232 break;
233 case 4: // pickup
234 if (c && !cr_can_pickup) return false;
235 if (!from) return false;
236 if (from == world.hand()) {
237 if (!mouseable()) return false;
238 } else if (!c) {
239 // TODO: valid check for vehicles?
240 if (!carryable()) return false;
242 from->addCarried(this); // TODO: correct behaviour?
243 break;
244 case 5: // drop
245 if (!from) return false;
246 // TODO: this check isn't very good for vehicles ;p
247 // if (from != carriedby) return false;
248 break;
249 case 12: // eat
250 if (c && !cr_can_eat) return false;
251 break;
252 case 13: // hold hands with pointer
253 if (c) {
254 // TODO
256 break;
257 case 14: // stop holding hands with pointer
258 if (c) {
259 // TODO
261 break;
262 case 92: // TODO: hack for 'UI Mouse Down' event - we need a real event system!
263 std::cout << "faking event 92 on " << identify() << std::endl;
264 CompoundPart *p = world.partAt(world.hand()->x, world.hand()->y);
265 if (!p || p->getParent() != this) // if something is horridly broken here, return
266 return false; // was caos_assert(p && p->getParent() == this);
267 p->handleClick(world.hand()->x - p->x - p->getParent()->x, world.hand()->y - p->y - p->getParent()->y);
268 // TODO: we're [obviously] missing firing the pointer script here, but it's a hack for now
269 break;
272 bool ranscript = false;
274 shared_ptr<script> s = findScript(event);
275 if (s) {
276 bool madevm = false;
277 if (!vm) { madevm = true; vm = world.getVM(this); }
279 // TODO: 'engine.version < 3' check for nointerrupt param is a guess and untested - fuzzie
280 if (vm->fireScript(s, (event == 9 || engine.version < 3), from)) {
281 lastScript = event;
282 zotstack();
283 vm->setVariables(one, two);
285 // TODO: we should set _it_ in a more sensible way
286 CreatureAgent *a = dynamic_cast<CreatureAgent *>(this);
287 if (a) {
288 Creature *c = a->getCreature();
289 assert(c);
290 vm->_it_ = c->getAttentionFocus();
293 vmTick();
294 ranscript = true;
295 } else if (madevm) {
296 world.freeVM(vm);
297 vm = 0;
301 switch (event) {
302 case 5:
303 from->dropCarried(this); // TODO: correct?
304 break;
307 return ranscript;
310 bool Agent::queueScript(unsigned short event, AgentRef from, caosVar p0, caosVar p1) {
311 // Queue a script for execution on the VM of this agent.
313 if (dying) return false;
315 // only bother firing the event if either it exists, or it's one with engine code attached
316 // TODO: why don't we do the engine checks/etc here?
317 switch (event) {
318 default:
319 if (!findScript(event)) return false;
321 case 0:
322 case 1:
323 case 2:
324 case 3:
325 case 4:
326 case 5:
327 case 12:
328 case 13:
329 case 14:
330 case 92:
331 world.queueScript(event, this, from, p0, p1);
334 return true;
337 void Agent::handleClick(float clickx, float clicky) {
338 // Handle a mouse click.
340 // old-style click handling (c1/c2)
341 if (engine.version < 3) {
342 int action = -1;
344 // look up the relevant action for our ACTV state from the clac table
345 if ((unsigned int)actv.getInt() < 3)
346 action = clac[actv.getInt()];
348 if (action != -1) {
349 queueScript(calculateScriptId(action), (Agent *)world.hand());
352 return;
355 // new-style click handling (c2e)
356 if (clik != -1) {
357 // TODO: handle CLIK
358 } else if (clac[0] != -1) {
359 queueScript(calculateScriptId(clac[0]), (Agent *)world.hand());
363 void Agent::playAudio(std::string filename, bool controlled, bool loop) {
364 assert(!dying);
366 sound.reset();
367 world.playAudio(filename, this, controlled, loop);
370 bool agentOnCamera(Agent *targ, bool checkall = false); // caosVM_camera.cpp
372 void Agent::updateAudio(boost::shared_ptr<AudioSource> s) {
373 assert(s);
375 s->setPos(x + getWidth() / 2, y + getHeight() / 2, zorder);
376 // TODO: we could do with a nicer 'inrange' check
377 bool inrange = (x + 500 > world.camera.getX()) && (x + getWidth() - 500 < world.camera.getX() + world.camera.getWidth()) &&
378 (y + 500 > world.camera.getY()) && (y + getHeight() - 500 < world.camera.getY() + world.camera.getHeight());
379 s->setMute(
380 world.camera.getMetaRoom() != world.map.metaRoomAt(x, y)
382 !inrange
384 // TODO: setVelocity?
387 Point const Agent::boundingBoxPoint(unsigned int n) {
388 return boundingBoxPoint(n, Point(x, y), getWidth(), getHeight());
391 Point const Agent::boundingBoxPoint(unsigned int n, Point in, unsigned int w, unsigned int h) {
392 Point p;
394 switch (n) {
395 case 0: // left
396 p.x = in.x;
397 p.y = in.y + (h / 2.0f);
398 break;
400 case 1: // right
401 p.x = in.x + w;
402 p.y = in.y + (h / 2.0f);
403 break;
405 case 2: // top
406 p.x = in.x + (w / 2.0f);
407 p.y = in.y;
408 break;
410 case 3: // bottom
411 p.x = in.x + (w / 2.0f);
412 p.y = in.y + h;
413 break;
415 default:
416 throw creaturesException("Agent::boundingBoxPoint got unknown direction");
419 return p;
422 bool Agent::validInRoomSystem() {
423 // Return true if this agent is inside the world room system at present, or false if it isn't.
425 // TODO: c1
426 if (engine.version == 1) return true;
428 return validInRoomSystem(Point(x, y), getWidth(), getHeight(), perm);
431 bool const Agent::validInRoomSystem(Point p, unsigned int w, unsigned int h, int testperm) {
432 // Return true if this agent is inside the world room system at the specified point, or false if it isn't.
434 for (unsigned int i = 0; i < 4; i++) {
435 Point src, dest;
436 switch (i) {
437 case 0: src = boundingBoxPoint(0, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // left to bottom
438 case 1: src = boundingBoxPoint(1, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // right to bottom
439 case 2: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(0, p, w, h); break; // top to left
440 case 3: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(1, p, w, h); break; // top to right
443 if (engine.version == 2) {
444 // Creatures 2 physics
446 int dx = dest.x - src.x;
447 int dy = dest.y - src.y;
449 Point deltapt(0,0);
450 double delta = 1000000000;
451 bool collided = false;
453 // TODO: check suffercollisions?
455 // TODO: muh, provided direction here is kinda a hack
456 findCollisionInDirection((i < 2) ? 3 : 2, src, dx, dy, deltapt, delta, collided, true);
457 if (collided) return false;
458 } else {
459 // Creatures 3 physics
461 float srcx = src.x, srcy = src.y;
463 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
464 if (!ourRoom) return false;
466 unsigned int dir; Line wall;
467 world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, wall, dir, testperm);
469 if (src != dest) return false;
473 return true;
476 void Agent::physicsTick() {
477 if (engine.version == 1) return; // C1 has no physics, and different attributes.
479 if (carriedby) return; // We don't move when carried, so what's the point?
481 if (engine.version == 2) {
482 // Creatures 2 physics is different.
483 physicsTickC2();
484 return;
487 if (!wasmoved) return; // some agents are created outside INST and get autokilled if we try physics on them before they move
489 // set destination point based on velocities
490 float destx = x + velx.getFloat();
491 float desty = y + vely.getFloat();
493 if (sufferphysics()) {
494 // TODO: falling behaviour needs looking at more closely..
495 // .. but it shouldn't be 'false' by default on non-physics agents, so..
496 falling = false;
497 // increase speed according to accg
498 // TODO: should we be changing vely first, instead of after a successful move (below)?
499 desty += accg.getFloat();
502 if (suffercollisions()) {
503 float lastdistance = 1000000.0f;
504 bool collided = false;
505 Line wall; // only valid when collided
506 unsigned int collidedirection = 0; // only valid when collided
507 Point bestmove;
509 // iterate through all four points of the bounding box
510 for (unsigned int i = 0; i < 4; i++) {
511 // this mess is because we want to start with the bottom point - DOWN (3) - before the others, for efficiency
512 Point src = boundingBoxPoint((i == 0) ? 3 : i - 1);
514 // store values
515 float srcx = src.x, srcy = src.y;
517 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
518 if (!ourRoom) {
519 ourRoom = world.map.roomAt(srcx, srcy);
521 if (!ourRoom) {
522 if (!displaycore) { // TODO: ugh, displaycore is a horrible thing to use for this
523 // we're out of the room system, physics bug, but let's try MVSFing back in to cover for fuzzie's poor programming skills
524 static bool tryingmove; tryingmove = false; // avoid infinite loop
525 if (!tryingmove && tryMoveToPlaceAround(x, y)) {
526 //std::cout << identify() << " was out of room system due to a physics bug but we hopefully moved it back in.." << std::endl;
527 tryingmove = true;
528 physicsTick();
529 return;
532 // didn't work!
533 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % srcx % srcy), false);
535 displaycore = true;
536 return; // out of room system
539 Point dest(destx + (srcx - x), desty + (srcy - y));
540 unsigned int local_collidedirection;
541 Line local_wall;
543 // this changes src to the point at which we end up
544 bool local_collided = world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, local_wall, local_collidedirection, perm);
546 float dist;
547 if (src.x == srcx && src.y == srcy)
548 dist = 0.0f;
549 else {
550 float xdiff = src.x - srcx;
551 float ydiff = src.y - srcy;
552 dist = xdiff*xdiff + ydiff*ydiff;
555 if (dist >= lastdistance) {
556 assert(i != 0); // this had better not be our first collision!
557 continue; // further away than a previous collision
560 lastdistance = dist;
561 bestmove.x = x + (src.x - srcx);
562 bestmove.y = y + (src.y - srcy);
563 collidedirection = local_collidedirection;
564 wall = local_wall;
565 collided = local_collided;
567 if (dist == 0.0f)
568 break; // no point checking any more, is there?
571 // *** do actual movement
572 if (lastdistance != 0.0f) {
573 moveTo(bestmove.x, bestmove.y);
575 if (collided) {
576 lastcollidedirection = collidedirection;
577 queueScript(6, 0, velx, vely); // TODO: include this? .. we need to include SOMETHING, c3 ball checks for <3
579 if (elas != 0) {
580 if (wall.getType() == HORIZONTAL) {
581 vely.setFloat(-vely.getFloat());
582 } else if (wall.getType() == VERTICAL) {
583 velx.setFloat(-velx.getFloat());
584 } else {
585 // line starts always have a lower x value than the end
586 float xdiff = wall.getEnd().x - wall.getStart().x;
587 float ydiff = wall.getEnd().y - wall.getStart().y;
588 float fvelx = velx.getFloat(), fvely = vely.getFloat();
590 // calculate input/slope angles
591 double inputangle;
592 if (fvelx == 0.0f) {
593 if (fvely > 0.0f)
594 inputangle = M_PI / 2.0;
595 else
596 inputangle = 3 * (M_PI / 2.0);
597 } else {
598 inputangle = atan(fvely / fvelx);
600 double slopeangle = atan(-ydiff / xdiff); // xdiff != 0 because wall isn't vertical
602 // calculate output angle
603 double outputangle = slopeangle + (slopeangle - inputangle) + M_PI;
605 // turn back into component velocities
606 double vectorlength = sqrt(fvelx*fvelx + fvely*fvely);
607 float xoutput = cos(outputangle) * vectorlength;
608 float youtput = sin(outputangle) * vectorlength;
610 velx.setFloat(xoutput);
611 vely.setFloat(-youtput);
614 if (elas != 100.0f) {
615 velx.setFloat(velx.getFloat() * (elas / 100.0f));
616 vely.setFloat(vely.getFloat() * (elas / 100.0f));
618 } else
619 vely.setFloat(0);
620 } else if (sufferphysics() && accg != 0) {
621 falling = true; // TODO: icky
622 vely.setFloat(vely.getFloat() + accg.getFloat());
624 } else { velx.setFloat(0); vely.setFloat(0); } // TODO: correct?
625 } else {
626 if (vely.hasDecimal() || velx.hasDecimal())
627 moveTo(destx, desty);
628 if (sufferphysics())
629 vely.setFloat(vely.getFloat() + accg.getFloat());
632 if (sufferphysics() && (aero != 0)) {
633 // reduce speed according to AERO
634 // TODO: aero should be an integer!
635 velx.setFloat(velx.getFloat() - (velx.getFloat() * (aero.getFloat() / 100.0f)));
636 vely.setFloat(vely.getFloat() - (vely.getFloat() * (aero.getFloat() / 100.0f)));
640 shared_ptr<Room> const Agent::bestRoomAt(unsigned int tryx, unsigned int tryy, unsigned int direction, shared_ptr<Room> exclude) {
641 std::vector<shared_ptr<Room> > rooms = world.map.roomsAt(tryx, tryy);
643 shared_ptr<Room> r;
645 if (rooms.size() == 0) return shared_ptr<Room>();
646 if (rooms.size() == 1) r = rooms[0];
647 else if (rooms.size() > 1) {
648 unsigned int j;
649 for (j = 0; j < rooms.size(); j++) {
650 if (rooms[j] == exclude) continue;
652 if (direction == 0) { // left
653 if (rooms[j]->x_left == tryx) break;
654 } else if (direction == 1) { // right
655 if (rooms[j]->x_right == tryx) break;
656 } else if (direction == 2) { // top
657 if (rooms[j]->y_left_ceiling == tryy) break;
658 } else { // down
659 if (rooms[j]->y_left_floor == tryy) break;
662 if (j == rooms.size()) j = 0;
663 r = rooms[j];
666 if (r == exclude) return shared_ptr<Room>();
667 else return r;
670 // Creatures 2 collision finding
671 void const Agent::findCollisionInDirection(unsigned int i, Point src, int &dx, int &dy, Point &deltapt, double &delta, bool &collided, bool followrooms) {
672 src.x = (int)src.x;
673 src.y = (int)src.y;
675 shared_ptr<Room> room = bestRoomAt(src.x, src.y, i, shared_ptr<Room>());
677 if (!room) { // out of room system
678 if (!displaycore)
679 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % src.x % src.y), false);
680 grav.setInt(0);
681 displaycore = true;
682 return;
685 int lastdirection;
687 bool steep = abs(dy) > abs(dx);
689 int signdx = dx < 0 ? -1 : 1;
690 int signdy = dy < 0 ? -1 : 1;
692 Line l(Point(0,0),Point(dx,dy));
694 Point lastpoint(0,0);
696 for (int loc = 0; loc <= abs(steep ? dy : dx); loc++) {
697 Point p = steep ? l.pointAtY(loc*signdy) : l.pointAtX(loc*signdx);
698 p.x = (int)p.x;
699 p.y = (int)p.y;
701 bool trycollisions = false;
703 bool endofroom = false;
705 if (src.x + p.x < room->x_left) {
706 if (i != 1 && dx < 0) { trycollisions = true; lastdirection = 0; }
707 endofroom = true;
709 if (src.x + p.x > room->x_right) {
710 if (i != 0 && dx > 0) { trycollisions = true; lastdirection = 1; }
711 endofroom = true;
713 if (src.y + p.y < room->y_left_ceiling) {
714 if (i != 3 && dy < 0) { trycollisions = true; lastdirection = 2; }
715 endofroom = true;
717 if (src.y + p.y > room->y_left_floor) {
718 if (i != 2 && dy > 0) { trycollisions = true; lastdirection = 3; }
719 endofroom = true;
722 // find the next room, if necessary, and work out whether we should move into it or break out
723 if (endofroom) {
724 shared_ptr<Room> newroom = bestRoomAt(src.x + p.x, src.y + p.y, i, room);
726 bool collision = false;
728 // collide if we're out of the room system
729 if (!newroom) { collision = true; }
730 // collide if there's no new room connected to this one
731 else if (room->doors.find(newroom) == room->doors.end()) { collision = true; }
732 // collide if the PERM between this room and the new room is smaller than or equal to our size
733 else if (size.getInt() > room->doors[newroom]->perm) { collision = true; }
735 if (collision && (trycollisions || (!newroom))) {
736 collided = true;
737 break;
740 // move to the new room and keep going!
741 room = newroom;
744 if (room->floorpoints.size() && i == 3 && dy >= 0 && size.getInt() > room->floorvalue.getInt()) { // TODO: Hack!
745 // TODO: we don't check floorYatX isn't returning a 'real' room floor, but floorpoints should cover the whole floor anyway
746 int floory = room->floorYatX(src.x + p.x);
748 // never collide when the top point of an object is below the floor.
749 Point top = boundingBoxPoint(2);
751 // TODO: consider steep floors
752 if (src.y + p.y > floory && top.y < floory) {
753 collided = true;
754 lastdirection = 3;
755 break;
759 if ((!followrooms) && endofroom) break;
761 lastpoint = p;
764 double length2 = (lastpoint.x * lastpoint.x) + (lastpoint.y * lastpoint.y);
765 if (length2 < delta) {
766 // TODO: !followrooms is a horrible way to detect a non-physics call
767 if (collided && followrooms) lastcollidedirection = lastdirection;
768 deltapt = lastpoint;
769 delta = length2;
773 void Agent::physicsTickC2() {
774 int dx = velx.getInt(), dy = vely.getInt();
776 if (dx != 0 || dy != 0) grav.setInt(1);
778 if (grav.getInt() != 0 && sufferphysics()) {
779 dy += accg.getInt();
782 grav.setInt(1);
784 if (dy == 0 && dx == 0) { // nothing to do
785 if (vely.getInt() == 0) {
786 // really no motion
787 grav.setInt(0);
788 } else {
789 // y motion cancelled by gravity
790 vely.setInt(dy);
792 return;
794 vely.setInt(dy);
796 Point deltapt(0,0);
797 double delta = 1000000000;
799 bool collided = false;
801 if (suffercollisions()) {
802 for (unsigned int i = 0; i < 4; i++) {
803 Point src = boundingBoxPoint(i);
804 findCollisionInDirection(i, src, dx, dy, deltapt, delta, collided, true);
806 } else {
807 deltapt.x = dx;
808 deltapt.y = dy;
811 if (collided && (velx.getInt() != 0 || vely.getInt() != 0)) {
812 if (lastcollidedirection >= 2) // up and down
813 vely.setInt(-(vely.getInt() - (rest.getInt() * vely.getInt()) / 100));
814 else
815 velx.setInt(-(velx.getInt() - (rest.getInt() * velx.getInt()) / 100));
816 queueScript(6, 0);
818 if ((int)deltapt.x == 0 && (int)deltapt.y == 0) {
819 grav.setInt(0);
820 velx.setInt(0);
821 vely.setInt(0);
822 } else {
823 moveTo(x + (int)deltapt.x, y + (int)deltapt.y);
824 if (sufferphysics()) {
825 int fricx = (aero.getInt() * velx.getInt()) / 100;
826 int fricy = (aero.getInt() * vely.getInt()) / 100;
827 if (abs(velx.getInt()) > 0 && fricx == 0) fricx = (velx.getInt() < 0) ? -1 : 1;
828 if (abs(vely.getInt()) > 0 && fricy == 0) fricy = (vely.getInt() < 0) ? -1 : 1;
829 velx.setInt(velx.getInt() - fricx);
830 vely.setInt(vely.getInt() - fricy);
835 void Agent::tick() {
836 // sanity checks to stop ticks on dead agents
837 LifeAssert la(this);
838 if (dying) return;
840 if (sound) {
841 // if the sound is no longer playing...
842 if (sound->getState() != SS_PLAY) {
843 // ...release our reference to it
844 sound.reset();
845 } else {
846 // otherwise, reposition audio
847 updateAudio(sound);
851 // don't tick paused agents
852 if (paused) return;
854 // CA updates
855 if (emitca_index != -1 && emitca_amount != 0.0f) {
856 assert(0 <= emitca_index && emitca_index <= 19);
857 shared_ptr<Room> r = world.map.roomAt(x, y);
858 if (r) {
859 r->catemp[emitca_index] += emitca_amount;
860 /*if (r->catemp[emitca_index] <= 0.0f) r->catemp[emitca_index] = 0.0f;
861 else if (r->catemp[emitca_index] >= 1.0f) r->catemp[emitca_index] = 1.0f;*/
865 // tick the physics engine
866 physicsTick();
867 if (dying) return; // in case we were autokilled
869 // update the timer if needed, and then queue a timer event if necessary
870 if (timerrate) {
871 tickssincelasttimer++;
872 if (tickssincelasttimer == timerrate) {
873 queueScript(9); // TODO: include this?
874 tickssincelasttimer = 0;
876 assert(timerrate > tickssincelasttimer);
879 // tick the agent VM
880 if (vm) vmTick();
883 void Agent::unhandledException(std::string info, bool wasscript) {
884 // TODO: do something with this? empty the stack?
885 if (world.autostop) {
886 // autostop is mostly for Creatures Village, which is full of buggy scripts
887 stopScript();
888 } else if (world.autokill && !dynamic_cast<CreatureAgent *>(this)) { // don't autokill creatures! TODO: someday we probably should :)
889 kill();
890 if (wasscript)
891 std::cerr << identify() << " was autokilled during script " << lastScript << " due to: " << info << std::endl;
892 else
893 std::cerr << identify() << " was autokilled due to: " << info << std::endl;
894 } else {
895 stopScript();
896 if (wasscript)
897 std::cerr << identify() << " caused an exception during script " << lastScript << ": " << info << std::endl;
898 else
899 std::cerr << identify() << " caused an exception: " << info << std::endl;
903 void Agent::vmTick() {
904 // Tick the VM associated with this agent.
906 assert(vm); // There must *be* a VM to tick.
907 LifeAssert la(this); // sanity check
909 // If we're out of timeslice, give ourselves some more (depending on the game type).
910 if (!vm->timeslice) {
911 vm->timeslice = (engine.version < 3) ? 1 : 5;
914 // Keep trying to run VMs while we don't run out of timeslice, end up with a blocked VM, or a VM stops.
915 while (vm && vm->timeslice && !vm->isBlocking() && !vm->stopped()) {
916 assert(vm->timeslice > 0);
918 // Tell the VM to tick (using all available timeslice), catching exceptions as necessary.
919 try {
920 vm->tick();
921 } catch (invalidAgentException &e) {
922 // try letting the exception script handle it
923 if (!queueScript(255))
924 unhandledException(e.prettyPrint(), true);
925 else
926 stopScript(); // we still want current script to die
927 } catch (creaturesException &e) {
928 unhandledException(e.prettyPrint(), true);
929 } catch (std::exception &e) {
930 unhandledException(e.what(), true);
933 // If the VM stopped, it's done.
934 if (vm && vm->stopped()) {
935 world.freeVM(vm);
936 vm = NULL;
940 // Zot any remaining timeslice, since we're done now.
941 if (vm) vm->timeslice = 0;
943 // If there's no current VM but there's one on the call stack, a previous VM must have finished,
944 // pop one from the call stack to run next time.
945 if (!vm && !vmstack.empty()) {
946 vm = vmstack.front();
947 vmstack.pop_front();
951 Agent::~Agent() {
952 assert(lifecount == 0);
954 if (!initialized) return;
955 if (!dying) {
956 // we can't do kill() here because we can't do anything which might try using our shared_ptr
957 // (since this could be during world destruction)
959 if (vm) {
960 vm->stop();
961 world.freeVM(vm);
962 vm = 0;
965 zotstack();
967 if (sound) {
968 sound->stop();
969 sound.reset();
974 void Agent::kill() {
975 assert(!dying);
976 if (floatable()) floatRelease();
977 if (carrying) dropCarried(carrying);
978 // TODO: should the carried agent really be responsible for dropping from vehicle?
979 if (invehicle) invehicle->drop(this);
981 dying = true; // what a world, what a world...
983 if (vm) {
984 vm->stop();
985 world.freeVM(vm);
986 vm = 0;
989 zotstack();
990 agents_iter->reset();
992 if (sound) {
993 sound->stop();
994 sound.reset();
998 unsigned int Agent::getZOrder() const {
999 if (invehicle) {
1000 // TODO: take notice of cabp in c2e, at least. also, stacking .. ?
1001 Vehicle *v = dynamic_cast<Vehicle *>(invehicle.get());
1002 assert(v);
1003 if (engine.version < 3)
1004 return v->cabinplane; // TODO: correct?
1005 else
1006 return v->getZOrder() + v->cabinplane;
1007 // TODO: Vehicle should probably rearrange zorder of passengers if ever moved
1008 } else if (carriedby) {
1009 // TODO: check for overflow
1010 // TODO: is adding our own zorder here correct behaviour? someone should check
1011 if (engine.version > 1)
1012 return carriedby->getZOrder() - 100;
1013 else
1014 return carriedby->getZOrder();
1015 } else {
1016 return zorder;
1020 void Agent::setZOrder(unsigned int z) {
1021 if (dying) return;
1022 zorder = z;
1025 int Agent::getUNID() const {
1026 if (unid != -1)
1027 return unid;
1028 return unid = world.newUNID(const_cast<Agent *>(this));
1031 #include "Catalogue.h"
1033 std::string Agent::identify() const {
1034 std::ostringstream o;
1035 o << (int)family << " " << (int)genus << " " << species;
1036 const std::string n = catalogue.getAgentName(family, genus, species);
1037 if (n.size())
1038 o << " (" + n + ")";
1039 /*if (unid != -1)
1040 o << " unid " << unid;
1041 else
1042 o << " (no unid assigned)"; */
1043 return o.str();
1046 bool agentzorder::operator ()(const Agent *s1, const Agent *s2) const {
1047 return s1->getZOrder() < s2->getZOrder();
1050 void Agent::pushVM(caosVM *newvm) {
1051 assert(newvm);
1052 if (vm)
1053 vmstack.push_front(vm);
1054 vm = newvm;
1057 bool Agent::vmStopped() {
1058 return (!vm || vm->stopped());
1061 void Agent::stopScript() {
1062 zotstack();
1063 if (vm)
1064 vm->stop();
1067 void Agent::addCarried(AgentRef a) {
1068 assert(a);
1070 // TODO: muh, vehicle drop needs more thought
1071 if (a->invehicle) {
1072 Vehicle *v = dynamic_cast<Vehicle *>(a->invehicle.get());
1073 assert(v);
1074 v->dropCarried(a);
1077 carry(a);
1079 // TODO: this doesn't reorder children or anything..
1080 a->setZOrder(a->zorder);
1082 // fire 'Got Carried Agent'
1083 if (engine.version >= 3)
1084 queueScript(124, a); // TODO: is this the correct param?
1087 void Agent::carry(AgentRef a) {
1088 assert(a);
1090 // TODO: check for infinite loops (eg, us carrying an agent which is carrying us) :)
1092 if (carrying)
1093 dropCarried(carrying);
1095 carrying = a;
1097 a->carriedby = AgentRef(this);
1098 // TODO: move carrying agent to the right position
1099 adjustCarried(0, 0);
1102 bool Agent::beDropped() {
1103 carriedby = AgentRef(0);
1104 bool wasinvehicle = invehicle;
1105 invehicle = AgentRef(0);
1107 // TODO: this doesn't reorder children or anything..
1108 setZOrder(zorder);
1110 // TODO: no idea if this is right, it tries to re-enable gravity when dropping C2 agents
1111 if (engine.version == 2) grav.setInt(1);
1112 if (engine.version == 3) falling = true;
1114 if (!wasinvehicle) { // ie, we're not being dropped by a vehicle
1115 // TODO: check for vehicles in a saner manner?
1116 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
1117 boost::shared_ptr<Agent> a = (*i);
1118 if (!a) continue;
1119 Vehicle *v = dynamic_cast<Vehicle *>(a.get());
1120 if (!v) continue;
1122 if (agentsTouching(this, v)) {
1123 v->addCarried(this);
1124 // TODO: how to handle not-invehicle case, where vehicle has failed to pick us up?
1125 if (invehicle) return true;
1130 if (engine.version == 1) {
1131 MetaRoom* m = world.map.metaRoomAt(x, y);
1132 if (!m) return false;
1133 shared_ptr<Room> r = m->nextFloorFromPoint(x, y);
1134 if (!r) return false;
1135 moveTo(x, r->bot.pointAtX(x).y - getHeight());
1136 } else {
1137 // TODO: maybe think about this some more
1138 tryMoveToPlaceAround(x, y);
1141 // TODO: return value is not used anywhere yet?
1142 return true;
1145 void Agent::dropCarried(AgentRef a) {
1146 drop(a);
1148 // TODO: this doesn't reorder children or anything..
1149 a->setZOrder(a->zorder);
1151 // fire 'Lost Carried Agent'
1152 if (engine.version >= 3)
1153 queueScript(125, a); // TODO: is this the correct param?
1156 void Agent::drop(AgentRef a) {
1157 if (!carrying) return;
1158 assert(carrying == a);
1160 a->beDropped();
1161 carrying = AgentRef(0);
1164 std::pair<int, int> Agent::getCarryPoint() {
1165 unsigned int ourpose = 0;
1167 SpritePart *s;
1168 if ((s = dynamic_cast<SpritePart *>(part(0))))
1169 ourpose = s->getBase() + s->getPose();
1171 std::pair<int, int> pos(0, 0);
1173 std::map<unsigned int, std::pair<int, int> >::iterator i = carry_points.find(ourpose);
1174 if (i != carry_points.end())
1175 pos = i->second;
1177 return pos;
1180 std::pair<int, int> Agent::getCarriedPoint() {
1181 unsigned int theirpose = 0;
1183 SpritePart *s;
1184 if ((s = dynamic_cast<SpritePart *>(part(0))))
1185 theirpose = s->getBase() + s->getPose();
1187 std::pair<int, int> pos(0, 0);
1189 std::map<unsigned int, std::pair<int, int> >::iterator i = carried_points.find(theirpose);
1190 if (i != carried_points.end()) {
1191 pos = i->second;
1192 } else if (s && engine.version > 1) {
1193 // TODO: why fudge pickup location here and not in default carried points or something?
1194 // (this is nornagon's code which i moved - fuzzie)
1195 pos.first = s->getSprite()->width(s->getCurrentSprite()) / 2;
1198 return pos;
1201 void Agent::adjustCarried(float unusedxoffset, float unusedyoffset) {
1202 // Adjust the position of the agent we're carrying.
1203 // TODO: this doesn't actually position the carried agent correctly, sigh
1205 if (!carrying) return;
1207 unsigned int ourpose = 0;
1209 int xoffset = 0, yoffset = 0;
1210 if (engine.version < 3 && world.hand() == this) {
1211 // this appears to produce correct behaviour in the respective games, don't ask me -nornagon
1212 if (engine.version == 2) {
1213 xoffset = world.hand()->getWidth() / 2;
1214 yoffset = world.hand()->getHeight() / 2 - 2;
1215 } else
1216 yoffset = world.hand()->getHeight() / 2 - 3;
1219 std::pair<int, int> carrypoint = getCarryPoint();
1220 xoffset += carrypoint.first;
1221 yoffset += carrypoint.second;
1223 std::pair<int, int> carriedpoint = carrying->getCarriedPoint();
1224 xoffset -= carriedpoint.first;
1225 yoffset -= carriedpoint.second;
1227 if (xoffset != 0 || yoffset != 0)
1228 carrying->moveTo(x + xoffset, y + yoffset, true);
1231 void Agent::setClassifier(unsigned char f, unsigned char g, unsigned short s) {
1232 family = f;
1233 genus = g;
1234 species = s;
1236 if (engine.version < 3) {
1237 // TODO: categories for other game versions
1238 category = -1;
1239 } else {
1240 category = world.findCategory(family, genus, species);
1244 bool Agent::tryMoveToPlaceAround(float x, float y) {
1245 if (!suffercollisions()) {
1246 moveTo(x, y);
1247 return true;
1250 // second hacky attempt, move from side to side (+/- width) and up (- height) a little
1251 unsigned int trywidth = getWidth() * 2; if (trywidth < 100) trywidth = 100;
1252 unsigned int tryheight = getHeight() * 2; if (tryheight < 100) tryheight = 100;
1253 for (unsigned int xadjust = 0; xadjust < trywidth; xadjust++) {
1254 for (unsigned int yadjust = 0; yadjust < tryheight; yadjust++) {
1255 if (validInRoomSystem(Point(x - xadjust, y - yadjust), getWidth(), getHeight(), perm))
1256 moveTo(x - xadjust, y - yadjust);
1257 else if ((xadjust != 0) && validInRoomSystem(Point(x + xadjust, y - yadjust), getWidth(), getHeight(), perm))
1258 moveTo(x + xadjust, y - yadjust);
1259 else
1260 continue;
1261 return true;
1265 return false;
1268 /* vim: set noet: */