endianlove.h: don't use stdint definitions outside the stdint ifdef
[openc2e.git] / CompoundPart.cpp
blob76f5a1e83e599ec2b1feb71381fcd8c838115061
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::handleClick(float clickx, float clicky) {
112 parent->handleClick(clickx + x + parent->x, clicky + y + parent->y);
115 CompoundPart::CompoundPart(Agent *p, unsigned int _id, int _x, int _y, int _z) : parent(p), zorder(_z), id(_id) {
116 addZOrder();
117 x = _x;
118 y = _y;
120 has_alpha = false;
123 CompoundPart::~CompoundPart() {
124 world.zorder.erase(zorder_iter);
127 SpritePart::SpritePart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg,
128 int _x, int _y, unsigned int _z) : AnimatablePart(p, _id, _x, _y, _z) {
129 origsprite = sprite = world.gallery.getImage(spritefile);
130 firstimg = fimg;
131 caos_assert(sprite);
133 pose = 0;
134 base = 0;
135 spriteno = firstimg;
136 is_transparent = (engine.version > 2);
137 framerate = 1;
138 framedelay = 0;
139 draw_mirrored = false;
141 if (sprite->numframes() <= firstimg) {
142 if (world.gametype == "cv") {
143 // Creatures Village allows you to create sprites with crazy invalid data, do the same as it does
144 // (obviously the firstimg data is invalid so all attempts to change the pose/etc will fail, same as in CV)
145 spriteno = 0;
146 } else if (engine.bmprenderer) {
147 // BLCK hasn't been called yet, so we can't check validity yet
148 } else {
149 throw caosException(boost::str(boost::format("Failed to create sprite part: first sprite %d is beyond %d sprite(s) in file") % firstimg % sprite->numframes()));
154 SpritePart::~SpritePart() {
157 void SpritePart::changeSprite(std::string spritefile, unsigned int fimg) {
158 shared_ptr<creaturesImage> spr = world.gallery.getImage(spritefile);
159 caos_assert(spr);
160 caos_assert(spr->numframes() > fimg);
161 // TODO: should we preserve base/pose here, instead?
162 pose = 0;
163 base = 0;
164 firstimg = fimg;
165 spriteno = fimg;
166 // TODO: should we preserve tint?
167 origsprite = sprite = spr;
170 unsigned int SpritePart::getWidth() {
171 return sprite->width(getCurrentSprite());
174 unsigned int SpritePart::getHeight() {
175 return sprite->height(getCurrentSprite());
178 unsigned int CompoundPart::getZOrder() const {
179 return parent->getZOrder() + zorder;
182 void CompoundPart::zapZOrder() {
183 renderable::zapZOrder();
184 world.zorder.erase(zorder_iter);
187 void CompoundPart::addZOrder() {
188 renderable::addZOrder();
189 zorder_iter = world.zorder.insert(this);
192 void SpritePart::tint(unsigned char r, unsigned char g, unsigned char b, unsigned char rotation, unsigned char swap) {
193 sprite = origsprite->mutableCopy();
194 sprite->tint(r, g, b, rotation, swap);
197 DullPart::DullPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
198 unsigned int _z) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
201 ButtonPart::ButtonPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
202 unsigned int _z, const bytestring_t &animhover, int msgid, int option) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
203 messageid = msgid;
204 hitopaquepixelsonly = (option == 1);
205 hoveranimation = animhover;
208 unsigned int calculateScriptId(unsigned int message_id); // from caosVM_agent.cpp, TODO: move into shared file
210 void ButtonPart::handleClick(float x, float y) {
211 caosVar v; v.setInt(id); // _p1_ is id of part, according to Edynn code
212 parent->queueScript(calculateScriptId(messageid), (Agent *)world.hand(), v);
215 TextPart::TextPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y, unsigned int _z, std::string fontsprite)
216 : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
217 textsprite = world.gallery.getImage(fontsprite);
218 caos_assert(textsprite);
219 caos_assert(textsprite->numframes() == 224);
220 leftmargin = 8; topmargin = 8; rightmargin = 8; bottommargin = 8;
221 linespacing = 0; charspacing = 0;
222 horz_align = leftalign; vert_align = top;
223 currpage = 0;
224 recalculateData(); // ie, insert a blank first page
227 TextPart::~TextPart() {
230 void TextPart::addTint(std::string tintinfo) {
231 // add a tint, starting at text.size(), using the data in tintinfo
232 // TODO: there's some caching to be done here, but tinting is rather rare, so..
234 unsigned short r = 128, g = 128, b = 128, rot = 128, swap = 128;
235 int where = 0;
236 std::string cur;
237 for (unsigned int i = 0; i <= tintinfo.size(); i++) {
238 if (i == tintinfo.size() || tintinfo[i] == ' ') {
239 unsigned short val = atoi(cur.c_str());
240 if (val <= 256) {
241 switch (where) {
242 case 0: r = val; break;
243 case 1: g = val; break;
244 case 2: b = val; break;
245 case 3: rot = val; break;
246 case 4: swap = val; break;
248 } // TODO: else explode();
249 where++;
250 cur = "";
251 if (where > 4) break;
252 } else cur += tintinfo[i];
255 texttintinfo t;
256 t.offset = text.size();
258 if (!(r == g == b == rot == swap == 128)) {
259 t.sprite = textsprite->mutableCopy();
260 t.sprite->tint(r, g, b, rot, swap);
261 } else t.sprite = textsprite;
263 tints.push_back(t);
266 void TextPart::setText(std::string t) {
267 text.clear();
268 tints.clear();
270 // parse and remove the <tint> tagging
271 for (unsigned int i = 0; i < t.size(); i++) {
272 if ((t[i] == '<') && (t.size() > i+4))
273 if ((t[i + 1] == 't') && (t[i + 2] == 'i') && (t[i + 3] == 'n') && (t[i + 4] == 't')) {
274 i += 5;
275 std::string tintinfo;
276 if (t[i] == ' ') i++; // skip initial space, if any
277 for (; i < t.size(); i++) {
278 if (t[i] == '>')
279 break;
280 tintinfo += t[i];
282 addTint(tintinfo);
283 continue;
285 text += t[i];
288 recalculateData();
291 void TextEntryPart::setText(std::string t) {
292 TextPart::setText(t);
294 // place caret at the end of the text
295 caretpos = text.size();
298 unsigned int calculateScriptId(unsigned int message_id); // from caosVM_agent.cpp, TODO: move into shared file
300 void TextEntryPart::handleClick(float clickx, float clicky) {
301 world.setFocus(this);
304 void TextEntryPart::handleKey(char c) {
305 text.insert(caretpos, 1, c);
306 caretpos++;
307 recalculateData();
310 void TextEntryPart::handleSpecialKey(char c) {
311 switch (c) {
312 case 8: // backspace
313 if (caretpos == 0) return;
314 text.erase(text.begin() + (caretpos - 1));
315 caretpos--;
316 break;
318 case 13: // return
319 // TODO: check if we should do this or a newline
320 parent->queueScript(calculateScriptId(messageid), 0); // TODO: is a null FROM correct?
321 return;
323 case 37: // left
324 if (caretpos == 0) return;
325 caretpos--;
326 return;
328 case 39: // right
329 if (caretpos == text.size()) return;
330 caretpos++;
331 return;
333 case 38: // up
334 case 40: // down
335 return;
337 case 46: // delete
338 if ((text.size() == 0) || (caretpos >= text.size()))
339 return;
340 text.erase(text.begin() + caretpos);
341 break;
343 default:
344 return;
347 assert(caretpos <= text.size());
349 recalculateData();
352 void TextPart::setFormat(int left, int top, int right, int bottom, int line, int _char, horizontalalign horza, verticalalign verta, bool lastpagescroll) {
353 leftmargin = left;
354 topmargin = top;
355 rightmargin = right;
356 bottommargin = bottom;
357 linespacing = line;
358 charspacing = _char;
359 horz_align = horza;
360 vert_align = verta;
361 last_page_scroll = lastpagescroll;
362 recalculateData();
365 unsigned int TextPart::calculateWordWidth(std::string word) {
366 unsigned int x = 0;
367 for (unsigned int i = 0; i < word.size(); i++) {
368 if (word[i] < 32) continue; // TODO: replace with space or similar?
369 int spriteid = word[i] - 32;
371 x += textsprite->width(spriteid);
372 if (i != 0) x += charspacing;
374 return x;
378 * Recalculate the data used for rendering the text part.
380 void TextPart::recalculateData() {
381 linedata currentdata;
383 lines.clear();
384 pages.clear();
385 pageheights.clear();
386 pages.push_back(0);
387 if (text.size() == 0) {
388 pageheights.push_back(0);
389 lines.push_back(currentdata); // blank line, so caret is rendered in TextEntryParts
390 return;
393 unsigned int textwidth = getWidth() - leftmargin - rightmargin;
394 unsigned int textheight = getHeight() - topmargin - bottommargin;
396 unsigned int currenty = 0, usedheight = 0;
397 unsigned int i = 0;
398 while (i < text.size()) {
399 bool newline = false;
400 unsigned int startoffset = i;
401 // first, retrieve a word from the text
402 std::string word;
403 for (; i < text.size(); i++) {
404 if ((text[i] == ' ') || (text[i] == '\n')) {
405 if (text[i] == '\n') newline = true;
406 i++;
407 break;
409 word += text[i];
412 // next, work out whether it fits
413 unsigned int wordlen = calculateWordWidth(word);
414 unsigned int spacelen = textsprite->width(0) + charspacing;
415 unsigned int possiblelen = wordlen;
416 if (currentdata.text.size() > 0)
417 possiblelen = wordlen + spacelen;
418 // TODO: set usedheight as appropriate/needed
419 usedheight = textsprite->height(0);
420 if (currentdata.width + possiblelen <= textwidth) {
421 // the rest of the word fits on the current line, so that's okay.
422 // add a space if we're not the first word on this line
423 if (currentdata.text.size() > 0) word = std::string(" ") + word;
424 currentdata.text += word;
425 currentdata.width += possiblelen;
426 } else if (wordlen <= textwidth) {
427 // the rest of the word doesn't fit on the current line, but does on the next line.
428 if (currenty + usedheight > textheight) {
429 pageheights.push_back(currenty);
430 pages.push_back(lines.size());
431 currenty = 0;
432 } else currenty += usedheight + linespacing + 1;
433 currentdata.text += " "; // TODO: HACK THINK ABOUT THIS
434 lines.push_back(currentdata);
435 currentdata.reset(startoffset);
437 currentdata.text += word;
438 currentdata.width += wordlen;
439 } else {
440 // TODO: word is too wide to fit on a single line
441 // we should output as much as possible and then go backwards
444 // we force a newline here if necessary (ie, if the last char is '\n', except not in the last case)
445 if ((i < text.size()) && (newline)) {
446 if (currenty + usedheight > textheight) {
447 pageheights.push_back(currenty);
448 pages.push_back(lines.size());
449 currenty = 0;
450 } else currenty += usedheight + linespacing + 1;
451 lines.push_back(currentdata);
452 currentdata.reset(i);
456 if (currentdata.text.size() > 0) {
457 currenty += usedheight;
458 if (text[text.size() - 1] == ' ') currentdata.text += " "; // TODO: HACK THINK ABOUT THIS
459 lines.push_back(currentdata);
462 pageheights.push_back(currenty);
465 void TextPart::partRender(Surface *renderer, int xoffset, int yoffset, TextEntryPart *caretdata) {
466 SpritePart::partRender(renderer, xoffset, yoffset);
468 unsigned int xoff = xoffset + x + leftmargin;
469 unsigned int yoff = yoffset + y + topmargin;
470 unsigned int textwidth = getWidth() - leftmargin - rightmargin;
471 unsigned int textheight = getHeight() - topmargin - bottommargin;
473 unsigned int currenty = 0, usedheight = 0;
474 if (vert_align == bottom)
475 currenty = textheight - pageheights[currpage];
476 else if (vert_align == middle)
477 currenty = (textheight - pageheights[currpage]) / 2;
478 unsigned int startline = pages[currpage];
479 unsigned int endline = (currpage + 1 < pages.size() ? pages[currpage + 1] : lines.size());
480 shared_ptr<creaturesImage> sprite = textsprite; unsigned int currtint = 0;
481 for (unsigned int i = startline; i < endline; i++) {
482 unsigned int currentx = 0, somex = xoff;
483 if (horz_align == rightalign)
484 somex = somex + (textwidth - lines[i].width);
485 else if (horz_align == centeralign)
486 somex = somex + ((textwidth - lines[i].width) / 2);
488 for (unsigned int x = 0; x < lines[i].text.size(); x++) {
489 if (currtint < tints.size() && tints[currtint].offset == lines[i].offset + x) {
490 sprite = tints[currtint].sprite;
491 currtint++;
494 if (lines[i].text[x] < 32) continue; // TODO: replace with space or similar?
495 int spriteid = lines[i].text[x] - 32;
496 renderer->render(sprite, spriteid, somex + currentx, yoff + currenty, has_alpha, alpha);
497 if ((caretdata) && (caretdata->caretpos == lines[i].offset + x))
498 caretdata->renderCaret(renderer, somex + currentx, yoff + currenty);
499 currentx += textsprite->width(spriteid) + charspacing;
501 if ((caretdata) && (caretdata->caretpos == lines[i].offset + lines[i].text.size()))
502 caretdata->renderCaret(renderer, somex + currentx, yoff + currenty);
503 currenty += textsprite->height(0) + linespacing + 1;
507 FixedTextPart::FixedTextPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
508 unsigned int _z, std::string fontsprite) : TextPart(p, _id, spritefile, fimg, _x, _y, _z, fontsprite) {
509 // nothing, hopefully.. :)
512 TextEntryPart::TextEntryPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
513 unsigned int _z, unsigned int msgid, std::string fontsprite) : TextPart(p, _id, spritefile, fimg, _x, _y, _z, fontsprite) {
514 // TODO: hm, this never gets freed..
515 if (!caretsprite) { caretsprite = world.gallery.getImage("cursor"); caos_assert(caretsprite); }
517 caretpose = 0;
518 caretpos = 0;
519 focused = false;
520 messageid = msgid;
523 void TextEntryPart::partRender(Surface *renderer, int xoffset, int yoffset) {
524 TextPart::partRender(renderer, xoffset, yoffset, (focused ? this : 0));
527 void TextEntryPart::renderCaret(Surface *renderer, int xoffset, int yoffset) {
528 // TODO: fudge xoffset/yoffset as required
529 renderer->render(caretsprite, caretpose, xoffset, yoffset, has_alpha, alpha);
532 void TextEntryPart::tick() {
533 SpritePart::tick();
535 if (focused) {
536 caretpose++;
537 if (caretpose == caretsprite->numframes())
538 caretpose = 0;
542 void SpritePart::tick() {
543 if (!animation.empty()) {
544 if (framerate > 1) {
545 framedelay++;
546 if (framedelay == (unsigned int)framerate + 1)
547 framedelay = 0;
550 if (framedelay == 0) {
551 unsigned int f = frameno + 1;
552 if (f == animation.size()) return;
553 if (animation[f] == 255) {
554 if (f == (animation.size() - 1)) f = 0;
555 else f = animation[f + 1];
557 // TODO: check f is valid..
558 setFrameNo(f);
563 CameraPart::CameraPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
564 unsigned int _z, unsigned int view_width, unsigned int view_height, unsigned int camera_width, unsigned int camera_height)
565 : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
566 viewwidth = view_width;
567 viewheight = view_height;
568 camerawidth = camera_width;
569 cameraheight = camera_height;
570 camera = shared_ptr<Camera>(new PartCamera(this));
573 void CameraPart::partRender(class Surface *renderer, int xoffset, int yoffset) {
574 // TODO: hack to stop us rendering cameras inside cameras. better way?
575 if (renderer == engine.backend->getMainSurface()) {
576 // make sure we're onscreen before bothering to do any work..
577 if (xoffset + x + viewwidth > 0 && yoffset + y + viewheight > 0 &&
578 xoffset + x < (int)renderer->getWidth() && yoffset + y < (int)renderer->getHeight()) {
579 Surface *surface = engine.backend->newSurface(viewwidth, viewheight);
580 assert(surface); // TODO: good behaviour?
581 world.drawWorld(camera.get(), surface);
582 renderer->blitSurface(surface, xoffset + x, yoffset + y, camerawidth, cameraheight);
583 engine.backend->freeSurface(surface);
587 SpritePart::partRender(renderer, xoffset, yoffset);
590 void CameraPart::tick() {
591 SpritePart::tick();
593 camera->tick();
596 GraphPart::GraphPart(Agent *p, unsigned int _id, std::string spritefile, unsigned int fimg, int _x, int _y,
597 unsigned int _z, unsigned int novalues) : SpritePart(p, _id, spritefile, fimg, _x, _y, _z) {
598 // TODO: store novalues
601 /* vim: set noet: */