allow any CompoundPart to gain focus, if it canGainFocus()
[openc2e.git] / Agent.cpp
blobd4ea8cdea4ee984e6d4b86cd762a0e19ce4d3a08
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()->pointerX(), world.hand()->pointerY());
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()->pointerX() - p->x - p->getParent()->x, world.hand()->pointerY() - 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 static bool inrange_at(const MetaRoom *room, float x, float y, unsigned int width, unsigned int height) {
387 const static unsigned int buffer = 500;
389 if (world.camera.getMetaRoom() != room)
390 return false;
391 if (x + buffer < world.camera.getX() || x + width - buffer > world.camera.getX() + world.camera.getWidth())
392 return false;
393 if (y + buffer < world.camera.getY() || y + height - buffer > world.camera.getY() + world.camera.getHeight())
394 return false;
395 return true;
398 void Agent::updateAudio(boost::shared_ptr<AudioSource> s) {
399 assert(s);
400 MetaRoom *room = world.map.metaRoomAt(x, y);
401 float xc = x;
403 bool inrange = false;
404 if (inrange_at(room, x, y, getWidth(), getHeight())) {
405 xc = x;
406 inrange = true;
407 } else if (room->wraparound()) {
408 if (inrange_at(room, x - room->width(), y, getWidth(), getHeight())) {
409 xc = x - room->width();
410 inrange = true;
411 } else if (inrange_at(room, x + room->width(), y, getWidth(), getHeight())) {
412 xc = x + room->width();
413 inrange = true;
416 s->setMute(!inrange);
417 if (inrange)
418 s->setPos(xc + getWidth() / 2, y + getHeight() / 2, zorder);
419 // TODO: setVelocity?
422 Point const Agent::boundingBoxPoint(unsigned int n) {
423 return boundingBoxPoint(n, Point(x, y), getWidth(), getHeight());
426 Point const Agent::boundingBoxPoint(unsigned int n, Point in, unsigned int w, unsigned int h) {
427 Point p;
429 switch (n) {
430 case 0: // left
431 p.x = in.x;
432 p.y = in.y + (h / 2.0f);
433 break;
435 case 1: // right
436 p.x = in.x + w;
437 p.y = in.y + (h / 2.0f);
438 break;
440 case 2: // top
441 p.x = in.x + (w / 2.0f);
442 p.y = in.y;
443 break;
445 case 3: // bottom
446 p.x = in.x + (w / 2.0f);
447 p.y = in.y + h;
448 break;
450 default:
451 throw creaturesException("Agent::boundingBoxPoint got unknown direction");
454 return p;
457 bool Agent::validInRoomSystem() {
458 // Return true if this agent is inside the world room system at present, or false if it isn't.
460 // TODO: c1
461 if (engine.version == 1) return true;
463 return validInRoomSystem(Point(x, y), getWidth(), getHeight(), perm);
466 bool const Agent::validInRoomSystem(Point p, unsigned int w, unsigned int h, int testperm) {
467 // Return true if this agent is inside the world room system at the specified point, or false if it isn't.
469 for (unsigned int i = 0; i < 4; i++) {
470 Point src, dest;
471 switch (i) {
472 case 0: src = boundingBoxPoint(0, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // left to bottom
473 case 1: src = boundingBoxPoint(1, p, w, h); dest = boundingBoxPoint(3, p, w, h); break; // right to bottom
474 case 2: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(0, p, w, h); break; // top to left
475 case 3: src = boundingBoxPoint(2, p, w, h); dest = boundingBoxPoint(1, p, w, h); break; // top to right
478 if (engine.version == 2) {
479 // Creatures 2 physics
481 int dx = dest.x - src.x;
482 int dy = dest.y - src.y;
484 Point deltapt(0,0);
485 double delta = 1000000000;
486 bool collided = false;
488 // TODO: check suffercollisions?
490 // TODO: muh, provided direction here is kinda a hack
491 findCollisionInDirection((i < 2) ? 3 : 2, src, dx, dy, deltapt, delta, collided, true);
492 if (collided) return false;
493 } else {
494 // Creatures 3 physics
496 float srcx = src.x, srcy = src.y;
498 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
499 if (!ourRoom) return false;
501 unsigned int dir; Line wall;
502 world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, wall, dir, testperm);
504 if (src != dest) return false;
508 return true;
511 void Agent::physicsTick() {
512 if (engine.version == 1) return; // C1 has no physics, and different attributes.
514 if (carriedby) return; // We don't move when carried, so what's the point?
516 if (engine.version == 2) {
517 // Creatures 2 physics is different.
518 physicsTickC2();
519 return;
522 if (!wasmoved) return; // some agents are created outside INST and get autokilled if we try physics on them before they move
524 // set destination point based on velocities
525 float destx = x + velx.getFloat();
526 float desty = y + vely.getFloat();
528 if (sufferphysics()) {
529 // TODO: falling behaviour needs looking at more closely..
530 // .. but it shouldn't be 'false' by default on non-physics agents, so..
531 falling = false;
532 // increase speed according to accg
533 // TODO: should we be changing vely first, instead of after a successful move (below)?
534 desty += accg.getFloat();
537 if (suffercollisions()) {
538 float lastdistance = 1000000.0f;
539 bool collided = false;
540 Line wall; // only valid when collided
541 unsigned int collidedirection = 0; // only valid when collided
542 Point bestmove;
544 // iterate through all four points of the bounding box
545 for (unsigned int i = 0; i < 4; i++) {
546 // this mess is because we want to start with the bottom point - DOWN (3) - before the others, for efficiency
547 Point src = boundingBoxPoint((i == 0) ? 3 : i - 1);
549 // store values
550 float srcx = src.x, srcy = src.y;
552 shared_ptr<Room> ourRoom = world.map.roomAt(srcx, srcy);
553 if (!ourRoom) {
554 ourRoom = world.map.roomAt(srcx, srcy);
556 if (!ourRoom) {
557 if (!displaycore) { // TODO: ugh, displaycore is a horrible thing to use for this
558 // we're out of the room system, physics bug, but let's try MVSFing back in to cover for fuzzie's poor programming skills
559 static bool tryingmove; tryingmove = false; // avoid infinite loop
560 if (!tryingmove && tryMoveToPlaceAround(x, y)) {
561 //std::cout << identify() << " was out of room system due to a physics bug but we hopefully moved it back in.." << std::endl;
562 tryingmove = true;
563 physicsTick();
564 return;
567 // didn't work!
568 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % srcx % srcy), false);
570 displaycore = true;
571 return; // out of room system
574 Point dest(destx + (srcx - x), desty + (srcy - y));
575 unsigned int local_collidedirection;
576 Line local_wall;
578 // this changes src to the point at which we end up
579 bool local_collided = world.map.collideLineWithRoomSystem(src, dest, ourRoom, src, local_wall, local_collidedirection, perm);
581 float dist;
582 if (src.x == srcx && src.y == srcy)
583 dist = 0.0f;
584 else {
585 float xdiff = src.x - srcx;
586 float ydiff = src.y - srcy;
587 dist = xdiff*xdiff + ydiff*ydiff;
590 if (dist >= lastdistance) {
591 assert(i != 0); // this had better not be our first collision!
592 continue; // further away than a previous collision
595 lastdistance = dist;
596 bestmove.x = x + (src.x - srcx);
597 bestmove.y = y + (src.y - srcy);
598 collidedirection = local_collidedirection;
599 wall = local_wall;
600 collided = local_collided;
602 if (dist == 0.0f)
603 break; // no point checking any more, is there?
606 // *** do actual movement
607 if (lastdistance != 0.0f) {
608 moveTo(bestmove.x, bestmove.y);
610 if (collided) {
611 lastcollidedirection = collidedirection;
612 queueScript(6, 0, velx, vely); // TODO: include this? .. we need to include SOMETHING, c3 ball checks for <3
614 if (elas != 0) {
615 if (wall.getType() == HORIZONTAL) {
616 vely.setFloat(-vely.getFloat());
617 } else if (wall.getType() == VERTICAL) {
618 velx.setFloat(-velx.getFloat());
619 } else {
620 // line starts always have a lower x value than the end
621 float xdiff = wall.getEnd().x - wall.getStart().x;
622 float ydiff = wall.getEnd().y - wall.getStart().y;
623 float fvelx = velx.getFloat(), fvely = vely.getFloat();
625 // calculate input/slope angles
626 double inputangle;
627 if (fvelx == 0.0f) {
628 if (fvely > 0.0f)
629 inputangle = M_PI / 2.0;
630 else
631 inputangle = 3 * (M_PI / 2.0);
632 } else {
633 inputangle = atan(fvely / fvelx);
635 double slopeangle = atan(-ydiff / xdiff); // xdiff != 0 because wall isn't vertical
637 // calculate output angle
638 double outputangle = slopeangle + (slopeangle - inputangle) + M_PI;
640 // turn back into component velocities
641 double vectorlength = sqrt(fvelx*fvelx + fvely*fvely);
642 float xoutput = cos(outputangle) * vectorlength;
643 float youtput = sin(outputangle) * vectorlength;
645 velx.setFloat(xoutput);
646 vely.setFloat(-youtput);
649 if (elas != 100.0f) {
650 velx.setFloat(velx.getFloat() * (elas / 100.0f));
651 vely.setFloat(vely.getFloat() * (elas / 100.0f));
653 } else
654 vely.setFloat(0);
655 } else if (sufferphysics() && accg != 0) {
656 falling = true; // TODO: icky
657 vely.setFloat(vely.getFloat() + accg.getFloat());
659 } else { velx.setFloat(0); vely.setFloat(0); } // TODO: correct?
660 } else {
661 if (vely.hasDecimal() || velx.hasDecimal())
662 moveTo(destx, desty);
663 if (sufferphysics())
664 vely.setFloat(vely.getFloat() + accg.getFloat());
667 if (sufferphysics() && (aero != 0)) {
668 // reduce speed according to AERO
669 // TODO: aero should be an integer!
670 velx.setFloat(velx.getFloat() - (velx.getFloat() * (aero.getFloat() / 100.0f)));
671 vely.setFloat(vely.getFloat() - (vely.getFloat() * (aero.getFloat() / 100.0f)));
675 shared_ptr<Room> const Agent::bestRoomAt(unsigned int tryx, unsigned int tryy, unsigned int direction, shared_ptr<Room> exclude) {
676 std::vector<shared_ptr<Room> > rooms = world.map.roomsAt(tryx, tryy);
678 shared_ptr<Room> r;
680 if (rooms.size() == 0) return shared_ptr<Room>();
681 if (rooms.size() == 1) r = rooms[0];
682 else if (rooms.size() > 1) {
683 unsigned int j;
684 for (j = 0; j < rooms.size(); j++) {
685 if (rooms[j] == exclude) continue;
687 if (direction == 0) { // left
688 if (rooms[j]->x_left == tryx) break;
689 } else if (direction == 1) { // right
690 if (rooms[j]->x_right == tryx) break;
691 } else if (direction == 2) { // top
692 if (rooms[j]->y_left_ceiling == tryy) break;
693 } else { // down
694 if (rooms[j]->y_left_floor == tryy) break;
697 if (j == rooms.size()) j = 0;
698 r = rooms[j];
701 if (r == exclude) return shared_ptr<Room>();
702 else return r;
705 // Creatures 2 collision finding
706 void const Agent::findCollisionInDirection(unsigned int i, Point src, int &dx, int &dy, Point &deltapt, double &delta, bool &collided, bool followrooms) {
707 src.x = (int)src.x;
708 src.y = (int)src.y;
710 shared_ptr<Room> room = bestRoomAt(src.x, src.y, i, shared_ptr<Room>());
712 if (!room) { // out of room system
713 if (!displaycore)
714 unhandledException(boost::str(boost::format("out of room system at (%f, %f)") % src.x % src.y), false);
715 grav.setInt(0);
716 displaycore = true;
717 return;
720 int lastdirection;
722 bool steep = abs(dy) > abs(dx);
724 int signdx = dx < 0 ? -1 : 1;
725 int signdy = dy < 0 ? -1 : 1;
727 Line l(Point(0,0),Point(dx,dy));
729 Point lastpoint(0,0);
731 for (int loc = 0; loc <= abs(steep ? dy : dx); loc++) {
732 Point p = steep ? l.pointAtY(loc*signdy) : l.pointAtX(loc*signdx);
733 p.x = (int)p.x;
734 p.y = (int)p.y;
736 bool trycollisions = false;
738 bool endofroom = false;
740 if (src.x + p.x < room->x_left) {
741 if (i != 1 && dx < 0) { trycollisions = true; lastdirection = 0; }
742 endofroom = true;
744 if (src.x + p.x > room->x_right) {
745 if (i != 0 && dx > 0) { trycollisions = true; lastdirection = 1; }
746 endofroom = true;
748 if (src.y + p.y < room->y_left_ceiling) {
749 if (i != 3 && dy < 0) { trycollisions = true; lastdirection = 2; }
750 endofroom = true;
752 if (src.y + p.y > room->y_left_floor) {
753 if (i != 2 && dy > 0) { trycollisions = true; lastdirection = 3; }
754 endofroom = true;
757 // find the next room, if necessary, and work out whether we should move into it or break out
758 if (endofroom) {
759 shared_ptr<Room> newroom = bestRoomAt(src.x + p.x, src.y + p.y, i, room);
761 bool collision = false;
763 // collide if we're out of the room system
764 if (!newroom) { collision = true; }
765 // collide if there's no new room connected to this one
766 else if (room->doors.find(newroom) == room->doors.end()) { collision = true; }
767 // collide if the PERM between this room and the new room is smaller than or equal to our size
768 else if (size.getInt() > room->doors[newroom]->perm) { collision = true; }
770 if (collision && (trycollisions || (!newroom))) {
771 collided = true;
772 break;
775 // move to the new room and keep going!
776 room = newroom;
779 if (room->floorpoints.size() && i == 3 && dy >= 0 && size.getInt() > room->floorvalue.getInt()) { // TODO: Hack!
780 // TODO: we don't check floorYatX isn't returning a 'real' room floor, but floorpoints should cover the whole floor anyway
781 int floory = room->floorYatX(src.x + p.x);
783 // never collide when the top point of an object is below the floor.
784 Point top = boundingBoxPoint(2);
786 // TODO: consider steep floors
787 if (src.y + p.y > floory && top.y < floory) {
788 collided = true;
789 lastdirection = 3;
790 break;
794 if ((!followrooms) && endofroom) break;
796 lastpoint = p;
799 double length2 = (lastpoint.x * lastpoint.x) + (lastpoint.y * lastpoint.y);
800 if (length2 < delta) {
801 // TODO: !followrooms is a horrible way to detect a non-physics call
802 if (collided && followrooms) lastcollidedirection = lastdirection;
803 deltapt = lastpoint;
804 delta = length2;
808 void Agent::physicsTickC2() {
809 int dx = velx.getInt(), dy = vely.getInt();
811 if (dx != 0 || dy != 0) grav.setInt(1);
813 if (grav.getInt() != 0 && sufferphysics()) {
814 dy += accg.getInt();
817 grav.setInt(1);
819 if (dy == 0 && dx == 0) { // nothing to do
820 if (vely.getInt() == 0) {
821 // really no motion
822 grav.setInt(0);
823 } else {
824 // y motion cancelled by gravity
825 vely.setInt(dy);
827 return;
829 vely.setInt(dy);
831 Point deltapt(0,0);
832 double delta = 1000000000;
834 bool collided = false;
836 if (suffercollisions()) {
837 for (unsigned int i = 0; i < 4; i++) {
838 Point src = boundingBoxPoint(i);
839 findCollisionInDirection(i, src, dx, dy, deltapt, delta, collided, true);
841 } else {
842 deltapt.x = dx;
843 deltapt.y = dy;
846 if (collided && (velx.getInt() != 0 || vely.getInt() != 0)) {
847 if (lastcollidedirection >= 2) // up and down
848 vely.setInt(-(vely.getInt() - (rest.getInt() * vely.getInt()) / 100));
849 else
850 velx.setInt(-(velx.getInt() - (rest.getInt() * velx.getInt()) / 100));
851 queueScript(6, 0);
853 if ((int)deltapt.x == 0 && (int)deltapt.y == 0) {
854 grav.setInt(0);
855 velx.setInt(0);
856 vely.setInt(0);
857 } else {
858 moveTo(x + (int)deltapt.x, y + (int)deltapt.y);
859 if (sufferphysics()) {
860 int fricx = (aero.getInt() * velx.getInt()) / 100;
861 int fricy = (aero.getInt() * vely.getInt()) / 100;
862 if (abs(velx.getInt()) > 0 && fricx == 0) fricx = (velx.getInt() < 0) ? -1 : 1;
863 if (abs(vely.getInt()) > 0 && fricy == 0) fricy = (vely.getInt() < 0) ? -1 : 1;
864 velx.setInt(velx.getInt() - fricx);
865 vely.setInt(vely.getInt() - fricy);
870 void Agent::tick() {
871 // sanity checks to stop ticks on dead agents
872 LifeAssert la(this);
873 if (dying) return;
875 if (sound) {
876 // if the sound is no longer playing...
877 if (sound->getState() != SS_PLAY) {
878 // ...release our reference to it
879 sound.reset();
880 } else {
881 // otherwise, reposition audio
882 updateAudio(sound);
886 // don't tick paused agents
887 if (paused) return;
889 // CA updates
890 if (emitca_index != -1 && emitca_amount != 0.0f) {
891 assert(0 <= emitca_index && emitca_index <= 19);
892 shared_ptr<Room> r = world.map.roomAt(x, y);
893 if (r) {
894 r->catemp[emitca_index] += emitca_amount;
895 /*if (r->catemp[emitca_index] <= 0.0f) r->catemp[emitca_index] = 0.0f;
896 else if (r->catemp[emitca_index] >= 1.0f) r->catemp[emitca_index] = 1.0f;*/
900 // tick the physics engine
901 physicsTick();
902 if (dying) return; // in case we were autokilled
904 // update the timer if needed, and then queue a timer event if necessary
905 if (timerrate) {
906 tickssincelasttimer++;
907 if (tickssincelasttimer == timerrate) {
908 queueScript(9); // TODO: include this?
909 tickssincelasttimer = 0;
911 assert(timerrate > tickssincelasttimer);
914 // tick the agent VM
915 if (vm) vmTick();
918 void Agent::unhandledException(std::string info, bool wasscript) {
919 // TODO: do something with this? empty the stack?
920 if (world.autostop) {
921 // autostop is mostly for Creatures Village, which is full of buggy scripts
922 stopScript();
923 } else if (world.autokill && !dynamic_cast<CreatureAgent *>(this)) { // don't autokill creatures! TODO: someday we probably should :)
924 kill();
925 if (wasscript)
926 std::cerr << identify() << " was autokilled during script " << lastScript << " due to: " << info << std::endl;
927 else
928 std::cerr << identify() << " was autokilled due to: " << info << std::endl;
929 } else {
930 stopScript();
931 if (wasscript)
932 std::cerr << identify() << " caused an exception during script " << lastScript << ": " << info << std::endl;
933 else
934 std::cerr << identify() << " caused an exception: " << info << std::endl;
938 void Agent::vmTick() {
939 // Tick the VM associated with this agent.
941 assert(vm); // There must *be* a VM to tick.
942 LifeAssert la(this); // sanity check
944 // If we're out of timeslice, give ourselves some more (depending on the game type).
945 if (!vm->timeslice) {
946 vm->timeslice = (engine.version < 3) ? 1 : 5;
949 // Keep trying to run VMs while we don't run out of timeslice, end up with a blocked VM, or a VM stops.
950 while (vm && vm->timeslice && !vm->isBlocking() && !vm->stopped()) {
951 assert(vm->timeslice > 0);
953 // Tell the VM to tick (using all available timeslice), catching exceptions as necessary.
954 try {
955 vm->tick();
956 } catch (invalidAgentException &e) {
957 // try letting the exception script handle it
958 if (!queueScript(255))
959 unhandledException(e.prettyPrint(), true);
960 else
961 stopScript(); // we still want current script to die
962 } catch (creaturesException &e) {
963 unhandledException(e.prettyPrint(), true);
964 } catch (std::exception &e) {
965 unhandledException(e.what(), true);
968 // If the VM stopped, it's done.
969 if (vm && vm->stopped()) {
970 world.freeVM(vm);
971 vm = NULL;
975 // Zot any remaining timeslice, since we're done now.
976 if (vm) vm->timeslice = 0;
978 // If there's no current VM but there's one on the call stack, a previous VM must have finished,
979 // pop one from the call stack to run next time.
980 if (!vm && !vmstack.empty()) {
981 vm = vmstack.front();
982 vmstack.pop_front();
986 Agent::~Agent() {
987 assert(lifecount == 0);
989 if (!initialized) return;
990 if (!dying) {
991 // we can't do kill() here because we can't do anything which might try using our shared_ptr
992 // (since this could be during world destruction)
994 if (vm) {
995 vm->stop();
996 world.freeVM(vm);
997 vm = 0;
1000 zotstack();
1002 if (sound) {
1003 sound->stop();
1004 sound.reset();
1009 void Agent::kill() {
1010 assert(!dying);
1011 if (floatable()) floatRelease();
1012 if (carrying) dropCarried(carrying);
1013 if (carriedby) carriedby->drop(this);
1014 // TODO: should the carried agent really be responsible for dropping from vehicle?
1015 if (invehicle) invehicle->drop(this);
1017 dying = true; // what a world, what a world...
1019 if (vm) {
1020 vm->stop();
1021 world.freeVM(vm);
1022 vm = 0;
1025 zotstack();
1026 agents_iter->reset();
1028 if (sound) {
1029 sound->stop();
1030 sound.reset();
1034 unsigned int Agent::getZOrder() const {
1035 if (invehicle) {
1036 // TODO: take notice of cabp in c2e, at least. also, stacking .. ?
1037 Vehicle *v = dynamic_cast<Vehicle *>(invehicle.get());
1038 assert(v);
1039 if (engine.version < 3)
1040 return v->cabinplane; // TODO: correct?
1041 else
1042 return v->getZOrder() + v->cabinplane;
1043 // TODO: Vehicle should probably rearrange zorder of passengers if ever moved
1044 } else if (carriedby) {
1045 // TODO: check for overflow
1046 // TODO: is adding our own zorder here correct behaviour? someone should check
1047 if (engine.version > 1)
1048 return carriedby->getZOrder() - 100;
1049 else
1050 return carriedby->getZOrder();
1051 } else {
1052 return zorder;
1056 void Agent::setZOrder(unsigned int z) {
1057 if (dying) return;
1058 zorder = z;
1061 int Agent::getUNID() const {
1062 if (unid != -1)
1063 return unid;
1064 return unid = world.newUNID(const_cast<Agent *>(this));
1067 #include "Catalogue.h"
1069 std::string Agent::identify() const {
1070 std::ostringstream o;
1071 o << (int)family << " " << (int)genus << " " << species;
1072 const std::string n = catalogue.getAgentName(family, genus, species);
1073 if (n.size())
1074 o << " (" + n + ")";
1075 /*if (unid != -1)
1076 o << " unid " << unid;
1077 else
1078 o << " (no unid assigned)"; */
1079 return o.str();
1082 bool agentzorder::operator ()(const Agent *s1, const Agent *s2) const {
1083 return s1->getZOrder() < s2->getZOrder();
1086 void Agent::pushVM(caosVM *newvm) {
1087 assert(newvm);
1088 if (vm)
1089 vmstack.push_front(vm);
1090 vm = newvm;
1093 bool Agent::vmStopped() {
1094 return (!vm || vm->stopped());
1097 void Agent::stopScript() {
1098 zotstack();
1099 if (vm)
1100 vm->stop();
1103 void Agent::addCarried(AgentRef a) {
1104 assert(a);
1106 // TODO: muh, vehicle drop needs more thought
1107 if (a->invehicle) {
1108 Vehicle *v = dynamic_cast<Vehicle *>(a->invehicle.get());
1109 assert(v);
1110 v->dropCarried(a);
1113 carry(a);
1115 // TODO: this doesn't reorder children or anything..
1116 a->setZOrder(a->zorder);
1118 // fire 'Got Carried Agent'
1119 if (engine.version >= 3)
1120 queueScript(124, a); // TODO: is this the correct param?
1123 void Agent::carry(AgentRef a) {
1124 assert(a);
1126 // TODO: check for infinite loops (eg, us carrying an agent which is carrying us) :)
1128 if (carrying)
1129 dropCarried(carrying);
1131 carrying = a;
1133 a->carriedby = AgentRef(this);
1134 // TODO: move carrying agent to the right position
1135 adjustCarried(0, 0);
1138 bool Agent::beDropped() {
1139 carriedby = AgentRef(0);
1140 bool wasinvehicle = invehicle;
1141 invehicle = AgentRef(0);
1143 // TODO: this doesn't reorder children or anything..
1144 setZOrder(zorder);
1146 // TODO: no idea if this is right, it tries to re-enable gravity when dropping C2 agents
1147 if (engine.version == 2) grav.setInt(1);
1148 if (engine.version == 3) falling = true;
1150 if (!wasinvehicle) { // ie, we're not being dropped by a vehicle
1151 // TODO: check for vehicles in a saner manner?
1152 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
1153 boost::shared_ptr<Agent> a = (*i);
1154 if (!a) continue;
1155 Vehicle *v = dynamic_cast<Vehicle *>(a.get());
1156 if (!v) continue;
1158 if (agentsTouching(this, v)) {
1159 v->addCarried(this);
1160 // TODO: how to handle not-invehicle case, where vehicle has failed to pick us up?
1161 if (invehicle) return true;
1166 if (engine.version > 1) {
1167 // TODO: maybe think about this some more - does this belong here?
1168 tryMoveToPlaceAround(x, y);
1171 // TODO: return value is not used anywhere yet?
1172 return true;
1175 void Agent::dropCarried(AgentRef a) {
1176 drop(a);
1178 // TODO: this doesn't reorder children or anything..
1179 a->setZOrder(a->zorder);
1181 // fire 'Lost Carried Agent'
1182 if (engine.version >= 3)
1183 queueScript(125, a); // TODO: is this the correct param?
1186 void Agent::drop(AgentRef a) {
1187 if (!carrying) return;
1188 assert(carrying == a);
1190 a->beDropped();
1191 carrying = AgentRef(0);
1194 std::pair<int, int> Agent::getCarryPoint() {
1195 unsigned int ourpose = 0;
1197 SpritePart *s;
1198 if ((s = dynamic_cast<SpritePart *>(part(0))))
1199 ourpose = s->getBase() + s->getPose();
1201 std::pair<int, int> pos(0, 0);
1203 std::map<unsigned int, std::pair<int, int> >::iterator i = carry_points.find(ourpose);
1204 if (i != carry_points.end())
1205 pos = i->second;
1207 return pos;
1210 std::pair<int, int> Agent::getCarriedPoint() {
1211 unsigned int theirpose = 0;
1213 SpritePart *s;
1214 if ((s = dynamic_cast<SpritePart *>(part(0))))
1215 theirpose = s->getBase() + s->getPose();
1217 std::pair<int, int> pos(0, 0);
1219 std::map<unsigned int, std::pair<int, int> >::iterator i = carried_points.find(theirpose);
1220 if (i != carried_points.end()) {
1221 pos = i->second;
1222 } else if (s && engine.version > 2) {
1223 // TODO: why fudge pickup location here and not in default carried points or something?
1224 // (this is nornagon's code which i moved - fuzzie)
1225 pos.first = s->getSprite()->width(s->getCurrentSprite()) / 2;
1228 return pos;
1231 void Agent::adjustCarried(float unusedxoffset, float unusedyoffset) {
1232 // Adjust the position of the agent we're carrying.
1233 // TODO: this doesn't actually position the carried agent correctly, sigh
1235 if (!carrying) return;
1237 unsigned int ourpose = 0;
1239 int xoffset = 0, yoffset = 0;
1240 if (engine.version < 3 && world.hand() == this) {
1241 // this appears to produce correct behaviour in the respective games, don't ask me -nornagon
1242 if (engine.version == 2)
1243 yoffset = world.hand()->getHeight() / 2 - 2;
1244 else
1245 yoffset = world.hand()->getHeight() / 2 - 3;
1248 std::pair<int, int> carrypoint = getCarryPoint();
1249 xoffset += carrypoint.first;
1250 yoffset += carrypoint.second;
1252 std::pair<int, int> carriedpoint = carrying->getCarriedPoint();
1253 xoffset -= carriedpoint.first;
1254 yoffset -= carriedpoint.second;
1256 if (xoffset != 0 || yoffset != 0)
1257 carrying->moveTo(x + xoffset, y + yoffset, true);
1260 void Agent::setClassifier(unsigned char f, unsigned char g, unsigned short s) {
1261 family = f;
1262 genus = g;
1263 species = s;
1265 if (engine.version < 3) {
1266 // TODO: categories for other game versions
1267 category = -1;
1268 } else {
1269 category = world.findCategory(family, genus, species);
1273 bool Agent::tryMoveToPlaceAround(float x, float y) {
1274 if (!suffercollisions()) {
1275 moveTo(x, y);
1276 return true;
1279 // second hacky attempt, move from side to side (+/- width) and up (- height) a little
1280 unsigned int trywidth = getWidth() * 2; if (trywidth < 100) trywidth = 100;
1281 unsigned int tryheight = getHeight() * 2; if (tryheight < 100) tryheight = 100;
1282 for (unsigned int xadjust = 0; xadjust < trywidth; xadjust++) {
1283 for (unsigned int yadjust = 0; yadjust < tryheight; yadjust++) {
1284 if (validInRoomSystem(Point(x - xadjust, y - yadjust), getWidth(), getHeight(), perm))
1285 moveTo(x - xadjust, y - yadjust);
1286 else if ((xadjust != 0) && validInRoomSystem(Point(x + xadjust, y - yadjust), getWidth(), getHeight(), perm))
1287 moveTo(x + xadjust, y - yadjust);
1288 else
1289 continue;
1290 return true;
1294 return false;
1297 void Agent::join(unsigned int outid, AgentRef dest, unsigned int inid) {
1298 assert(dest);
1299 assert(outports.find(outid) != outports.end());
1300 assert(dest->inports.find(inid) != dest->inports.end());
1301 outports[outid]->dests.push_back(std::pair<AgentRef, unsigned int>(dest, inid));
1302 dest->inports[inid]->source = this;
1303 dest->inports[inid]->sourceid = outid;
1306 /* vim: set noet: */