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)
147 throw caosException(boost::str(boost::format("Failed to create sprite part: first sprite %d is beyond %d sprite(s) in file") % firstimg
% sprite
->numframes()));
152 SpritePart::~SpritePart() {
155 void SpritePart::changeSprite(std::string spritefile
, unsigned int fimg
) {
156 shared_ptr
<creaturesImage
> spr
= world
.gallery
.getImage(spritefile
);
158 caos_assert(spr
->numframes() > fimg
);
159 // TODO: should we preserve base/pose here, instead?
164 // TODO: should we preserve tint?
165 origsprite
= sprite
= spr
;
168 unsigned int SpritePart::getWidth() {
169 return sprite
->width(getCurrentSprite());
172 unsigned int SpritePart::getHeight() {
173 return sprite
->height(getCurrentSprite());
176 unsigned int CompoundPart::getZOrder() const {
177 return parent
->getZOrder() + zorder
;
180 void CompoundPart::zapZOrder() {
181 renderable::zapZOrder();
182 world
.zorder
.erase(zorder_iter
);
185 void CompoundPart::addZOrder() {
186 renderable::addZOrder();
187 zorder_iter
= world
.zorder
.insert(this);
190 void SpritePart::tint(unsigned char r
, unsigned char g
, unsigned char b
, unsigned char rotation
, unsigned char swap
) {
191 sprite
= origsprite
->mutableCopy();
192 sprite
->tint(r
, g
, b
, rotation
, swap
);
195 DullPart::DullPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
196 unsigned int _z
) : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
199 ButtonPart::ButtonPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
200 unsigned int _z
, const bytestring_t
&animhover
, int msgid
, int option
) : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
202 hitopaquepixelsonly
= (option
== 1);
203 hoveranimation
= animhover
;
206 unsigned int calculateScriptId(unsigned int message_id
); // from caosVM_agent.cpp, TODO: move into shared file
208 void ButtonPart::handleClick(float x
, float y
) {
209 caosVar v
; v
.setInt(id
); // _p1_ is id of part, according to Edynn code
210 parent
->queueScript(calculateScriptId(messageid
), (Agent
*)world
.hand(), v
);
213 TextPart::TextPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
, unsigned int _z
, std::string fontsprite
)
214 : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
215 textsprite
= world
.gallery
.getImage(fontsprite
);
216 caos_assert(textsprite
);
217 caos_assert(textsprite
->numframes() == 224);
218 leftmargin
= 8; topmargin
= 8; rightmargin
= 8; bottommargin
= 8;
219 linespacing
= 0; charspacing
= 0;
220 horz_align
= leftalign
; vert_align
= top
;
222 recalculateData(); // ie, insert a blank first page
225 TextPart::~TextPart() {
228 void TextPart::addTint(std::string tintinfo
) {
229 // add a tint, starting at text.size(), using the data in tintinfo
230 // TODO: there's some caching to be done here, but tinting is rather rare, so..
232 unsigned short r
= 128, g
= 128, b
= 128, rot
= 128, swap
= 128;
235 for (unsigned int i
= 0; i
<= tintinfo
.size(); i
++) {
236 if (i
== tintinfo
.size() || tintinfo
[i
] == ' ') {
237 unsigned short val
= atoi(cur
.c_str());
240 case 0: r
= val
; break;
241 case 1: g
= val
; break;
242 case 2: b
= val
; break;
243 case 3: rot
= val
; break;
244 case 4: swap
= val
; break;
246 } // TODO: else explode();
249 if (where
> 4) break;
250 } else cur
+= tintinfo
[i
];
254 t
.offset
= text
.size();
256 if (!(r
== g
== b
== rot
== swap
== 128)) {
257 t
.sprite
= textsprite
->mutableCopy();
258 t
.sprite
->tint(r
, g
, b
, rot
, swap
);
259 } else t
.sprite
= textsprite
;
264 void TextPart::setText(std::string t
) {
268 // parse and remove the <tint> tagging
269 for (unsigned int i
= 0; i
< t
.size(); i
++) {
270 if ((t
[i
] == '<') && (t
.size() > i
+4))
271 if ((t
[i
+ 1] == 't') && (t
[i
+ 2] == 'i') && (t
[i
+ 3] == 'n') && (t
[i
+ 4] == 't')) {
273 std::string tintinfo
;
274 if (t
[i
] == ' ') i
++; // skip initial space, if any
275 for (; i
< t
.size(); i
++) {
289 void TextEntryPart::setText(std::string t
) {
290 TextPart::setText(t
);
292 // place caret at the end of the text
293 caretpos
= text
.size();
296 unsigned int calculateScriptId(unsigned int message_id
); // from caosVM_agent.cpp, TODO: move into shared file
298 void TextEntryPart::handleClick(float clickx
, float clicky
) {
299 world
.setFocus(this);
302 void TextEntryPart::handleKey(char c
) {
303 text
.insert(caretpos
, 1, c
);
308 void TextEntryPart::handleSpecialKey(char c
) {
311 if (caretpos
== 0) return;
312 text
.erase(text
.begin() + (caretpos
- 1));
317 // TODO: check if we should do this or a newline
318 parent
->queueScript(calculateScriptId(messageid
), 0); // TODO: is a null FROM correct?
322 if (caretpos
== 0) return;
327 if (caretpos
== text
.size()) return;
336 if ((text
.size() == 0) || (caretpos
>= text
.size()))
338 text
.erase(text
.begin() + caretpos
);
345 assert(caretpos
<= text
.size());
350 void TextPart::setFormat(int left
, int top
, int right
, int bottom
, int line
, int _char
, horizontalalign horza
, verticalalign verta
, bool lastpagescroll
) {
354 bottommargin
= bottom
;
359 last_page_scroll
= lastpagescroll
;
363 unsigned int TextPart::calculateWordWidth(std::string word
) {
365 for (unsigned int i
= 0; i
< word
.size(); i
++) {
366 if (word
[i
] < 32) continue; // TODO: replace with space or similar?
367 int spriteid
= word
[i
] - 32;
369 x
+= textsprite
->width(spriteid
);
370 if (i
!= 0) x
+= charspacing
;
376 * Recalculate the data used for rendering the text part.
378 void TextPart::recalculateData() {
379 linedata currentdata
;
385 if (text
.size() == 0) {
386 pageheights
.push_back(0);
387 lines
.push_back(currentdata
); // blank line, so caret is rendered in TextEntryParts
391 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
392 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
394 unsigned int currenty
= 0, usedheight
= 0;
396 while (i
< text
.size()) {
397 bool newline
= false;
398 unsigned int startoffset
= i
;
399 // first, retrieve a word from the text
401 for (; i
< text
.size(); i
++) {
402 if ((text
[i
] == ' ') || (text
[i
] == '\n')) {
403 if (text
[i
] == '\n') newline
= true;
410 // next, work out whether it fits
411 unsigned int wordlen
= calculateWordWidth(word
);
412 unsigned int spacelen
= textsprite
->width(0) + charspacing
;
413 unsigned int possiblelen
= wordlen
;
414 if (currentdata
.text
.size() > 0)
415 possiblelen
= wordlen
+ spacelen
;
416 // TODO: set usedheight as appropriate/needed
417 usedheight
= textsprite
->height(0);
418 if (currentdata
.width
+ possiblelen
<= textwidth
) {
419 // the rest of the word fits on the current line, so that's okay.
420 // add a space if we're not the first word on this line
421 if (currentdata
.text
.size() > 0) word
= std::string(" ") + word
;
422 currentdata
.text
+= word
;
423 currentdata
.width
+= possiblelen
;
424 } else if (wordlen
<= textwidth
) {
425 // the rest of the word doesn't fit on the current line, but does on the next line.
426 if (currenty
+ usedheight
> textheight
) {
427 pageheights
.push_back(currenty
);
428 pages
.push_back(lines
.size());
430 } else currenty
+= usedheight
+ linespacing
+ 1;
431 currentdata
.text
+= " "; // TODO: HACK THINK ABOUT THIS
432 lines
.push_back(currentdata
);
433 currentdata
.reset(startoffset
);
435 currentdata
.text
+= word
;
436 currentdata
.width
+= wordlen
;
438 // TODO: word is too wide to fit on a single line
439 // we should output as much as possible and then go backwards
442 // we force a newline here if necessary (ie, if the last char is '\n', except not in the last case)
443 if ((i
< text
.size()) && (newline
)) {
444 if (currenty
+ usedheight
> textheight
) {
445 pageheights
.push_back(currenty
);
446 pages
.push_back(lines
.size());
448 } else currenty
+= usedheight
+ linespacing
+ 1;
449 lines
.push_back(currentdata
);
450 currentdata
.reset(i
);
454 if (currentdata
.text
.size() > 0) {
455 currenty
+= usedheight
;
456 if (text
[text
.size() - 1] == ' ') currentdata
.text
+= " "; // TODO: HACK THINK ABOUT THIS
457 lines
.push_back(currentdata
);
460 pageheights
.push_back(currenty
);
463 void TextPart::partRender(Surface
*renderer
, int xoffset
, int yoffset
, TextEntryPart
*caretdata
) {
464 SpritePart::partRender(renderer
, xoffset
, yoffset
);
466 unsigned int xoff
= xoffset
+ x
+ leftmargin
;
467 unsigned int yoff
= yoffset
+ y
+ topmargin
;
468 unsigned int textwidth
= getWidth() - leftmargin
- rightmargin
;
469 unsigned int textheight
= getHeight() - topmargin
- bottommargin
;
471 unsigned int currenty
= 0, usedheight
= 0;
472 if (vert_align
== bottom
)
473 currenty
= textheight
- pageheights
[currpage
];
474 else if (vert_align
== middle
)
475 currenty
= (textheight
- pageheights
[currpage
]) / 2;
476 unsigned int startline
= pages
[currpage
];
477 unsigned int endline
= (currpage
+ 1 < pages
.size() ? pages
[currpage
+ 1] : lines
.size());
478 shared_ptr
<creaturesImage
> sprite
= textsprite
; unsigned int currtint
= 0;
479 for (unsigned int i
= startline
; i
< endline
; i
++) {
480 unsigned int currentx
= 0, somex
= xoff
;
481 if (horz_align
== rightalign
)
482 somex
= somex
+ (textwidth
- lines
[i
].width
);
483 else if (horz_align
== centeralign
)
484 somex
= somex
+ ((textwidth
- lines
[i
].width
) / 2);
486 for (unsigned int x
= 0; x
< lines
[i
].text
.size(); x
++) {
487 if (currtint
< tints
.size() && tints
[currtint
].offset
== lines
[i
].offset
+ x
) {
488 sprite
= tints
[currtint
].sprite
;
492 if (lines
[i
].text
[x
] < 32) continue; // TODO: replace with space or similar?
493 int spriteid
= lines
[i
].text
[x
] - 32;
494 renderer
->render(sprite
, spriteid
, somex
+ currentx
, yoff
+ currenty
, has_alpha
, alpha
);
495 if ((caretdata
) && (caretdata
->caretpos
== lines
[i
].offset
+ x
))
496 caretdata
->renderCaret(renderer
, somex
+ currentx
, yoff
+ currenty
);
497 currentx
+= textsprite
->width(spriteid
) + charspacing
;
499 if ((caretdata
) && (caretdata
->caretpos
== lines
[i
].offset
+ lines
[i
].text
.size()))
500 caretdata
->renderCaret(renderer
, somex
+ currentx
, yoff
+ currenty
);
501 currenty
+= textsprite
->height(0) + linespacing
+ 1;
505 FixedTextPart::FixedTextPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
506 unsigned int _z
, std::string fontsprite
) : TextPart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
, fontsprite
) {
507 // nothing, hopefully.. :)
510 TextEntryPart::TextEntryPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
511 unsigned int _z
, unsigned int msgid
, std::string fontsprite
) : TextPart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
, fontsprite
) {
512 // TODO: hm, this never gets freed..
513 if (!caretsprite
) { caretsprite
= world
.gallery
.getImage("cursor"); caos_assert(caretsprite
); }
521 void TextEntryPart::partRender(Surface
*renderer
, int xoffset
, int yoffset
) {
522 TextPart::partRender(renderer
, xoffset
, yoffset
, (focused
? this : 0));
525 void TextEntryPart::renderCaret(Surface
*renderer
, int xoffset
, int yoffset
) {
526 // TODO: fudge xoffset/yoffset as required
527 renderer
->render(caretsprite
, caretpose
, xoffset
, yoffset
, has_alpha
, alpha
);
530 void TextEntryPart::tick() {
535 if (caretpose
== caretsprite
->numframes())
540 void SpritePart::tick() {
541 if (!animation
.empty()) {
544 if (framedelay
== (unsigned int)framerate
+ 1)
548 if (framedelay
== 0) {
549 unsigned int f
= frameno
+ 1;
550 if (f
== animation
.size()) return;
551 if (animation
[f
] == 255) {
552 if (f
== (animation
.size() - 1)) f
= 0;
553 else f
= animation
[f
+ 1];
555 // TODO: check f is valid..
561 CameraPart::CameraPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
562 unsigned int _z
, unsigned int view_width
, unsigned int view_height
, unsigned int camera_width
, unsigned int camera_height
)
563 : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
564 viewwidth
= view_width
;
565 viewheight
= view_height
;
566 camerawidth
= camera_width
;
567 cameraheight
= camera_height
;
568 camera
= shared_ptr
<Camera
>(new PartCamera(this));
571 void CameraPart::partRender(class Surface
*renderer
, int xoffset
, int yoffset
) {
572 // TODO: hack to stop us rendering cameras inside cameras. better way?
573 if (renderer
== engine
.backend
->getMainSurface()) {
574 // make sure we're onscreen before bothering to do any work..
575 if (xoffset
+ x
+ viewwidth
> 0 && yoffset
+ y
+ viewheight
> 0 &&
576 xoffset
+ x
< (int)renderer
->getWidth() && yoffset
+ y
< (int)renderer
->getHeight()) {
577 Surface
*surface
= engine
.backend
->newSurface(viewwidth
, viewheight
);
578 assert(surface
); // TODO: good behaviour?
579 world
.drawWorld(camera
.get(), surface
);
580 renderer
->blitSurface(surface
, xoffset
+ x
, yoffset
+ y
, camerawidth
, cameraheight
);
581 engine
.backend
->freeSurface(surface
);
585 SpritePart::partRender(renderer
, xoffset
, yoffset
);
588 void CameraPart::tick() {
594 GraphPart::GraphPart(Agent
*p
, unsigned int _id
, std::string spritefile
, unsigned int fimg
, int _x
, int _y
,
595 unsigned int _z
, unsigned int novalues
) : SpritePart(p
, _id
, spritefile
, fimg
, _x
, _y
, _z
) {
596 // TODO: store novalues