reorganise bubble creation code into Bubble::newBubble
[openc2e.git] / CompoundPart.cpp
blobb24aa33fcb83360e41acdcf5c93191eaafd3aee8
1 /*
2 * CompoundPart.cpp
3 * openc2e
5 * Created by Alyssa Milburn on Tue May 25 2004.
6 * Copyright (c) 2004-2008 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 "CompoundPart.h"
21 #include "CameraPart.h"
22 #include "Camera.h"
23 #include "World.h"
24 #include "Engine.h"
25 #include "creaturesImage.h"
26 #include "Backend.h"
27 #include "Agent.h"
29 bool partzorder::operator ()(const CompoundPart *s1, const CompoundPart *s2) const {
30 // TODO: unsure about all of this, needs a check (but seems to work)
31 if (s1->getParent()->getZOrder() == s2->getParent()->getZOrder()) {
32 // part 0 is often at the same plane as other parts..
33 // TODO: is this correct fix? I suspect not, we should be sorting by reaction order, not id.
34 if (s1->getParent() == s2->getParent()) {
35 return s1->id > s2->id;
36 } else
37 return s1->getZOrder() > s2->getZOrder();
39 return s1->getParent()->getZOrder() > s2->getParent()->getZOrder();
42 shared_ptr<creaturesImage> TextEntryPart::caretsprite;
44 void CompoundPart::render(Surface *renderer, int xoffset, int yoffset) {
45 if (parent->visible) {
46 partRender(renderer, xoffset + (int)parent->x, yoffset + (int)parent->y);
47 if (parent->displaycore /*&& (id == 0)*/) {
48 // TODO: tsk, this should be drawn along with the other craziness on the line plane, i expect
49 int xoff = xoffset + (int)parent->x + x;
50 int yoff = yoffset + (int)parent->y + y;
51 renderer->renderLine(xoff + (getWidth() / 2), yoff, xoff + getWidth(), yoff + (getHeight() / 2), 0xFF0000CC);
52 renderer->renderLine(xoff + getWidth(), yoff + (getHeight() / 2), xoff + (getWidth() / 2), yoff + getHeight(), 0xFF0000CC);
53 renderer->renderLine(xoff + (getWidth() / 2), yoff + getHeight(), xoff, yoff + (getHeight() / 2), 0xFF0000CC);
54 renderer->renderLine(xoff, yoff + (getHeight() / 2), xoff + (getWidth() / 2), yoff, 0xFF0000CC);
59 bool CompoundPart::showOnRemoteCameras() {
60 return !parent->camerashy();
63 void SpritePart::partRender(Surface *renderer, int xoffset, int yoffset) {
64 // TODO: we need a nicer way to handle such errors
65 if (getCurrentSprite() >= getSprite()->numframes()) {
66 std::string err = boost::str(boost::format("pose to be rendered %d (firstimg %d, base %d) was past end of sprite file '%s' (%d sprites)") %
67 pose % firstimg % base % getSprite()->getName() % getSprite()->numframes());
68 parent->unhandledException(err, false);
69 return;
71 assert(getCurrentSprite() < getSprite()->numframes());
72 renderer->render(getSprite(), getCurrentSprite(), xoffset + x, yoffset + y, has_alpha, alpha, draw_mirrored);
75 void SpritePart::setFrameNo(unsigned int f) {
76 assert(f < animation.size());
77 if (firstimg + base + animation[f] >= getSprite()->numframes()) {
78 std::string err = boost::str(boost::format("animation frame %d (firstimg %d, base %d, value %d) was past end of sprite file '%s' (%d sprites)") %
79 f % firstimg % base % (int)animation[f] % getSprite()->getName() % getSprite()->numframes());
80 parent->unhandledException(err, false);
81 animation.clear();
82 return;
85 frameno = f;
86 pose = animation[f];
87 spriteno = firstimg + base + pose;
90 void SpritePart::setPose(unsigned int p) {
91 if (firstimg + base + p >= getSprite()->numframes()) {
92 std::string err = boost::str(boost::format("new pose %d (firstimg %d, base %d) was past end of sprite file '%s' (%d sprites)") %
93 p % firstimg % base % getSprite()->getName() % getSprite()->numframes());
94 parent->unhandledException(err, false);
95 return;
98 animation.clear();
99 pose = p;
100 spriteno = firstimg + base + pose;
103 void SpritePart::setBase(unsigned int b) {
104 base = b;
107 bool SpritePart::transparentAt(unsigned int x, unsigned int y) {
108 return getSprite()->transparentAt(getCurrentSprite(), x, y);
111 void CompoundPart::gainFocus() {
112 assert(false);
115 void CompoundPart::loseFocus() {
116 throw creaturesException("impossible loseFocus() call");
119 void CompoundPart::handleKey(char c) {
120 throw creaturesException("impossible handleKey() call");
123 void CompoundPart::handleSpecialKey(char c) {
124 throw creaturesException("impossible handleSpecialKey() call");
127 int CompoundPart::handleClick(float clickx, float clicky) {
128 return parent->handleClick(clickx + x + parent->x, clicky + y + parent->y);
131 CompoundPart::CompoundPart(Agent *p, unsigned int _id, int _x, int _y, int _z) : parent(p), zorder(_z), id(_id) {
132 addZOrder();
133 x = _x;
134 y = _y;
136 has_alpha = false;
139 CompoundPart::~CompoundPart() {
140 world.zorder.erase(zorder_iter);
143 SpritePart::SpritePart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg,
144 int _x, int _y, unsigned int _z) : AnimatablePart(p, _id, _x, _y, _z) {
145 origsprite = sprite = world.gallery.getImage(spritefile);
146 firstimg = fimg;
147 caos_assert(sprite);
149 pose = 0;
150 base = 0;
151 spriteno = firstimg;
152 is_transparent = (engine.version > 2);
153 framerate = 1;
154 framedelay = 0;
155 draw_mirrored = false;
157 if (sprite->numframes() <= firstimg) {
158 if (world.gametype == "cv") {
159 // Creatures Village allows you to create sprites with crazy invalid data, do the same as it does
160 // (obviously the firstimg data is invalid so all attempts to change the pose/etc will fail, same as in CV)
161 spriteno = 0;
162 } else if (engine.bmprenderer) {
163 // BLCK hasn't been called yet, so we can't check validity yet
164 } else {
165 throw caosException(boost::str(boost::format("Failed to create sprite part: first sprite %d is beyond %d sprite(s) in file") % firstimg % sprite->numframes()));
170 SpritePart::~SpritePart() {
173 void SpritePart::changeSprite(std::string spritefile, unsigned int fimg) {
174 shared_ptr<creaturesImage> spr = world.gallery.getImage(spritefile);
175 caos_assert(spr);
176 caos_assert(spr->numframes() > fimg);
177 // TODO: should we preserve base/pose here, instead?
178 pose = 0;
179 base = 0;
180 firstimg = fimg;
181 spriteno = fimg;
182 // TODO: should we preserve tint?
183 origsprite = sprite = spr;
186 unsigned int SpritePart::getWidth() {
187 return sprite->width(getCurrentSprite());
190 unsigned int SpritePart::getHeight() {
191 return sprite->height(getCurrentSprite());
194 unsigned int CompoundPart::getZOrder() const {
195 return parent->getZOrder() + zorder;
198 void CompoundPart::zapZOrder() {
199 renderable::zapZOrder();
200 world.zorder.erase(zorder_iter);
203 void CompoundPart::addZOrder() {
204 renderable::addZOrder();
205 zorder_iter = world.zorder.insert(this);
208 void SpritePart::tint(unsigned char r, unsigned char g, unsigned char b, unsigned char rotation, unsigned char swap) {
209 sprite = origsprite->mutableCopy();
210 sprite->tint(r, g, b, rotation, swap);
213 DullPart::DullPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
214 unsigned int _z) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
217 ButtonPart::ButtonPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
218 unsigned int _z, const bytestring_t &animhover, int msgid, int option) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
219 messageid = msgid;
220 hitopaquepixelsonly = (option == 1);
221 hoveranimation = animhover;
224 unsigned int calculateScriptId(unsigned int message_id); // from caosVM_agent.cpp, TODO: move into shared file
226 int ButtonPart::handleClick(float x, float y) {
227 return calculateScriptId(messageid);
230 TextPart::TextPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y, unsigned int _z, std::string fontsprite)
231 : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
232 textsprite = world.gallery.getImage(fontsprite);
233 caos_assert(textsprite);
234 caos_assert(textsprite->numframes() == 224);
235 leftmargin = 8; topmargin = 8; rightmargin = 8; bottommargin = 8;
236 linespacing = 0; charspacing = 0;
237 horz_align = leftalign; vert_align = top;
238 currpage = 0;
239 recalculateData(); // ie, insert a blank first page
242 TextPart::~TextPart() {
245 void TextPart::addTint(std::string tintinfo) {
246 // add a tint, starting at text.size(), using the data in tintinfo
247 // TODO: there's some caching to be done here, but tinting is rather rare, so..
249 unsigned short r = 128, g = 128, b = 128, rot = 128, swap = 128;
250 int where = 0;
251 std::string cur;
252 for (unsigned int i = 0; i <= tintinfo.size(); i++) {
253 if (i == tintinfo.size() || tintinfo[i] == ' ') {
254 unsigned short val = atoi(cur.c_str());
255 if (val <= 256) {
256 switch (where) {
257 case 0: r = val; break;
258 case 1: g = val; break;
259 case 2: b = val; break;
260 case 3: rot = val; break;
261 case 4: swap = val; break;
263 } // TODO: else explode();
264 where++;
265 cur = "";
266 if (where > 4) break;
267 } else cur += tintinfo[i];
270 texttintinfo t;
271 t.offset = text.size();
273 if (!(r == g == b == rot == swap == 128)) {
274 t.sprite = textsprite->mutableCopy();
275 t.sprite->tint(r, g, b, rot, swap);
276 } else t.sprite = textsprite;
278 tints.push_back(t);
281 void TextPart::setText(std::string t) {
282 text.clear();
283 tints.clear();
285 // parse and remove the <tint> tagging
286 for (unsigned int i = 0; i < t.size(); i++) {
287 if ((t[i] == '<') && (t.size() > i+4))
288 if ((t[i + 1] == 't') && (t[i + 2] == 'i') && (t[i + 3] == 'n') && (t[i + 4] == 't')) {
289 i += 5;
290 std::string tintinfo;
291 if (t[i] == ' ') i++; // skip initial space, if any
292 for (; i < t.size(); i++) {
293 if (t[i] == '>')
294 break;
295 tintinfo += t[i];
297 addTint(tintinfo);
298 continue;
300 text += t[i];
303 recalculateData();
306 void TextEntryPart::setText(std::string t) {
307 TextPart::setText(t);
309 // place caret at the end of the text
310 caretpos = text.size();
313 unsigned int calculateScriptId(unsigned int message_id); // from caosVM_agent.cpp, TODO: move into shared file
315 int TextEntryPart::handleClick(float clickx, float clicky) {
316 world.setFocus(this);
318 return -1; // TODO: this shouldn't be passed onto the parent agent?
321 void TextEntryPart::handleKey(char c) {
322 text.insert(caretpos, 1, c);
323 caretpos++;
324 recalculateData();
327 void TextEntryPart::handleSpecialKey(char c) {
328 switch (c) {
329 case 8: // backspace
330 if (caretpos == 0) return;
331 text.erase(text.begin() + (caretpos - 1));
332 caretpos--;
333 break;
335 case 13: // return
336 // TODO: check if we should do this or a newline
337 parent->queueScript(calculateScriptId(messageid), 0); // TODO: is a null FROM correct?
338 return;
340 case 37: // left
341 if (caretpos == 0) return;
342 caretpos--;
343 return;
345 case 39: // right
346 if (caretpos == text.size()) return;
347 caretpos++;
348 return;
350 case 38: // up
351 case 40: // down
352 return;
354 case 46: // delete
355 if ((text.size() == 0) || (caretpos >= text.size()))
356 return;
357 text.erase(text.begin() + caretpos);
358 break;
360 default:
361 return;
364 assert(caretpos <= text.size());
366 recalculateData();
369 void TextPart::setFormat(int left, int top, int right, int bottom, int line, int _char, horizontalalign horza, verticalalign verta, bool lastpagescroll) {
370 leftmargin = left;
371 topmargin = top;
372 rightmargin = right;
373 bottommargin = bottom;
374 linespacing = line;
375 charspacing = _char;
376 horz_align = horza;
377 vert_align = verta;
378 last_page_scroll = lastpagescroll;
379 recalculateData();
382 unsigned int TextPart::calculateWordWidth(std::string word) {
383 unsigned int x = 0;
384 for (unsigned int i = 0; i < word.size(); i++) {
385 if (word[i] < 32) continue; // TODO: replace with space or similar?
386 int spriteid = word[i] - 32;
388 x += textsprite->width(spriteid);
389 if (i != 0) x += charspacing;
391 return x;
395 * Recalculate the data used for rendering the text part.
397 void TextPart::recalculateData() {
398 linedata currentdata;
400 lines.clear();
401 pages.clear();
402 pageheights.clear();
403 pages.push_back(0);
404 if (text.size() == 0) {
405 pageheights.push_back(0);
406 lines.push_back(currentdata); // blank line, so caret is rendered in TextEntryParts
407 return;
410 unsigned int textwidth = getWidth() - leftmargin - rightmargin;
411 unsigned int textheight = getHeight() - topmargin - bottommargin;
413 unsigned int currenty = 0, usedheight = 0;
414 unsigned int i = 0;
415 while (i < text.size()) {
416 bool newline = false;
417 unsigned int startoffset = i;
418 // first, retrieve a word from the text
419 std::string word;
420 for (; i < text.size(); i++) {
421 if ((text[i] == ' ') || (text[i] == '\n')) {
422 if (text[i] == '\n') newline = true;
423 i++;
424 break;
426 word += text[i];
429 // next, work out whether it fits
430 unsigned int wordlen = calculateWordWidth(word);
431 unsigned int spacelen = textsprite->width(0) + charspacing;
432 unsigned int possiblelen = wordlen;
433 if (currentdata.text.size() > 0)
434 possiblelen = wordlen + spacelen;
435 // TODO: set usedheight as appropriate/needed
436 usedheight = textsprite->height(0);
437 if (currentdata.width + possiblelen <= textwidth) {
438 // the rest of the word fits on the current line, so that's okay.
439 // add a space if we're not the first word on this line
440 if (currentdata.text.size() > 0) word = std::string(" ") + word;
441 currentdata.text += word;
442 currentdata.width += possiblelen;
443 } else if (wordlen <= textwidth) {
444 // the rest of the word doesn't fit on the current line, but does on the next line.
445 if (currenty + usedheight > textheight) {
446 pageheights.push_back(currenty);
447 pages.push_back(lines.size());
448 currenty = 0;
449 } else currenty += usedheight + linespacing + 1;
450 currentdata.text += " "; // TODO: HACK THINK ABOUT THIS
451 lines.push_back(currentdata);
452 currentdata.reset(startoffset);
454 currentdata.text += word;
455 currentdata.width += wordlen;
456 } else {
457 // TODO: word is too wide to fit on a single line
458 // we should output as much as possible and then go backwards
461 // we force a newline here if necessary (ie, if the last char is '\n', except not in the last case)
462 if ((i < text.size()) && (newline)) {
463 if (currenty + usedheight > textheight) {
464 pageheights.push_back(currenty);
465 pages.push_back(lines.size());
466 currenty = 0;
467 } else currenty += usedheight + linespacing + 1;
468 lines.push_back(currentdata);
469 currentdata.reset(i);
473 if (currentdata.text.size() > 0) {
474 currenty += usedheight;
475 if (text[text.size() - 1] == ' ') currentdata.text += " "; // TODO: HACK THINK ABOUT THIS
476 lines.push_back(currentdata);
479 pageheights.push_back(currenty);
482 void TextPart::partRender(Surface *renderer, int xoffset, int yoffset, TextEntryPart *caretdata) {
483 SpritePart::partRender(renderer, xoffset, yoffset);
485 unsigned int xoff = xoffset + x + leftmargin;
486 unsigned int yoff = yoffset + y + topmargin;
487 unsigned int textwidth = getWidth() - leftmargin - rightmargin;
488 unsigned int textheight = getHeight() - topmargin - bottommargin;
490 unsigned int currenty = 0, usedheight = 0;
491 if (vert_align == bottom)
492 currenty = textheight - pageheights[currpage];
493 else if (vert_align == middle)
494 currenty = (textheight - pageheights[currpage]) / 2;
495 unsigned int startline = pages[currpage];
496 unsigned int endline = (currpage + 1 < pages.size() ? pages[currpage + 1] : lines.size());
497 shared_ptr<creaturesImage> sprite = textsprite; unsigned int currtint = 0;
498 for (unsigned int i = startline; i < endline; i++) {
499 unsigned int currentx = 0, somex = xoff;
500 if (horz_align == rightalign)
501 somex = somex + (textwidth - lines[i].width);
502 else if (horz_align == centeralign)
503 somex = somex + ((textwidth - lines[i].width) / 2);
505 for (unsigned int x = 0; x < lines[i].text.size(); x++) {
506 if (currtint < tints.size() && tints[currtint].offset == lines[i].offset + x) {
507 sprite = tints[currtint].sprite;
508 currtint++;
511 if (lines[i].text[x] < 32) continue; // TODO: replace with space or similar?
512 int spriteid = lines[i].text[x] - 32;
513 renderer->render(sprite, spriteid, somex + currentx, yoff + currenty, has_alpha, alpha);
514 if ((caretdata) && (caretdata->caretpos == lines[i].offset + x))
515 caretdata->renderCaret(renderer, somex + currentx, yoff + currenty);
516 currentx += textsprite->width(spriteid) + charspacing;
518 if ((caretdata) && (caretdata->caretpos == lines[i].offset + lines[i].text.size()))
519 caretdata->renderCaret(renderer, somex + currentx, yoff + currenty);
520 currenty += textsprite->height(0) + linespacing + 1;
524 FixedTextPart::FixedTextPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
525 unsigned int _z, std::string fontsprite) : TextPart(p, _id, spritefile, fimg, _x, _y, _z, fontsprite) {
526 // nothing, hopefully.. :)
529 TextEntryPart::TextEntryPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
530 unsigned int _z, unsigned int msgid, std::string fontsprite) : TextPart(p, _id, spritefile, fimg, _x, _y, _z, fontsprite) {
531 // TODO: hm, this never gets freed..
532 if (!caretsprite) { caretsprite = world.gallery.getImage("cursor"); caos_assert(caretsprite); }
534 caretpose = 0;
535 caretpos = 0;
536 focused = false;
537 messageid = msgid;
540 void TextEntryPart::partRender(Surface *renderer, int xoffset, int yoffset) {
541 TextPart::partRender(renderer, xoffset, yoffset, (focused ? this : 0));
544 void TextEntryPart::renderCaret(Surface *renderer, int xoffset, int yoffset) {
545 // TODO: fudge xoffset/yoffset as required
546 renderer->render(caretsprite, caretpose, xoffset, yoffset, has_alpha, alpha);
549 void TextEntryPart::tick() {
550 SpritePart::tick();
552 if (focused) {
553 caretpose++;
554 if (caretpose == caretsprite->numframes())
555 caretpose = 0;
559 void SpritePart::tick() {
560 if (!animation.empty()) {
561 if (framerate > 1) {
562 framedelay++;
563 if (framedelay == (unsigned int)framerate + 1)
564 framedelay = 0;
567 if (framedelay == 0) {
568 unsigned int f = frameno + 1;
569 if (f == animation.size()) return;
570 if (animation[f] == 255) {
571 if (f == (animation.size() - 1)) f = 0;
572 else f = animation[f + 1];
574 // TODO: check f is valid..
575 setFrameNo(f);
580 CameraPart::CameraPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
581 unsigned int _z, unsigned int view_width, unsigned int view_height, unsigned int camera_width, unsigned int camera_height)
582 : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
583 viewwidth = view_width;
584 viewheight = view_height;
585 camerawidth = camera_width;
586 cameraheight = camera_height;
587 camera = shared_ptr<Camera>(new PartCamera(this));
590 void CameraPart::partRender(class Surface *renderer, int xoffset, int yoffset) {
591 // TODO: hack to stop us rendering cameras inside cameras. better way?
592 if (renderer == engine.backend->getMainSurface()) {
593 // make sure we're onscreen before bothering to do any work..
594 if (xoffset + x + viewwidth > 0 && yoffset + y + viewheight > 0 &&
595 xoffset + x < (int)renderer->getWidth() && yoffset + y < (int)renderer->getHeight()) {
596 Surface *surface = engine.backend->newSurface(viewwidth, viewheight);
597 assert(surface); // TODO: good behaviour?
598 world.drawWorld(camera.get(), surface);
599 renderer->blitSurface(surface, xoffset + x, yoffset + y, camerawidth, cameraheight);
600 engine.backend->freeSurface(surface);
604 SpritePart::partRender(renderer, xoffset, yoffset);
607 void CameraPart::tick() {
608 SpritePart::tick();
610 camera->tick();
613 GraphPart::GraphPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
614 unsigned int _z, unsigned int novalues) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
615 // TODO: store novalues
618 /* vim: set noet: */