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"
25 #include "creaturesImage.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
;
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);
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);
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);
100 spriteno
= firstimg
+ base
+ pose
;
103 void SpritePart::setBase(unsigned int b
) {
107 bool SpritePart::transparentAt(unsigned int x
, unsigned int y
) {
108 return getSprite()->transparentAt(getCurrentSprite(), x
, y
);
111 int CompoundPart::handleClick(float clickx
, float clicky
) {
112 return 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
) {
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
);
136 is_transparent
= (engine
.version
> 2);
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)
146 } else if (engine
.bmprenderer
) {
147 // BLCK hasn't been called yet, so we can't check validity yet
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
);
160 caos_assert(spr
->numframes() > fimg
);
161 // TODO: should we preserve base/pose here, instead?
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
) {
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 int ButtonPart::handleClick(float x
, float y
) {
211 return calculateScriptId(messageid
);
214 TextPart::TextPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
, unsigned int _z
, std::string fontsprite
)
215 : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
216 textsprite
= world
.gallery
.getImage(fontsprite
);
217 caos_assert(textsprite
);
218 caos_assert(textsprite
->numframes() == 224);
219 leftmargin
= 8; topmargin
= 8; rightmargin
= 8; bottommargin
= 8;
220 linespacing
= 0; charspacing
= 0;
221 horz_align
= leftalign
; vert_align
= top
;
223 recalculateData(); // ie, insert a blank first page
226 TextPart::~TextPart() {
229 void TextPart::addTint(std::string tintinfo
) {
230 // add a tint, starting at text.size(), using the data in tintinfo
231 // TODO: there's some caching to be done here, but tinting is rather rare, so..
233 unsigned short r
= 128, g
= 128, b
= 128, rot
= 128, swap
= 128;
236 for (unsigned int i
= 0; i
<= tintinfo
.size(); i
++) {
237 if (i
== tintinfo
.size() || tintinfo
[i
] == ' ') {
238 unsigned short val
= atoi(cur
.c_str());
241 case 0: r
= val
; break;
242 case 1: g
= val
; break;
243 case 2: b
= val
; break;
244 case 3: rot
= val
; break;
245 case 4: swap
= val
; break;
247 } // TODO: else explode();
250 if (where
> 4) break;
251 } else cur
+= tintinfo
[i
];
255 t
.offset
= text
.size();
257 if (!(r
== g
== b
== rot
== swap
== 128)) {
258 t
.sprite
= textsprite
->mutableCopy();
259 t
.sprite
->tint(r
, g
, b
, rot
, swap
);
260 } else t
.sprite
= textsprite
;
265 void TextPart::setText(std::string t
) {
269 // parse and remove the <tint> tagging
270 for (unsigned int i
= 0; i
< t
.size(); i
++) {
271 if ((t
[i
] == '<') && (t
.size() > i
+4))
272 if ((t
[i
+ 1] == 't') && (t
[i
+ 2] == 'i') && (t
[i
+ 3] == 'n') && (t
[i
+ 4] == 't')) {
274 std::string tintinfo
;
275 if (t
[i
] == ' ') i
++; // skip initial space, if any
276 for (; i
< t
.size(); i
++) {
290 void TextEntryPart::setText(std::string t
) {
291 TextPart::setText(t
);
293 // place caret at the end of the text
294 caretpos
= text
.size();
297 unsigned int calculateScriptId(unsigned int message_id
); // from caosVM_agent.cpp, TODO: move into shared file
299 int TextEntryPart::handleClick(float clickx
, float clicky
) {
300 world
.setFocus(this);
302 return -1; // TODO: this shouldn't be passed onto the parent agent?
305 void TextEntryPart::handleKey(char c
) {
306 text
.insert(caretpos
, 1, c
);
311 void TextEntryPart::handleSpecialKey(char c
) {
314 if (caretpos
== 0) return;
315 text
.erase(text
.begin() + (caretpos
- 1));
320 // TODO: check if we should do this or a newline
321 parent
->queueScript(calculateScriptId(messageid
), 0); // TODO: is a null FROM correct?
325 if (caretpos
== 0) return;
330 if (caretpos
== text
.size()) return;
339 if ((text
.size() == 0) || (caretpos
>= text
.size()))
341 text
.erase(text
.begin() + caretpos
);
348 assert(caretpos
<= text
.size());
353 void TextPart::setFormat(int left
, int top
, int right
, int bottom
, int line
, int _char
, horizontalalign horza
, verticalalign verta
, bool lastpagescroll
) {
357 bottommargin
= bottom
;
362 last_page_scroll
= lastpagescroll
;
366 unsigned int TextPart::calculateWordWidth(std::string word
) {
368 for (unsigned int i
= 0; i
< word
.size(); i
++) {
369 if (word
[i
] < 32) continue; // TODO: replace with space or similar?
370 int spriteid
= word
[i
] - 32;
372 x
+= textsprite
->width(spriteid
);
373 if (i
!= 0) x
+= charspacing
;
379 * Recalculate the data used for rendering the text part.
381 void TextPart::recalculateData() {
382 linedata currentdata
;
388 if (text
.size() == 0) {
389 pageheights
.push_back(0);
390 lines
.push_back(currentdata
); // blank line, so caret is rendered in TextEntryParts
394 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
395 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
397 unsigned int currenty
= 0, usedheight
= 0;
399 while (i
< text
.size()) {
400 bool newline
= false;
401 unsigned int startoffset
= i
;
402 // first, retrieve a word from the text
404 for (; i
< text
.size(); i
++) {
405 if ((text
[i
] == ' ') || (text
[i
] == '\n')) {
406 if (text
[i
] == '\n') newline
= true;
413 // next, work out whether it fits
414 unsigned int wordlen
= calculateWordWidth(word
);
415 unsigned int spacelen
= textsprite
->width(0) + charspacing
;
416 unsigned int possiblelen
= wordlen
;
417 if (currentdata
.text
.size() > 0)
418 possiblelen
= wordlen
+ spacelen
;
419 // TODO: set usedheight as appropriate/needed
420 usedheight
= textsprite
->height(0);
421 if (currentdata
.width
+ possiblelen
<= textwidth
) {
422 // the rest of the word fits on the current line, so that's okay.
423 // add a space if we're not the first word on this line
424 if (currentdata
.text
.size() > 0) word
= std::string(" ") + word
;
425 currentdata
.text
+= word
;
426 currentdata
.width
+= possiblelen
;
427 } else if (wordlen
<= textwidth
) {
428 // the rest of the word doesn't fit on the current line, but does on the next line.
429 if (currenty
+ usedheight
> textheight
) {
430 pageheights
.push_back(currenty
);
431 pages
.push_back(lines
.size());
433 } else currenty
+= usedheight
+ linespacing
+ 1;
434 currentdata
.text
+= " "; // TODO: HACK THINK ABOUT THIS
435 lines
.push_back(currentdata
);
436 currentdata
.reset(startoffset
);
438 currentdata
.text
+= word
;
439 currentdata
.width
+= wordlen
;
441 // TODO: word is too wide to fit on a single line
442 // we should output as much as possible and then go backwards
445 // we force a newline here if necessary (ie, if the last char is '\n', except not in the last case)
446 if ((i
< text
.size()) && (newline
)) {
447 if (currenty
+ usedheight
> textheight
) {
448 pageheights
.push_back(currenty
);
449 pages
.push_back(lines
.size());
451 } else currenty
+= usedheight
+ linespacing
+ 1;
452 lines
.push_back(currentdata
);
453 currentdata
.reset(i
);
457 if (currentdata
.text
.size() > 0) {
458 currenty
+= usedheight
;
459 if (text
[text
.size() - 1] == ' ') currentdata
.text
+= " "; // TODO: HACK THINK ABOUT THIS
460 lines
.push_back(currentdata
);
463 pageheights
.push_back(currenty
);
466 void TextPart::partRender(Surface
*renderer
, int xoffset
, int yoffset
, TextEntryPart
*caretdata
) {
467 SpritePart::partRender(renderer
, xoffset
, yoffset
);
469 unsigned int xoff
= xoffset
+ x
+ leftmargin
;
470 unsigned int yoff
= yoffset
+ y
+ topmargin
;
471 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
472 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
474 unsigned int currenty
= 0, usedheight
= 0;
475 if (vert_align
== bottom
)
476 currenty
= textheight
- pageheights
[currpage
];
477 else if (vert_align
== middle
)
478 currenty
= (textheight
- pageheights
[currpage
]) / 2;
479 unsigned int startline
= pages
[currpage
];
480 unsigned int endline
= (currpage
+ 1 < pages
.size() ? pages
[currpage
+ 1] : lines
.size());
481 shared_ptr
<creaturesImage
> sprite
= textsprite
; unsigned int currtint
= 0;
482 for (unsigned int i
= startline
; i
< endline
; i
++) {
483 unsigned int currentx
= 0, somex
= xoff
;
484 if (horz_align
== rightalign
)
485 somex
= somex
+ (textwidth
- lines
[i
].width
);
486 else if (horz_align
== centeralign
)
487 somex
= somex
+ ((textwidth
- lines
[i
].width
) / 2);
489 for (unsigned int x
= 0; x
< lines
[i
].text
.size(); x
++) {
490 if (currtint
< tints
.size() && tints
[currtint
].offset
== lines
[i
].offset
+ x
) {
491 sprite
= tints
[currtint
].sprite
;
495 if (lines
[i
].text
[x
] < 32) continue; // TODO: replace with space or similar?
496 int spriteid
= lines
[i
].text
[x
] - 32;
497 renderer
->render(sprite
, spriteid
, somex
+ currentx
, yoff
+ currenty
, has_alpha
, alpha
);
498 if ((caretdata
) && (caretdata
->caretpos
== lines
[i
].offset
+ x
))
499 caretdata
->renderCaret(renderer
, somex
+ currentx
, yoff
+ currenty
);
500 currentx
+= textsprite
->width(spriteid
) + charspacing
;
502 if ((caretdata
) && (caretdata
->caretpos
== lines
[i
].offset
+ lines
[i
].text
.size()))
503 caretdata
->renderCaret(renderer
, somex
+ currentx
, yoff
+ currenty
);
504 currenty
+= textsprite
->height(0) + linespacing
+ 1;
508 FixedTextPart::FixedTextPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
509 unsigned int _z
, std::string fontsprite
) : TextPart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
, fontsprite
) {
510 // nothing, hopefully.. :)
513 TextEntryPart::TextEntryPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
514 unsigned int _z
, unsigned int msgid
, std::string fontsprite
) : TextPart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
, fontsprite
) {
515 // TODO: hm, this never gets freed..
516 if (!caretsprite
) { caretsprite
= world
.gallery
.getImage("cursor"); caos_assert(caretsprite
); }
524 void TextEntryPart::partRender(Surface
*renderer
, int xoffset
, int yoffset
) {
525 TextPart::partRender(renderer
, xoffset
, yoffset
, (focused
? this : 0));
528 void TextEntryPart::renderCaret(Surface
*renderer
, int xoffset
, int yoffset
) {
529 // TODO: fudge xoffset/yoffset as required
530 renderer
->render(caretsprite
, caretpose
, xoffset
, yoffset
, has_alpha
, alpha
);
533 void TextEntryPart::tick() {
538 if (caretpose
== caretsprite
->numframes())
543 void SpritePart::tick() {
544 if (!animation
.empty()) {
547 if (framedelay
== (unsigned int)framerate
+ 1)
551 if (framedelay
== 0) {
552 unsigned int f
= frameno
+ 1;
553 if (f
== animation
.size()) return;
554 if (animation
[f
] == 255) {
555 if (f
== (animation
.size() - 1)) f
= 0;
556 else f
= animation
[f
+ 1];
558 // TODO: check f is valid..
564 CameraPart::CameraPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
565 unsigned int _z
, unsigned int view_width
, unsigned int view_height
, unsigned int camera_width
, unsigned int camera_height
)
566 : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
567 viewwidth
= view_width
;
568 viewheight
= view_height
;
569 camerawidth
= camera_width
;
570 cameraheight
= camera_height
;
571 camera
= shared_ptr
<Camera
>(new PartCamera(this));
574 void CameraPart::partRender(class Surface
*renderer
, int xoffset
, int yoffset
) {
575 // TODO: hack to stop us rendering cameras inside cameras. better way?
576 if (renderer
== engine
.backend
->getMainSurface()) {
577 // make sure we're onscreen before bothering to do any work..
578 if (xoffset
+ x
+ viewwidth
> 0 && yoffset
+ y
+ viewheight
> 0 &&
579 xoffset
+ x
< (int)renderer
->getWidth() && yoffset
+ y
< (int)renderer
->getHeight()) {
580 Surface
*surface
= engine
.backend
->newSurface(viewwidth
, viewheight
);
581 assert(surface
); // TODO: good behaviour?
582 world
.drawWorld(camera
.get(), surface
);
583 renderer
->blitSurface(surface
, xoffset
+ x
, yoffset
+ y
, camerawidth
, cameraheight
);
584 engine
.backend
->freeSurface(surface
);
588 SpritePart::partRender(renderer
, xoffset
, yoffset
);
591 void CameraPart::tick() {
597 GraphPart::GraphPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
598 unsigned int _z
, unsigned int novalues
) : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
599 // TODO: store novalues