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::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
) {
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 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
;
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;
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());
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();
251 if (where
> 4) break;
252 } else cur
+= tintinfo
[i
];
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
;
266 void TextPart::setText(std::string t
) {
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')) {
275 std::string tintinfo
;
276 if (t
[i
] == ' ') i
++; // skip initial space, if any
277 for (; i
< t
.size(); i
++) {
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
);
310 void TextEntryPart::handleSpecialKey(char c
) {
313 if (caretpos
== 0) return;
314 text
.erase(text
.begin() + (caretpos
- 1));
319 // TODO: check if we should do this or a newline
320 parent
->queueScript(calculateScriptId(messageid
), 0); // TODO: is a null FROM correct?
324 if (caretpos
== 0) return;
329 if (caretpos
== text
.size()) return;
338 if ((text
.size() == 0) || (caretpos
>= text
.size()))
340 text
.erase(text
.begin() + caretpos
);
347 assert(caretpos
<= text
.size());
352 void TextPart::setFormat(int left
, int top
, int right
, int bottom
, int line
, int _char
, horizontalalign horza
, verticalalign verta
, bool lastpagescroll
) {
356 bottommargin
= bottom
;
361 last_page_scroll
= lastpagescroll
;
365 unsigned int TextPart::calculateWordWidth(std::string word
) {
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
;
378 * Recalculate the data used for rendering the text part.
380 void TextPart::recalculateData() {
381 linedata currentdata
;
387 if (text
.size() == 0) {
388 pageheights
.push_back(0);
389 lines
.push_back(currentdata
); // blank line, so caret is rendered in TextEntryParts
393 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
394 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
396 unsigned int currenty
= 0, usedheight
= 0;
398 while (i
< text
.size()) {
399 bool newline
= false;
400 unsigned int startoffset
= i
;
401 // first, retrieve a word from the text
403 for (; i
< text
.size(); i
++) {
404 if ((text
[i
] == ' ') || (text
[i
] == '\n')) {
405 if (text
[i
] == '\n') newline
= true;
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());
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
;
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());
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
;
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
); }
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() {
537 if (caretpose
== caretsprite
->numframes())
542 void SpritePart::tick() {
543 if (!animation
.empty()) {
546 if (framedelay
== (unsigned int)framerate
+ 1)
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..
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() {
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