add a hack to deal with ACTV on compound agents in c1/c2
[openc2e.git] / Agent.cpp
blob8061f7e04263710308c6b7ef854906599490e5e7
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 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 if (vm->fireScript(s, event == 9, from)) {
280 lastScript = event;
281 zotstack();
282 vm->setVariables(one, two);
284 // TODO: we should set _it_ in a more sensible way
285 CreatureAgent *a = dynamic_cast<CreatureAgent *>(this);
286 if (a) {
287 Creature *c = a->getCreature();
288 assert(c);
289 vm->_it_ = c->getAttentionFocus();
292 vmTick();
293 ranscript = true;
294 } else if (madevm) {
295 world.freeVM(vm);
296 vm = 0;
300 switch (event) {
301 case 5:
302 from->dropCarried(this); // TODO: correct?
303 break;
306 return ranscript;
309 bool Agent::queueScript(unsigned short event, AgentRef from, caosVar p0, caosVar p1) {
310 // Queue a script for execution on the VM of this agent.
312 if (dying) return false;
314 // only bother firing the event if either it exists, or it's one with engine code attached
315 // TODO: why don't we do the engine checks/etc here?
316 switch (event) {
317 default:
318 if (!findScript(event)) return false;
320 case 0:
321 case 1:
322 case 2:
323 case 3:
324 case 4:
325 case 5:
326 case 12:
327 case 13:
328 case 14:
329 case 92:
330 world.queueScript(event, this, from, p0, p1);
333 return true;
336 int Agent::handleClick(float clickx, float clicky) {
337 // Handle a mouse click.
338 if (!activateable()) return -1;
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 return calculateScriptId(action);
352 return -1;
355 // new-style click handling (c2e)
356 if (clik != -1) {
357 return -1; // TODO: handle CLIK
358 } else if (clac[0] != -1) {
359 return calculateScriptId(clac[0]);
362 return -1;
365 void Agent::playAudio(std::string filename, bool controlled, bool loop) {
366 assert(!dying);
368 sound.reset();
369 world.playAudio(filename, this, controlled, loop);
372 bool agentOnCamera(Agent *targ, bool checkall = false); // caosVM_camera.cpp
374 void Agent::updateAudio(boost::shared_ptr<AudioSource> s) {
375 assert(s);
377 s->setPos(x + getWidth() / 2, y + getHeight() / 2, zorder);
378 // TODO: we could do with a nicer 'inrange' check
379 bool inrange = (x + 500 > world.camera.getX()) && (x + getWidth() - 500 < world.camera.getX() + world.camera.getWidth()) &&
380 (y + 500 > world.camera.getY()) && (y + getHeight() - 500 < world.camera.getY() + world.camera.getHeight());
381 s->setMute(
382 world.camera.getMetaRoom() != world.map.metaRoomAt(x, y)
384 !inrange
386 // TODO: setVelocity?
389 Point const Agent::boundingBoxPoint(unsigned int n) {
390 return boundingBoxPoint(n, Point(x, y), getWidth(), getHeight());
393 Point const Agent::boundingBoxPoint(unsigned int n, Point in, unsigned int w, unsigned int h) {
394 Point p;
396 switch (n) {
397 case 0: // left
398 p.x = in.x;
399 p.y = in.y + (h / 2.0f);
400 break;
402 case 1: // right
403 p.x = in.x + w;
404 p.y = in.y + (h / 2.0f);
405 break;
407 case 2: // top
408 p.x = in.x + (w / 2.0f);
409 p.y = in.y;
410 break;
412 case 3: // bottom
413 p.x = in.x + (w / 2.0f);
414 p.y = in.y + h;
415 break;
417 default:
418 throw creaturesException("Agent::boundingBoxPoint got unknown direction");
421 return p;
424 bool Agent::validInRoomSystem() {
425 // Return true if this agent is inside the world room system at present, or false if it isn't.
427 // TODO: c1
428 if (engine.version == 1) return true;
430 return validInRoomSystem(Point(x, y), getWidth(), getHeight(), perm);
433 bool const Agent::validInRoomSystem(Point p, unsigned int w, unsigned int h, int testperm) {
434 // Return true if this agent is inside the world room system at the specified point, or false if it isn't.
436 for (unsigned int i = 0; i < 4; i++) {
437 Point src, dest;
438 switch (i) {
439 case 0: src = boundingBoxPoint(0, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // left to bottom
440 case 1: src = boundingBoxPoint(1, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // right to bottom
441 case 2: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(0, p, w, h); break; // top to left
442 case 3: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(1, p, w, h); break; // top to right
445 if (engine.version == 2) {
446 // Creatures 2 physics
448 int dx = dest.x - src.x;
449 int dy = dest.y - src.y;
451 Point deltapt(0,0);
452 double delta = 1000000000;
453 bool collided = false;
455 // TODO: check suffercollisions?
457 // TODO: muh, provided direction here is kinda a hack
458 findCollisionInDirection((i < 2) ? 3 : 2, src, dx, dy, deltapt, delta, collided, true);
459 if (collided) return false;
460 } else {
461 // Creatures 3 physics
463 float srcx = src.x, srcy = src.y;
465 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
466 if (!ourRoom) return false;
468 unsigned int dir; Line wall;
469 world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, wall, dir, testperm);
471 if (src != dest) return false;
475 return true;
478 void Agent::physicsTick() {
479 if (engine.version == 1) return; // C1 has no physics, and different attributes.
481 if (carriedby) return; // We don't move when carried, so what's the point?
483 if (engine.version == 2) {
484 // Creatures 2 physics is different.
485 physicsTickC2();
486 return;
489 if (!wasmoved) return; // some agents are created outside INST and get autokilled if we try physics on them before they move
491 // set destination point based on velocities
492 float destx = x + velx.getFloat();
493 float desty = y + vely.getFloat();
495 if (sufferphysics()) {
496 // TODO: falling behaviour needs looking at more closely..
497 // .. but it shouldn't be 'false' by default on non-physics agents, so..
498 falling = false;
499 // increase speed according to accg
500 // TODO: should we be changing vely first, instead of after a successful move (below)?
501 desty += accg.getFloat();
504 if (suffercollisions()) {
505 float lastdistance = 1000000.0f;
506 bool collided = false;
507 Line wall; // only valid when collided
508 unsigned int collidedirection = 0; // only valid when collided
509 Point bestmove;
511 // iterate through all four points of the bounding box
512 for (unsigned int i = 0; i < 4; i++) {
513 // this mess is because we want to start with the bottom point - DOWN (3) - before the others, for efficiency
514 Point src = boundingBoxPoint((i == 0) ? 3 : i - 1);
516 // store values
517 float srcx = src.x, srcy = src.y;
519 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
520 if (!ourRoom) {
521 ourRoom = world.map.roomAt(srcx, srcy);
523 if (!ourRoom) {
524 if (!displaycore) { // TODO: ugh, displaycore is a horrible thing to use for this
525 // we're out of the room system, physics bug, but let's try MVSFing back in to cover for fuzzie's poor programming skills
526 static bool tryingmove; tryingmove = false; // avoid infinite loop
527 if (!tryingmove && tryMoveToPlaceAround(x, y)) {
528 //std::cout << identify() << " was out of room system due to a physics bug but we hopefully moved it back in.." << std::endl;
529 tryingmove = true;
530 physicsTick();
531 return;
534 // didn't work!
535 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % srcx % srcy), false);
537 displaycore = true;
538 return; // out of room system
541 Point dest(destx + (srcx - x), desty + (srcy - y));
542 unsigned int local_collidedirection;
543 Line local_wall;
545 // this changes src to the point at which we end up
546 bool local_collided = world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, local_wall, local_collidedirection, perm);
548 float dist;
549 if (src.x == srcx && src.y == srcy)
550 dist = 0.0f;
551 else {
552 float xdiff = src.x - srcx;
553 float ydiff = src.y - srcy;
554 dist = xdiff*xdiff + ydiff*ydiff;
557 if (dist >= lastdistance) {
558 assert(i != 0); // this had better not be our first collision!
559 continue; // further away than a previous collision
562 lastdistance = dist;
563 bestmove.x = x + (src.x - srcx);
564 bestmove.y = y + (src.y - srcy);
565 collidedirection = local_collidedirection;
566 wall = local_wall;
567 collided = local_collided;
569 if (dist == 0.0f)
570 break; // no point checking any more, is there?
573 // *** do actual movement
574 if (lastdistance != 0.0f) {
575 moveTo(bestmove.x, bestmove.y);
577 if (collided) {
578 lastcollidedirection = collidedirection;
579 queueScript(6, 0, velx, vely); // TODO: include this? .. we need to include SOMETHING, c3 ball checks for <3
581 if (elas != 0) {
582 if (wall.getType() == HORIZONTAL) {
583 vely.setFloat(-vely.getFloat());
584 } else if (wall.getType() == VERTICAL) {
585 velx.setFloat(-velx.getFloat());
586 } else {
587 // line starts always have a lower x value than the end
588 float xdiff = wall.getEnd().x - wall.getStart().x;
589 float ydiff = wall.getEnd().y - wall.getStart().y;
590 float fvelx = velx.getFloat(), fvely = vely.getFloat();
592 // calculate input/slope angles
593 double inputangle;
594 if (fvelx == 0.0f) {
595 if (fvely > 0.0f)
596 inputangle = M_PI / 2.0;
597 else
598 inputangle = 3 * (M_PI / 2.0);
599 } else {
600 inputangle = atan(fvely / fvelx);
602 double slopeangle = atan(-ydiff / xdiff); // xdiff != 0 because wall isn't vertical
604 // calculate output angle
605 double outputangle = slopeangle + (slopeangle - inputangle) + M_PI;
607 // turn back into component velocities
608 double vectorlength = sqrt(fvelx*fvelx + fvely*fvely);
609 float xoutput = cos(outputangle) * vectorlength;
610 float youtput = sin(outputangle) * vectorlength;
612 velx.setFloat(xoutput);
613 vely.setFloat(-youtput);
616 if (elas != 100.0f) {
617 velx.setFloat(velx.getFloat() * (elas / 100.0f));
618 vely.setFloat(vely.getFloat() * (elas / 100.0f));
620 } else
621 vely.setFloat(0);
622 } else if (sufferphysics() && accg != 0) {
623 falling = true; // TODO: icky
624 vely.setFloat(vely.getFloat() + accg.getFloat());
626 } else { velx.setFloat(0); vely.setFloat(0); } // TODO: correct?
627 } else {
628 if (vely.hasDecimal() || velx.hasDecimal())
629 moveTo(destx, desty);
630 if (sufferphysics())
631 vely.setFloat(vely.getFloat() + accg.getFloat());
634 if (sufferphysics() && (aero != 0)) {
635 // reduce speed according to AERO
636 // TODO: aero should be an integer!
637 velx.setFloat(velx.getFloat() - (velx.getFloat() * (aero.getFloat() / 100.0f)));
638 vely.setFloat(vely.getFloat() - (vely.getFloat() * (aero.getFloat() / 100.0f)));
642 shared_ptr<Room> const Agent::bestRoomAt(unsigned int tryx, unsigned int tryy, unsigned int direction, shared_ptr<Room> exclude) {
643 std::vector<shared_ptr<Room> > rooms = world.map.roomsAt(tryx, tryy);
645 shared_ptr<Room> r;
647 if (rooms.size() == 0) return shared_ptr<Room>();
648 if (rooms.size() == 1) r = rooms[0];
649 else if (rooms.size() > 1) {
650 unsigned int j;
651 for (j = 0; j < rooms.size(); j++) {
652 if (rooms[j] == exclude) continue;
654 if (direction == 0) { // left
655 if (rooms[j]->x_left == tryx) break;
656 } else if (direction == 1) { // right
657 if (rooms[j]->x_right == tryx) break;
658 } else if (direction == 2) { // top
659 if (rooms[j]->y_left_ceiling == tryy) break;
660 } else { // down
661 if (rooms[j]->y_left_floor == tryy) break;
664 if (j == rooms.size()) j = 0;
665 r = rooms[j];
668 if (r == exclude) return shared_ptr<Room>();
669 else return r;
672 // Creatures 2 collision finding
673 void const Agent::findCollisionInDirection(unsigned int i, Point src, int &dx, int &dy, Point &deltapt, double &delta, bool &collided, bool followrooms) {
674 src.x = (int)src.x;
675 src.y = (int)src.y;
677 shared_ptr<Room> room = bestRoomAt(src.x, src.y, i, shared_ptr<Room>());
679 if (!room) { // out of room system
680 if (!displaycore)
681 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % src.x % src.y), false);
682 grav.setInt(0);
683 displaycore = true;
684 return;
687 int lastdirection;
689 bool steep = abs(dy) > abs(dx);
691 int signdx = dx < 0 ? -1 : 1;
692 int signdy = dy < 0 ? -1 : 1;
694 Line l(Point(0,0),Point(dx,dy));
696 Point lastpoint(0,0);
698 for (int loc = 0; loc <= abs(steep ? dy : dx); loc++) {
699 Point p = steep ? l.pointAtY(loc*signdy) : l.pointAtX(loc*signdx);
700 p.x = (int)p.x;
701 p.y = (int)p.y;
703 bool trycollisions = false;
705 bool endofroom = false;
707 if (src.x + p.x < room->x_left) {
708 if (i != 1 && dx < 0) { trycollisions = true; lastdirection = 0; }
709 endofroom = true;
711 if (src.x + p.x > room->x_right) {
712 if (i != 0 && dx > 0) { trycollisions = true; lastdirection = 1; }
713 endofroom = true;
715 if (src.y + p.y < room->y_left_ceiling) {
716 if (i != 3 && dy < 0) { trycollisions = true; lastdirection = 2; }
717 endofroom = true;
719 if (src.y + p.y > room->y_left_floor) {
720 if (i != 2 && dy > 0) { trycollisions = true; lastdirection = 3; }
721 endofroom = true;
724 // find the next room, if necessary, and work out whether we should move into it or break out
725 if (endofroom) {
726 shared_ptr<Room> newroom = bestRoomAt(src.x + p.x, src.y + p.y, i, room);
728 bool collision = false;
730 // collide if we're out of the room system
731 if (!newroom) { collision = true; }
732 // collide if there's no new room connected to this one
733 else if (room->doors.find(newroom) == room->doors.end()) { collision = true; }
734 // collide if the PERM between this room and the new room is smaller than or equal to our size
735 else if (size.getInt() > room->doors[newroom]->perm) { collision = true; }
737 if (collision && (trycollisions || (!newroom))) {
738 collided = true;
739 break;
742 // move to the new room and keep going!
743 room = newroom;
746 if (room->floorpoints.size() && i == 3 && dy >= 0 && size.getInt() > room->floorvalue.getInt()) { // TODO: Hack!
747 // TODO: we don't check floorYatX isn't returning a 'real' room floor, but floorpoints should cover the whole floor anyway
748 int floory = room->floorYatX(src.x + p.x);
750 // never collide when the top point of an object is below the floor.
751 Point top = boundingBoxPoint(2);
753 // TODO: consider steep floors
754 if (src.y + p.y > floory && top.y < floory) {
755 collided = true;
756 lastdirection = 3;
757 break;
761 if ((!followrooms) && endofroom) break;
763 lastpoint = p;
766 double length2 = (lastpoint.x * lastpoint.x) + (lastpoint.y * lastpoint.y);
767 if (length2 < delta) {
768 // TODO: !followrooms is a horrible way to detect a non-physics call
769 if (collided && followrooms) lastcollidedirection = lastdirection;
770 deltapt = lastpoint;
771 delta = length2;
775 void Agent::physicsTickC2() {
776 int dx = velx.getInt(), dy = vely.getInt();
778 if (dx != 0 || dy != 0) grav.setInt(1);
780 if (grav.getInt() != 0 && sufferphysics()) {
781 dy += accg.getInt();
784 grav.setInt(1);
786 if (dy == 0 && dx == 0) { // nothing to do
787 if (vely.getInt() == 0) {
788 // really no motion
789 grav.setInt(0);
790 } else {
791 // y motion cancelled by gravity
792 vely.setInt(dy);
794 return;
796 vely.setInt(dy);
798 Point deltapt(0,0);
799 double delta = 1000000000;
801 bool collided = false;
803 if (suffercollisions()) {
804 for (unsigned int i = 0; i < 4; i++) {
805 Point src = boundingBoxPoint(i);
806 findCollisionInDirection(i, src, dx, dy, deltapt, delta, collided, true);
808 } else {
809 deltapt.x = dx;
810 deltapt.y = dy;
813 if (collided && (velx.getInt() != 0 || vely.getInt() != 0)) {
814 if (lastcollidedirection >= 2) // up and down
815 vely.setInt(-(vely.getInt() - (rest.getInt() * vely.getInt()) / 100));
816 else
817 velx.setInt(-(velx.getInt() - (rest.getInt() * velx.getInt()) / 100));
818 queueScript(6, 0);
820 if ((int)deltapt.x == 0 && (int)deltapt.y == 0) {
821 grav.setInt(0);
822 velx.setInt(0);
823 vely.setInt(0);
824 } else {
825 moveTo(x + (int)deltapt.x, y + (int)deltapt.y);
826 if (sufferphysics()) {
827 int fricx = (aero.getInt() * velx.getInt()) / 100;
828 int fricy = (aero.getInt() * vely.getInt()) / 100;
829 if (abs(velx.getInt()) > 0 && fricx == 0) fricx = (velx.getInt() < 0) ? -1 : 1;
830 if (abs(vely.getInt()) > 0 && fricy == 0) fricy = (vely.getInt() < 0) ? -1 : 1;
831 velx.setInt(velx.getInt() - fricx);
832 vely.setInt(vely.getInt() - fricy);
837 void Agent::tick() {
838 // sanity checks to stop ticks on dead agents
839 LifeAssert la(this);
840 if (dying) return;
842 if (sound) {
843 // if the sound is no longer playing...
844 if (sound->getState() != SS_PLAY) {
845 // ...release our reference to it
846 sound.reset();
847 } else {
848 // otherwise, reposition audio
849 updateAudio(sound);
853 // don't tick paused agents
854 if (paused) return;
856 // CA updates
857 if (emitca_index != -1 && emitca_amount != 0.0f) {
858 assert(0 <= emitca_index && emitca_index <= 19);
859 shared_ptr<Room> r = world.map.roomAt(x, y);
860 if (r) {
861 r->catemp[emitca_index] += emitca_amount;
862 /*if (r->catemp[emitca_index] <= 0.0f) r->catemp[emitca_index] = 0.0f;
863 else if (r->catemp[emitca_index] >= 1.0f) r->catemp[emitca_index] = 1.0f;*/
867 // tick the physics engine
868 physicsTick();
869 if (dying) return; // in case we were autokilled
871 // update the timer if needed, and then queue a timer event if necessary
872 if (timerrate) {
873 tickssincelasttimer++;
874 if (tickssincelasttimer == timerrate) {
875 queueScript(9); // TODO: include this?
876 tickssincelasttimer = 0;
878 assert(timerrate > tickssincelasttimer);
881 // tick the agent VM
882 if (vm) vmTick();
885 void Agent::unhandledException(std::string info, bool wasscript) {
886 // TODO: do something with this? empty the stack?
887 if (world.autostop) {
888 // autostop is mostly for Creatures Village, which is full of buggy scripts
889 stopScript();
890 } else if (world.autokill && !dynamic_cast<CreatureAgent *>(this)) { // don't autokill creatures! TODO: someday we probably should :)
891 kill();
892 if (wasscript)
893 std::cerr << identify() << " was autokilled during script " << lastScript << " due to: " << info << std::endl;
894 else
895 std::cerr << identify() << " was autokilled due to: " << info << std::endl;
896 } else {
897 stopScript();
898 if (wasscript)
899 std::cerr << identify() << " caused an exception during script " << lastScript << ": " << info << std::endl;
900 else
901 std::cerr << identify() << " caused an exception: " << info << std::endl;
905 void Agent::vmTick() {
906 // Tick the VM associated with this agent.
908 assert(vm); // There must *be* a VM to tick.
909 LifeAssert la(this); // sanity check
911 // If we're out of timeslice, give ourselves some more (depending on the game type).
912 if (!vm->timeslice) {
913 vm->timeslice = (engine.version < 3) ? 1 : 5;
916 // Keep trying to run VMs while we don't run out of timeslice, end up with a blocked VM, or a VM stops.
917 while (vm && vm->timeslice && !vm->isBlocking() && !vm->stopped()) {
918 assert(vm->timeslice > 0);
920 // Tell the VM to tick (using all available timeslice), catching exceptions as necessary.
921 try {
922 vm->tick();
923 } catch (invalidAgentException &e) {
924 // try letting the exception script handle it
925 if (!queueScript(255))
926 unhandledException(e.prettyPrint(), true);
927 else
928 stopScript(); // we still want current script to die
929 } catch (creaturesException &e) {
930 unhandledException(e.prettyPrint(), true);
931 } catch (std::exception &e) {
932 unhandledException(e.what(), true);
935 // If the VM stopped, it's done.
936 if (vm && vm->stopped()) {
937 world.freeVM(vm);
938 vm = NULL;
942 // Zot any remaining timeslice, since we're done now.
943 if (vm) vm->timeslice = 0;
945 // If there's no current VM but there's one on the call stack, a previous VM must have finished,
946 // pop one from the call stack to run next time.
947 if (!vm && !vmstack.empty()) {
948 vm = vmstack.front();
949 vmstack.pop_front();
953 Agent::~Agent() {
954 assert(lifecount == 0);
956 if (!initialized) return;
957 if (!dying) {
958 // we can't do kill() here because we can't do anything which might try using our shared_ptr
959 // (since this could be during world destruction)
961 if (vm) {
962 vm->stop();
963 world.freeVM(vm);
964 vm = 0;
967 zotstack();
969 if (sound) {
970 sound->stop();
971 sound.reset();
976 void Agent::kill() {
977 assert(!dying);
978 if (floatable()) floatRelease();
979 if (carrying) dropCarried(carrying);
980 // TODO: should the carried agent really be responsible for dropping from vehicle?
981 if (invehicle) invehicle->drop(this);
983 dying = true; // what a world, what a world...
985 if (vm) {
986 vm->stop();
987 world.freeVM(vm);
988 vm = 0;
991 zotstack();
992 agents_iter->reset();
994 if (sound) {
995 sound->stop();
996 sound.reset();
1000 unsigned int Agent::getZOrder() const {
1001 if (invehicle) {
1002 // TODO: take notice of cabp in c2e, at least. also, stacking .. ?
1003 Vehicle *v = dynamic_cast<Vehicle *>(invehicle.get());
1004 assert(v);
1005 if (engine.version < 3)
1006 return v->cabinplane; // TODO: correct?
1007 else
1008 return v->getZOrder() + v->cabinplane;
1009 // TODO: Vehicle should probably rearrange zorder of passengers if ever moved
1010 } else if (carriedby) {
1011 // TODO: check for overflow
1012 // TODO: is adding our own zorder here correct behaviour? someone should check
1013 if (engine.version > 1)
1014 return carriedby->getZOrder() - 100;
1015 else
1016 return carriedby->getZOrder();
1017 } else {
1018 return zorder;
1022 void Agent::setZOrder(unsigned int z) {
1023 if (dying) return;
1024 zorder = z;
1027 int Agent::getUNID() const {
1028 if (unid != -1)
1029 return unid;
1030 return unid = world.newUNID(const_cast<Agent *>(this));
1033 #include "Catalogue.h"
1035 std::string Agent::identify() const {
1036 std::ostringstream o;
1037 o << (int)family << " " << (int)genus << " " << species;
1038 const std::string n = catalogue.getAgentName(family, genus, species);
1039 if (n.size())
1040 o << " (" + n + ")";
1041 /*if (unid != -1)
1042 o << " unid " << unid;
1043 else
1044 o << " (no unid assigned)"; */
1045 return o.str();
1048 bool agentzorder::operator ()(const Agent *s1, const Agent *s2) const {
1049 return s1->getZOrder() < s2->getZOrder();
1052 void Agent::pushVM(caosVM *newvm) {
1053 assert(newvm);
1054 if (vm)
1055 vmstack.push_front(vm);
1056 vm = newvm;
1059 bool Agent::vmStopped() {
1060 return (!vm || vm->stopped());
1063 void Agent::stopScript() {
1064 zotstack();
1065 if (vm)
1066 vm->stop();
1069 void Agent::addCarried(AgentRef a) {
1070 assert(a);
1072 // TODO: muh, vehicle drop needs more thought
1073 if (a->invehicle) {
1074 Vehicle *v = dynamic_cast<Vehicle *>(a->invehicle.get());
1075 assert(v);
1076 v->dropCarried(a);
1079 carry(a);
1081 // TODO: this doesn't reorder children or anything..
1082 a->setZOrder(a->zorder);
1084 // fire 'Got Carried Agent'
1085 if (engine.version >= 3)
1086 queueScript(124, a); // TODO: is this the correct param?
1089 void Agent::carry(AgentRef a) {
1090 assert(a);
1092 // TODO: check for infinite loops (eg, us carrying an agent which is carrying us) :)
1094 if (carrying)
1095 dropCarried(carrying);
1097 carrying = a;
1099 a->carriedby = AgentRef(this);
1100 // TODO: move carrying agent to the right position
1101 adjustCarried(0, 0);
1104 bool Agent::beDropped() {
1105 carriedby = AgentRef(0);
1106 bool wasinvehicle = invehicle;
1107 invehicle = AgentRef(0);
1109 // TODO: this doesn't reorder children or anything..
1110 setZOrder(zorder);
1112 // TODO: no idea if this is right, it tries to re-enable gravity when dropping C2 agents
1113 if (engine.version == 2) grav.setInt(1);
1114 if (engine.version == 3) falling = true;
1116 if (!wasinvehicle) { // ie, we're not being dropped by a vehicle
1117 // TODO: check for vehicles in a saner manner?
1118 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
1119 boost::shared_ptr<Agent> a = (*i);
1120 if (!a) continue;
1121 Vehicle *v = dynamic_cast<Vehicle *>(a.get());
1122 if (!v) continue;
1124 if (agentsTouching(this, v)) {
1125 v->addCarried(this);
1126 // TODO: how to handle not-invehicle case, where vehicle has failed to pick us up?
1127 if (invehicle) return true;
1132 if (engine.version == 1) {
1133 MetaRoom* m = world.map.metaRoomAt(x, y);
1134 if (!m) return false;
1135 shared_ptr<Room> r = m->nextFloorFromPoint(x, y);
1136 if (!r) return false;
1137 moveTo(x, r->bot.pointAtX(x).y - getHeight());
1138 } else {
1139 // TODO: maybe think about this some more
1140 tryMoveToPlaceAround(x, y);
1143 // TODO: return value is not used anywhere yet?
1144 return true;
1147 void Agent::dropCarried(AgentRef a) {
1148 drop(a);
1150 // TODO: this doesn't reorder children or anything..
1151 a->setZOrder(a->zorder);
1153 // fire 'Lost Carried Agent'
1154 if (engine.version >= 3)
1155 queueScript(125, a); // TODO: is this the correct param?
1158 void Agent::drop(AgentRef a) {
1159 if (!carrying) return;
1160 assert(carrying == a);
1162 a->beDropped();
1163 carrying = AgentRef(0);
1166 std::pair<int, int> Agent::getCarryPoint() {
1167 unsigned int ourpose = 0;
1169 SpritePart *s;
1170 if ((s = dynamic_cast<SpritePart *>(part(0))))
1171 ourpose = s->getBase() + s->getPose();
1173 std::pair<int, int> pos(0, 0);
1175 std::map<unsigned int, std::pair<int, int> >::iterator i = carry_points.find(ourpose);
1176 if (i != carry_points.end())
1177 pos = i->second;
1179 return pos;
1182 std::pair<int, int> Agent::getCarriedPoint() {
1183 unsigned int theirpose = 0;
1185 SpritePart *s;
1186 if ((s = dynamic_cast<SpritePart *>(part(0))))
1187 theirpose = s->getBase() + s->getPose();
1189 std::pair<int, int> pos(0, 0);
1191 std::map<unsigned int, std::pair<int, int> >::iterator i = carried_points.find(theirpose);
1192 if (i != carried_points.end()) {
1193 pos = i->second;
1194 } else if (s && engine.version > 1) {
1195 // TODO: why fudge pickup location here and not in default carried points or something?
1196 // (this is nornagon's code which i moved - fuzzie)
1197 pos.first = s->getSprite()->width(s->getCurrentSprite()) / 2;
1200 return pos;
1203 void Agent::adjustCarried(float unusedxoffset, float unusedyoffset) {
1204 // Adjust the position of the agent we're carrying.
1205 // TODO: this doesn't actually position the carried agent correctly, sigh
1207 if (!carrying) return;
1209 unsigned int ourpose = 0;
1211 int xoffset = 0, yoffset = 0;
1212 if (engine.version < 3 && world.hand() == this) {
1213 // this appears to produce correct behaviour in the respective games, don't ask me -nornagon
1214 if (engine.version == 2) {
1215 xoffset = world.hand()->getWidth() / 2;
1216 yoffset = world.hand()->getHeight() / 2 - 2;
1217 } else
1218 yoffset = world.hand()->getHeight() / 2 - 3;
1221 std::pair<int, int> carrypoint = getCarryPoint();
1222 xoffset += carrypoint.first;
1223 yoffset += carrypoint.second;
1225 std::pair<int, int> carriedpoint = carrying->getCarriedPoint();
1226 xoffset -= carriedpoint.first;
1227 yoffset -= carriedpoint.second;
1229 if (xoffset != 0 || yoffset != 0)
1230 carrying->moveTo(x + xoffset, y + yoffset, true);
1233 void Agent::setClassifier(unsigned char f, unsigned char g, unsigned short s) {
1234 family = f;
1235 genus = g;
1236 species = s;
1238 if (engine.version < 3) {
1239 // TODO: categories for other game versions
1240 category = -1;
1241 } else {
1242 category = world.findCategory(family, genus, species);
1246 bool Agent::tryMoveToPlaceAround(float x, float y) {
1247 if (!suffercollisions()) {
1248 moveTo(x, y);
1249 return true;
1252 // second hacky attempt, move from side to side (+/- width) and up (- height) a little
1253 unsigned int trywidth = getWidth() * 2; if (trywidth < 100) trywidth = 100;
1254 unsigned int tryheight = getHeight() * 2; if (tryheight < 100) tryheight = 100;
1255 for (unsigned int xadjust = 0; xadjust < trywidth; xadjust++) {
1256 for (unsigned int yadjust = 0; yadjust < tryheight; yadjust++) {
1257 if (validInRoomSystem(Point(x - xadjust, y - yadjust), getWidth(), getHeight(), perm))
1258 moveTo(x - xadjust, y - yadjust);
1259 else if ((xadjust != 0) && validInRoomSystem(Point(x + xadjust, y - yadjust), getWidth(), getHeight(), perm))
1260 moveTo(x + xadjust, y - yadjust);
1261 else
1262 continue;
1263 return true;
1267 return false;
1270 void Agent::join(unsigned int outid, AgentRef dest, unsigned int inid) {
1271 assert(dest);
1272 assert(outports.find(outid) != outports.end());
1273 assert(dest->inports.find(inid) != dest->inports.end());
1274 outports[outid]->dests.push_back(std::pair<AgentRef, unsigned int>(dest, inid));
1275 dest->inports[inid]->source = this;
1276 dest->inports[inid]->sourceid = outid;
1279 /* vim: set noet: */