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 void CompoundPart::gainFocus() {
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
) {
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
);
152 is_transparent
= (engine
.version
> 2);
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)
162 } else if (engine
.bmprenderer
) {
163 // BLCK hasn't been called yet, so we can't check validity yet
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
);
176 caos_assert(spr
->numframes() > fimg
);
177 // TODO: should we preserve base/pose here, instead?
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
) {
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
;
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;
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());
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();
266 if (where
> 4) break;
267 } else cur
+= tintinfo
[i
];
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
;
281 void TextPart::setText(std::string t
) {
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')) {
290 std::string tintinfo
;
291 if (t
[i
] == ' ') i
++; // skip initial space, if any
292 for (; i
< t
.size(); i
++) {
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
);
327 void TextEntryPart::handleSpecialKey(char c
) {
330 if (caretpos
== 0) return;
331 text
.erase(text
.begin() + (caretpos
- 1));
336 // TODO: check if we should do this or a newline
337 parent
->queueScript(calculateScriptId(messageid
), 0); // TODO: is a null FROM correct?
341 if (caretpos
== 0) return;
346 if (caretpos
== text
.size()) return;
355 if ((text
.size() == 0) || (caretpos
>= text
.size()))
357 text
.erase(text
.begin() + caretpos
);
364 assert(caretpos
<= text
.size());
369 void TextPart::setFormat(int left
, int top
, int right
, int bottom
, int line
, int _char
, horizontalalign horza
, verticalalign verta
, bool lastpagescroll
) {
373 bottommargin
= bottom
;
378 last_page_scroll
= lastpagescroll
;
382 unsigned int TextPart::calculateWordWidth(std::string word
) {
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
;
395 * Recalculate the data used for rendering the text part.
397 void TextPart::recalculateData() {
398 linedata currentdata
;
404 if (text
.size() == 0) {
405 pageheights
.push_back(0);
406 lines
.push_back(currentdata
); // blank line, so caret is rendered in TextEntryParts
410 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
411 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
413 unsigned int currenty
= 0, usedheight
= 0;
415 while (i
< text
.size()) {
416 bool newline
= false;
417 unsigned int startoffset
= i
;
418 // first, retrieve a word from the text
420 for (; i
< text
.size(); i
++) {
421 if ((text
[i
] == ' ') || (text
[i
] == '\n')) {
422 if (text
[i
] == '\n') newline
= true;
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());
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
;
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());
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
;
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
); }
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() {
554 if (caretpose
== caretsprite
->numframes())
559 void SpritePart::tick() {
560 if (!animation
.empty()) {
563 if (framedelay
== (unsigned int)framerate
+ 1)
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..
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() {
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