fixes to Vehicle::carry - only reject too-big passengers in c2e mode, plus other...
[openc2e.git] / Agent.cpp
bloba5bce2f6d846614a2d13d8c0a5787db8eef3e4e5
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 && !engine.bmprenderer) { // TODO: need to think about bmp-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 from->dropCarried(this); // TODO: correct?
249 break;
250 case 12: // eat
251 if (c && !cr_can_eat) return false;
252 break;
253 case 13: // hold hands with pointer
254 if (c) {
255 // TODO
257 break;
258 case 14: // stop holding hands with pointer
259 if (c) {
260 // TODO
262 break;
263 case 92: // TODO: hack for 'UI Mouse Down' event - we need a real event system!
264 std::cout << "faking event 92 on " << identify() << std::endl;
265 CompoundPart *p = world.partAt(world.hand()->x, world.hand()->y);
266 if (!p || p->getParent() != this) // if something is horridly broken here, return
267 return false; // was caos_assert(p && p->getParent() == this);
268 p->handleClick(world.hand()->x - p->x - p->getParent()->x, world.hand()->y - p->y - p->getParent()->y);
269 // TODO: we're [obviously] missing firing the pointer script here, but it's a hack for now
270 break;
273 bool ranscript = false;
275 shared_ptr<script> s = findScript(event);
276 if (s) {
277 bool madevm = false;
278 if (!vm) { madevm = true; vm = world.getVM(this); }
280 if (vm->fireScript(s, event == 9, 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 if (invehicle) break;
304 if (engine.version > 1) break;
306 // Creatures 1 drop snapping
307 // TODO: this probably doesn't belong here, but it has to be run after the
308 // drop script starts (see for instance C1 carrots, which change pose)
309 MetaRoom* m = world.map.metaRoomAt(x, y);
310 if (!m) break;
311 shared_ptr<Room> r = m->nextFloorFromPoint(x, y);
312 if (!r) break;
313 moveTo(x, r->bot.pointAtX(x).y - getHeight());
315 break;
318 return ranscript;
321 bool Agent::queueScript(unsigned short event, AgentRef from, caosVar p0, caosVar p1) {
322 // Queue a script for execution on the VM of this agent.
324 if (dying) return false;
326 // only bother firing the event if either it exists, or it's one with engine code attached
327 // TODO: why don't we do the engine checks/etc here?
328 switch (event) {
329 default:
330 if (!findScript(event)) return false;
332 case 0:
333 case 1:
334 case 2:
335 case 3:
336 case 4:
337 case 5:
338 case 12:
339 case 13:
340 case 14:
341 case 92:
342 world.queueScript(event, this, from, p0, p1);
345 return true;
348 int Agent::handleClick(float clickx, float clicky) {
349 // Handle a mouse click.
350 if (!activateable()) return -1;
352 // old-style click handling (c1/c2)
353 if (engine.version < 3) {
354 int action = -1;
356 // look up the relevant action for our ACTV state from the clac table
357 if ((unsigned int)actv.getInt() < 3)
358 action = clac[actv.getInt()];
360 if (action != -1) {
361 return calculateScriptId(action);
364 return -1;
367 // new-style click handling (c2e)
368 if (clik != -1) {
369 return -1; // TODO: handle CLIK
370 } else if (clac[0] != -1) {
371 return calculateScriptId(clac[0]);
374 return -1;
377 void Agent::playAudio(std::string filename, bool controlled, bool loop) {
378 assert(!dying);
380 sound.reset();
381 world.playAudio(filename, this, controlled, loop);
384 bool agentOnCamera(Agent *targ, bool checkall = false); // caosVM_camera.cpp
386 void Agent::updateAudio(boost::shared_ptr<AudioSource> s) {
387 assert(s);
389 s->setPos(x + getWidth() / 2, y + getHeight() / 2, zorder);
390 // TODO: we could do with a nicer 'inrange' check
391 bool inrange = (x + 500 > world.camera.getX()) && (x + getWidth() - 500 < world.camera.getX() + world.camera.getWidth()) &&
392 (y + 500 > world.camera.getY()) && (y + getHeight() - 500 < world.camera.getY() + world.camera.getHeight());
393 s->setMute(
394 world.camera.getMetaRoom() != world.map.metaRoomAt(x, y)
396 !inrange
398 // TODO: setVelocity?
401 Point const Agent::boundingBoxPoint(unsigned int n) {
402 return boundingBoxPoint(n, Point(x, y), getWidth(), getHeight());
405 Point const Agent::boundingBoxPoint(unsigned int n, Point in, unsigned int w, unsigned int h) {
406 Point p;
408 switch (n) {
409 case 0: // left
410 p.x = in.x;
411 p.y = in.y + (h / 2.0f);
412 break;
414 case 1: // right
415 p.x = in.x + w;
416 p.y = in.y + (h / 2.0f);
417 break;
419 case 2: // top
420 p.x = in.x + (w / 2.0f);
421 p.y = in.y;
422 break;
424 case 3: // bottom
425 p.x = in.x + (w / 2.0f);
426 p.y = in.y + h;
427 break;
429 default:
430 throw creaturesException("Agent::boundingBoxPoint got unknown direction");
433 return p;
436 bool Agent::validInRoomSystem() {
437 // Return true if this agent is inside the world room system at present, or false if it isn't.
439 // TODO: c1
440 if (engine.version == 1) return true;
442 return validInRoomSystem(Point(x, y), getWidth(), getHeight(), perm);
445 bool const Agent::validInRoomSystem(Point p, unsigned int w, unsigned int h, int testperm) {
446 // Return true if this agent is inside the world room system at the specified point, or false if it isn't.
448 for (unsigned int i = 0; i < 4; i++) {
449 Point src, dest;
450 switch (i) {
451 case 0: src = boundingBoxPoint(0, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // left to bottom
452 case 1: src = boundingBoxPoint(1, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // right to bottom
453 case 2: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(0, p, w, h); break; // top to left
454 case 3: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(1, p, w, h); break; // top to right
457 if (engine.version == 2) {
458 // Creatures 2 physics
460 int dx = dest.x - src.x;
461 int dy = dest.y - src.y;
463 Point deltapt(0,0);
464 double delta = 1000000000;
465 bool collided = false;
467 // TODO: check suffercollisions?
469 // TODO: muh, provided direction here is kinda a hack
470 findCollisionInDirection((i < 2) ? 3 : 2, src, dx, dy, deltapt, delta, collided, true);
471 if (collided) return false;
472 } else {
473 // Creatures 3 physics
475 float srcx = src.x, srcy = src.y;
477 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
478 if (!ourRoom) return false;
480 unsigned int dir; Line wall;
481 world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, wall, dir, testperm);
483 if (src != dest) return false;
487 return true;
490 void Agent::physicsTick() {
491 if (engine.version == 1) return; // C1 has no physics, and different attributes.
493 if (carriedby) return; // We don't move when carried, so what's the point?
495 if (engine.version == 2) {
496 // Creatures 2 physics is different.
497 physicsTickC2();
498 return;
501 if (!wasmoved) return; // some agents are created outside INST and get autokilled if we try physics on them before they move
503 // set destination point based on velocities
504 float destx = x + velx.getFloat();
505 float desty = y + vely.getFloat();
507 if (sufferphysics()) {
508 // TODO: falling behaviour needs looking at more closely..
509 // .. but it shouldn't be 'false' by default on non-physics agents, so..
510 falling = false;
511 // increase speed according to accg
512 // TODO: should we be changing vely first, instead of after a successful move (below)?
513 desty += accg.getFloat();
516 if (suffercollisions()) {
517 float lastdistance = 1000000.0f;
518 bool collided = false;
519 Line wall; // only valid when collided
520 unsigned int collidedirection = 0; // only valid when collided
521 Point bestmove;
523 // iterate through all four points of the bounding box
524 for (unsigned int i = 0; i < 4; i++) {
525 // this mess is because we want to start with the bottom point - DOWN (3) - before the others, for efficiency
526 Point src = boundingBoxPoint((i == 0) ? 3 : i - 1);
528 // store values
529 float srcx = src.x, srcy = src.y;
531 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
532 if (!ourRoom) {
533 ourRoom = world.map.roomAt(srcx, srcy);
535 if (!ourRoom) {
536 if (!displaycore) { // TODO: ugh, displaycore is a horrible thing to use for this
537 // we're out of the room system, physics bug, but let's try MVSFing back in to cover for fuzzie's poor programming skills
538 static bool tryingmove; tryingmove = false; // avoid infinite loop
539 if (!tryingmove && tryMoveToPlaceAround(x, y)) {
540 //std::cout << identify() << " was out of room system due to a physics bug but we hopefully moved it back in.." << std::endl;
541 tryingmove = true;
542 physicsTick();
543 return;
546 // didn't work!
547 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % srcx % srcy), false);
549 displaycore = true;
550 return; // out of room system
553 Point dest(destx + (srcx - x), desty + (srcy - y));
554 unsigned int local_collidedirection;
555 Line local_wall;
557 // this changes src to the point at which we end up
558 bool local_collided = world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, local_wall, local_collidedirection, perm);
560 float dist;
561 if (src.x == srcx && src.y == srcy)
562 dist = 0.0f;
563 else {
564 float xdiff = src.x - srcx;
565 float ydiff = src.y - srcy;
566 dist = xdiff*xdiff + ydiff*ydiff;
569 if (dist >= lastdistance) {
570 assert(i != 0); // this had better not be our first collision!
571 continue; // further away than a previous collision
574 lastdistance = dist;
575 bestmove.x = x + (src.x - srcx);
576 bestmove.y = y + (src.y - srcy);
577 collidedirection = local_collidedirection;
578 wall = local_wall;
579 collided = local_collided;
581 if (dist == 0.0f)
582 break; // no point checking any more, is there?
585 // *** do actual movement
586 if (lastdistance != 0.0f) {
587 moveTo(bestmove.x, bestmove.y);
589 if (collided) {
590 lastcollidedirection = collidedirection;
591 queueScript(6, 0, velx, vely); // TODO: include this? .. we need to include SOMETHING, c3 ball checks for <3
593 if (elas != 0) {
594 if (wall.getType() == HORIZONTAL) {
595 vely.setFloat(-vely.getFloat());
596 } else if (wall.getType() == VERTICAL) {
597 velx.setFloat(-velx.getFloat());
598 } else {
599 // line starts always have a lower x value than the end
600 float xdiff = wall.getEnd().x - wall.getStart().x;
601 float ydiff = wall.getEnd().y - wall.getStart().y;
602 float fvelx = velx.getFloat(), fvely = vely.getFloat();
604 // calculate input/slope angles
605 double inputangle;
606 if (fvelx == 0.0f) {
607 if (fvely > 0.0f)
608 inputangle = M_PI / 2.0;
609 else
610 inputangle = 3 * (M_PI / 2.0);
611 } else {
612 inputangle = atan(fvely / fvelx);
614 double slopeangle = atan(-ydiff / xdiff); // xdiff != 0 because wall isn't vertical
616 // calculate output angle
617 double outputangle = slopeangle + (slopeangle - inputangle) + M_PI;
619 // turn back into component velocities
620 double vectorlength = sqrt(fvelx*fvelx + fvely*fvely);
621 float xoutput = cos(outputangle) * vectorlength;
622 float youtput = sin(outputangle) * vectorlength;
624 velx.setFloat(xoutput);
625 vely.setFloat(-youtput);
628 if (elas != 100.0f) {
629 velx.setFloat(velx.getFloat() * (elas / 100.0f));
630 vely.setFloat(vely.getFloat() * (elas / 100.0f));
632 } else
633 vely.setFloat(0);
634 } else if (sufferphysics() && accg != 0) {
635 falling = true; // TODO: icky
636 vely.setFloat(vely.getFloat() + accg.getFloat());
638 } else { velx.setFloat(0); vely.setFloat(0); } // TODO: correct?
639 } else {
640 if (vely.hasDecimal() || velx.hasDecimal())
641 moveTo(destx, desty);
642 if (sufferphysics())
643 vely.setFloat(vely.getFloat() + accg.getFloat());
646 if (sufferphysics() && (aero != 0)) {
647 // reduce speed according to AERO
648 // TODO: aero should be an integer!
649 velx.setFloat(velx.getFloat() - (velx.getFloat() * (aero.getFloat() / 100.0f)));
650 vely.setFloat(vely.getFloat() - (vely.getFloat() * (aero.getFloat() / 100.0f)));
654 shared_ptr<Room> const Agent::bestRoomAt(unsigned int tryx, unsigned int tryy, unsigned int direction, shared_ptr<Room> exclude) {
655 std::vector<shared_ptr<Room> > rooms = world.map.roomsAt(tryx, tryy);
657 shared_ptr<Room> r;
659 if (rooms.size() == 0) return shared_ptr<Room>();
660 if (rooms.size() == 1) r = rooms[0];
661 else if (rooms.size() > 1) {
662 unsigned int j;
663 for (j = 0; j < rooms.size(); j++) {
664 if (rooms[j] == exclude) continue;
666 if (direction == 0) { // left
667 if (rooms[j]->x_left == tryx) break;
668 } else if (direction == 1) { // right
669 if (rooms[j]->x_right == tryx) break;
670 } else if (direction == 2) { // top
671 if (rooms[j]->y_left_ceiling == tryy) break;
672 } else { // down
673 if (rooms[j]->y_left_floor == tryy) break;
676 if (j == rooms.size()) j = 0;
677 r = rooms[j];
680 if (r == exclude) return shared_ptr<Room>();
681 else return r;
684 // Creatures 2 collision finding
685 void const Agent::findCollisionInDirection(unsigned int i, Point src, int &dx, int &dy, Point &deltapt, double &delta, bool &collided, bool followrooms) {
686 src.x = (int)src.x;
687 src.y = (int)src.y;
689 shared_ptr<Room> room = bestRoomAt(src.x, src.y, i, shared_ptr<Room>());
691 if (!room) { // out of room system
692 if (!displaycore)
693 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % src.x % src.y), false);
694 grav.setInt(0);
695 displaycore = true;
696 return;
699 int lastdirection;
701 bool steep = abs(dy) > abs(dx);
703 int signdx = dx < 0 ? -1 : 1;
704 int signdy = dy < 0 ? -1 : 1;
706 Line l(Point(0,0),Point(dx,dy));
708 Point lastpoint(0,0);
710 for (int loc = 0; loc <= abs(steep ? dy : dx); loc++) {
711 Point p = steep ? l.pointAtY(loc*signdy) : l.pointAtX(loc*signdx);
712 p.x = (int)p.x;
713 p.y = (int)p.y;
715 bool trycollisions = false;
717 bool endofroom = false;
719 if (src.x + p.x < room->x_left) {
720 if (i != 1 && dx < 0) { trycollisions = true; lastdirection = 0; }
721 endofroom = true;
723 if (src.x + p.x > room->x_right) {
724 if (i != 0 && dx > 0) { trycollisions = true; lastdirection = 1; }
725 endofroom = true;
727 if (src.y + p.y < room->y_left_ceiling) {
728 if (i != 3 && dy < 0) { trycollisions = true; lastdirection = 2; }
729 endofroom = true;
731 if (src.y + p.y > room->y_left_floor) {
732 if (i != 2 && dy > 0) { trycollisions = true; lastdirection = 3; }
733 endofroom = true;
736 // find the next room, if necessary, and work out whether we should move into it or break out
737 if (endofroom) {
738 shared_ptr<Room> newroom = bestRoomAt(src.x + p.x, src.y + p.y, i, room);
740 bool collision = false;
742 // collide if we're out of the room system
743 if (!newroom) { collision = true; }
744 // collide if there's no new room connected to this one
745 else if (room->doors.find(newroom) == room->doors.end()) { collision = true; }
746 // collide if the PERM between this room and the new room is smaller than or equal to our size
747 else if (size.getInt() > room->doors[newroom]->perm) { collision = true; }
749 if (collision && (trycollisions || (!newroom))) {
750 collided = true;
751 break;
754 // move to the new room and keep going!
755 room = newroom;
758 if (room->floorpoints.size() && i == 3 && dy >= 0 && size.getInt() > room->floorvalue.getInt()) { // TODO: Hack!
759 // TODO: we don't check floorYatX isn't returning a 'real' room floor, but floorpoints should cover the whole floor anyway
760 int floory = room->floorYatX(src.x + p.x);
762 // never collide when the top point of an object is below the floor.
763 Point top = boundingBoxPoint(2);
765 // TODO: consider steep floors
766 if (src.y + p.y > floory && top.y < floory) {
767 collided = true;
768 lastdirection = 3;
769 break;
773 if ((!followrooms) && endofroom) break;
775 lastpoint = p;
778 double length2 = (lastpoint.x * lastpoint.x) + (lastpoint.y * lastpoint.y);
779 if (length2 < delta) {
780 // TODO: !followrooms is a horrible way to detect a non-physics call
781 if (collided && followrooms) lastcollidedirection = lastdirection;
782 deltapt = lastpoint;
783 delta = length2;
787 void Agent::physicsTickC2() {
788 int dx = velx.getInt(), dy = vely.getInt();
790 if (dx != 0 || dy != 0) grav.setInt(1);
792 if (grav.getInt() != 0 && sufferphysics()) {
793 dy += accg.getInt();
796 grav.setInt(1);
798 if (dy == 0 && dx == 0) { // nothing to do
799 if (vely.getInt() == 0) {
800 // really no motion
801 grav.setInt(0);
802 } else {
803 // y motion cancelled by gravity
804 vely.setInt(dy);
806 return;
808 vely.setInt(dy);
810 Point deltapt(0,0);
811 double delta = 1000000000;
813 bool collided = false;
815 if (suffercollisions()) {
816 for (unsigned int i = 0; i < 4; i++) {
817 Point src = boundingBoxPoint(i);
818 findCollisionInDirection(i, src, dx, dy, deltapt, delta, collided, true);
820 } else {
821 deltapt.x = dx;
822 deltapt.y = dy;
825 if (collided && (velx.getInt() != 0 || vely.getInt() != 0)) {
826 if (lastcollidedirection >= 2) // up and down
827 vely.setInt(-(vely.getInt() - (rest.getInt() * vely.getInt()) / 100));
828 else
829 velx.setInt(-(velx.getInt() - (rest.getInt() * velx.getInt()) / 100));
830 queueScript(6, 0);
832 if ((int)deltapt.x == 0 && (int)deltapt.y == 0) {
833 grav.setInt(0);
834 velx.setInt(0);
835 vely.setInt(0);
836 } else {
837 moveTo(x + (int)deltapt.x, y + (int)deltapt.y);
838 if (sufferphysics()) {
839 int fricx = (aero.getInt() * velx.getInt()) / 100;
840 int fricy = (aero.getInt() * vely.getInt()) / 100;
841 if (abs(velx.getInt()) > 0 && fricx == 0) fricx = (velx.getInt() < 0) ? -1 : 1;
842 if (abs(vely.getInt()) > 0 && fricy == 0) fricy = (vely.getInt() < 0) ? -1 : 1;
843 velx.setInt(velx.getInt() - fricx);
844 vely.setInt(vely.getInt() - fricy);
849 void Agent::tick() {
850 // sanity checks to stop ticks on dead agents
851 LifeAssert la(this);
852 if (dying) return;
854 if (sound) {
855 // if the sound is no longer playing...
856 if (sound->getState() != SS_PLAY) {
857 // ...release our reference to it
858 sound.reset();
859 } else {
860 // otherwise, reposition audio
861 updateAudio(sound);
865 // don't tick paused agents
866 if (paused) return;
868 // CA updates
869 if (emitca_index != -1 && emitca_amount != 0.0f) {
870 assert(0 <= emitca_index && emitca_index <= 19);
871 shared_ptr<Room> r = world.map.roomAt(x, y);
872 if (r) {
873 r->catemp[emitca_index] += emitca_amount;
874 /*if (r->catemp[emitca_index] <= 0.0f) r->catemp[emitca_index] = 0.0f;
875 else if (r->catemp[emitca_index] >= 1.0f) r->catemp[emitca_index] = 1.0f;*/
879 // tick the physics engine
880 physicsTick();
881 if (dying) return; // in case we were autokilled
883 // update the timer if needed, and then queue a timer event if necessary
884 if (timerrate) {
885 tickssincelasttimer++;
886 if (tickssincelasttimer == timerrate) {
887 queueScript(9); // TODO: include this?
888 tickssincelasttimer = 0;
890 assert(timerrate > tickssincelasttimer);
893 // tick the agent VM
894 if (vm) vmTick();
897 void Agent::unhandledException(std::string info, bool wasscript) {
898 // TODO: do something with this? empty the stack?
899 if (world.autostop) {
900 // autostop is mostly for Creatures Village, which is full of buggy scripts
901 stopScript();
902 } else if (world.autokill && !dynamic_cast<CreatureAgent *>(this)) { // don't autokill creatures! TODO: someday we probably should :)
903 kill();
904 if (wasscript)
905 std::cerr << identify() << " was autokilled during script " << lastScript << " due to: " << info << std::endl;
906 else
907 std::cerr << identify() << " was autokilled due to: " << info << std::endl;
908 } else {
909 stopScript();
910 if (wasscript)
911 std::cerr << identify() << " caused an exception during script " << lastScript << ": " << info << std::endl;
912 else
913 std::cerr << identify() << " caused an exception: " << info << std::endl;
917 void Agent::vmTick() {
918 // Tick the VM associated with this agent.
920 assert(vm); // There must *be* a VM to tick.
921 LifeAssert la(this); // sanity check
923 // If we're out of timeslice, give ourselves some more (depending on the game type).
924 if (!vm->timeslice) {
925 vm->timeslice = (engine.version < 3) ? 1 : 5;
928 // Keep trying to run VMs while we don't run out of timeslice, end up with a blocked VM, or a VM stops.
929 while (vm && vm->timeslice && !vm->isBlocking() && !vm->stopped()) {
930 assert(vm->timeslice > 0);
932 // Tell the VM to tick (using all available timeslice), catching exceptions as necessary.
933 try {
934 vm->tick();
935 } catch (invalidAgentException &e) {
936 // try letting the exception script handle it
937 if (!queueScript(255))
938 unhandledException(e.prettyPrint(), true);
939 else
940 stopScript(); // we still want current script to die
941 } catch (creaturesException &e) {
942 unhandledException(e.prettyPrint(), true);
943 } catch (std::exception &e) {
944 unhandledException(e.what(), true);
947 // If the VM stopped, it's done.
948 if (vm && vm->stopped()) {
949 world.freeVM(vm);
950 vm = NULL;
954 // Zot any remaining timeslice, since we're done now.
955 if (vm) vm->timeslice = 0;
957 // If there's no current VM but there's one on the call stack, a previous VM must have finished,
958 // pop one from the call stack to run next time.
959 if (!vm && !vmstack.empty()) {
960 vm = vmstack.front();
961 vmstack.pop_front();
965 Agent::~Agent() {
966 assert(lifecount == 0);
968 if (!initialized) return;
969 if (!dying) {
970 // we can't do kill() here because we can't do anything which might try using our shared_ptr
971 // (since this could be during world destruction)
973 if (vm) {
974 vm->stop();
975 world.freeVM(vm);
976 vm = 0;
979 zotstack();
981 if (sound) {
982 sound->stop();
983 sound.reset();
988 void Agent::kill() {
989 assert(!dying);
990 if (floatable()) floatRelease();
991 if (carrying) dropCarried(carrying);
992 // TODO: should the carried agent really be responsible for dropping from vehicle?
993 if (invehicle) invehicle->drop(this);
995 dying = true; // what a world, what a world...
997 if (vm) {
998 vm->stop();
999 world.freeVM(vm);
1000 vm = 0;
1003 zotstack();
1004 agents_iter->reset();
1006 if (sound) {
1007 sound->stop();
1008 sound.reset();
1012 unsigned int Agent::getZOrder() const {
1013 if (invehicle) {
1014 // TODO: take notice of cabp in c2e, at least. also, stacking .. ?
1015 Vehicle *v = dynamic_cast<Vehicle *>(invehicle.get());
1016 assert(v);
1017 if (engine.version < 3)
1018 return v->cabinplane; // TODO: correct?
1019 else
1020 return v->getZOrder() + v->cabinplane;
1021 // TODO: Vehicle should probably rearrange zorder of passengers if ever moved
1022 } else if (carriedby) {
1023 // TODO: check for overflow
1024 // TODO: is adding our own zorder here correct behaviour? someone should check
1025 if (engine.version > 1)
1026 return carriedby->getZOrder() - 100;
1027 else
1028 return carriedby->getZOrder();
1029 } else {
1030 return zorder;
1034 void Agent::setZOrder(unsigned int z) {
1035 if (dying) return;
1036 zorder = z;
1039 int Agent::getUNID() const {
1040 if (unid != -1)
1041 return unid;
1042 return unid = world.newUNID(const_cast<Agent *>(this));
1045 #include "Catalogue.h"
1047 std::string Agent::identify() const {
1048 std::ostringstream o;
1049 o << (int)family << " " << (int)genus << " " << species;
1050 const std::string n = catalogue.getAgentName(family, genus, species);
1051 if (n.size())
1052 o << " (" + n + ")";
1053 /*if (unid != -1)
1054 o << " unid " << unid;
1055 else
1056 o << " (no unid assigned)"; */
1057 return o.str();
1060 bool agentzorder::operator ()(const Agent *s1, const Agent *s2) const {
1061 return s1->getZOrder() < s2->getZOrder();
1064 void Agent::pushVM(caosVM *newvm) {
1065 assert(newvm);
1066 if (vm)
1067 vmstack.push_front(vm);
1068 vm = newvm;
1071 bool Agent::vmStopped() {
1072 return (!vm || vm->stopped());
1075 void Agent::stopScript() {
1076 zotstack();
1077 if (vm)
1078 vm->stop();
1081 void Agent::addCarried(AgentRef a) {
1082 assert(a);
1084 // TODO: muh, vehicle drop needs more thought
1085 if (a->invehicle) {
1086 Vehicle *v = dynamic_cast<Vehicle *>(a->invehicle.get());
1087 assert(v);
1088 v->dropCarried(a);
1091 carry(a);
1093 // TODO: this doesn't reorder children or anything..
1094 a->setZOrder(a->zorder);
1096 // fire 'Got Carried Agent'
1097 if (engine.version >= 3)
1098 queueScript(124, a); // TODO: is this the correct param?
1101 void Agent::carry(AgentRef a) {
1102 assert(a);
1104 // TODO: check for infinite loops (eg, us carrying an agent which is carrying us) :)
1106 if (carrying)
1107 dropCarried(carrying);
1109 carrying = a;
1111 a->carriedby = AgentRef(this);
1112 // TODO: move carrying agent to the right position
1113 adjustCarried(0, 0);
1116 bool Agent::beDropped() {
1117 carriedby = AgentRef(0);
1118 bool wasinvehicle = invehicle;
1119 invehicle = AgentRef(0);
1121 // TODO: this doesn't reorder children or anything..
1122 setZOrder(zorder);
1124 // TODO: no idea if this is right, it tries to re-enable gravity when dropping C2 agents
1125 if (engine.version == 2) grav.setInt(1);
1126 if (engine.version == 3) falling = true;
1128 if (!wasinvehicle) { // ie, we're not being dropped by a vehicle
1129 // TODO: check for vehicles in a saner manner?
1130 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
1131 boost::shared_ptr<Agent> a = (*i);
1132 if (!a) continue;
1133 Vehicle *v = dynamic_cast<Vehicle *>(a.get());
1134 if (!v) continue;
1136 if (agentsTouching(this, v)) {
1137 v->addCarried(this);
1138 // TODO: how to handle not-invehicle case, where vehicle has failed to pick us up?
1139 if (invehicle) return true;
1144 if (engine.version > 1) {
1145 // TODO: maybe think about this some more - does this belong here?
1146 tryMoveToPlaceAround(x, y);
1149 // TODO: return value is not used anywhere yet?
1150 return true;
1153 void Agent::dropCarried(AgentRef a) {
1154 drop(a);
1156 // TODO: this doesn't reorder children or anything..
1157 a->setZOrder(a->zorder);
1159 // fire 'Lost Carried Agent'
1160 if (engine.version >= 3)
1161 queueScript(125, a); // TODO: is this the correct param?
1164 void Agent::drop(AgentRef a) {
1165 if (!carrying) return;
1166 assert(carrying == a);
1168 a->beDropped();
1169 carrying = AgentRef(0);
1172 std::pair<int, int> Agent::getCarryPoint() {
1173 unsigned int ourpose = 0;
1175 SpritePart *s;
1176 if ((s = dynamic_cast<SpritePart *>(part(0))))
1177 ourpose = s->getBase() + s->getPose();
1179 std::pair<int, int> pos(0, 0);
1181 std::map<unsigned int, std::pair<int, int> >::iterator i = carry_points.find(ourpose);
1182 if (i != carry_points.end())
1183 pos = i->second;
1185 return pos;
1188 std::pair<int, int> Agent::getCarriedPoint() {
1189 unsigned int theirpose = 0;
1191 SpritePart *s;
1192 if ((s = dynamic_cast<SpritePart *>(part(0))))
1193 theirpose = s->getBase() + s->getPose();
1195 std::pair<int, int> pos(0, 0);
1197 std::map<unsigned int, std::pair<int, int> >::iterator i = carried_points.find(theirpose);
1198 if (i != carried_points.end()) {
1199 pos = i->second;
1200 } else if (s && engine.version > 1) {
1201 // TODO: why fudge pickup location here and not in default carried points or something?
1202 // (this is nornagon's code which i moved - fuzzie)
1203 pos.first = s->getSprite()->width(s->getCurrentSprite()) / 2;
1206 return pos;
1209 void Agent::adjustCarried(float unusedxoffset, float unusedyoffset) {
1210 // Adjust the position of the agent we're carrying.
1211 // TODO: this doesn't actually position the carried agent correctly, sigh
1213 if (!carrying) return;
1215 unsigned int ourpose = 0;
1217 int xoffset = 0, yoffset = 0;
1218 if (engine.version < 3 && world.hand() == this) {
1219 // this appears to produce correct behaviour in the respective games, don't ask me -nornagon
1220 if (engine.version == 2) {
1221 xoffset = world.hand()->getWidth() / 2;
1222 yoffset = world.hand()->getHeight() / 2 - 2;
1223 } else
1224 yoffset = world.hand()->getHeight() / 2 - 3;
1227 std::pair<int, int> carrypoint = getCarryPoint();
1228 xoffset += carrypoint.first;
1229 yoffset += carrypoint.second;
1231 std::pair<int, int> carriedpoint = carrying->getCarriedPoint();
1232 xoffset -= carriedpoint.first;
1233 yoffset -= carriedpoint.second;
1235 if (xoffset != 0 || yoffset != 0)
1236 carrying->moveTo(x + xoffset, y + yoffset, true);
1239 void Agent::setClassifier(unsigned char f, unsigned char g, unsigned short s) {
1240 family = f;
1241 genus = g;
1242 species = s;
1244 if (engine.version < 3) {
1245 // TODO: categories for other game versions
1246 category = -1;
1247 } else {
1248 category = world.findCategory(family, genus, species);
1252 bool Agent::tryMoveToPlaceAround(float x, float y) {
1253 if (!suffercollisions()) {
1254 moveTo(x, y);
1255 return true;
1258 // second hacky attempt, move from side to side (+/- width) and up (- height) a little
1259 unsigned int trywidth = getWidth() * 2; if (trywidth < 100) trywidth = 100;
1260 unsigned int tryheight = getHeight() * 2; if (tryheight < 100) tryheight = 100;
1261 for (unsigned int xadjust = 0; xadjust < trywidth; xadjust++) {
1262 for (unsigned int yadjust = 0; yadjust < tryheight; yadjust++) {
1263 if (validInRoomSystem(Point(x - xadjust, y - yadjust), getWidth(), getHeight(), perm))
1264 moveTo(x - xadjust, y - yadjust);
1265 else if ((xadjust != 0) && validInRoomSystem(Point(x + xadjust, y - yadjust), getWidth(), getHeight(), perm))
1266 moveTo(x + xadjust, y - yadjust);
1267 else
1268 continue;
1269 return true;
1273 return false;
1276 void Agent::join(unsigned int outid, AgentRef dest, unsigned int inid) {
1277 assert(dest);
1278 assert(outports.find(outid) != outports.end());
1279 assert(dest->inports.find(inid) != dest->inports.end());
1280 outports[outid]->dests.push_back(std::pair<AgentRef, unsigned int>(dest, inid));
1281 dest->inports[inid]->source = this;
1282 dest->inports[inid]->sourceid = outid;
1285 /* vim: set noet: */