conversation.cc

Go to the documentation of this file.
00001 /*
00002  *  Copyright (C) 2001-2002  The Exult Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 #ifdef HAVE_CONFIG_H
00020 #  include <config.h>
00021 #endif
00022 
00023 #include "actors.h"
00024 #include "conversation.h"
00025 #include "exult.h"
00026 #include "game.h"
00027 #include "gamewin.h"
00028 #include "mouse.h"
00029 #include "useval.h"
00030 #include "data/exult_bg_flx.h"
00031 
00032 #ifndef UNDER_CE
00033 using std::cout;
00034 using std::endl;
00035 using std::size_t;
00036 using std::strcpy;
00037 using std::string;
00038 using std::vector;
00039 #endif
00040 
00041 //TODO: show_face & show_avatar_choices seem to share code?
00042 //TODO: show_avatar_choices shouldn't first convert to char**, probably
00043 
00044 /*
00045  *  Store information about an NPC's face and text on the screen during
00046  *  a conversation:
00047  */
00048 class Npc_face_info {
00049  public:
00050   ShapeID shape;
00051   int face_num;     // NPC's face shape #.
00052   //int frame;
00053   bool text_pending;  // Text has been written, but user
00054   //   has not yet been prompted.
00055   Rectangle face_rect;    // Rectangle where face is shown.
00056   Rectangle text_rect;    // Rectangle NPC statement is shown in.
00057   int last_text_height;   // Height of last text painted.
00058   string cur_text;    // Current text being shown.
00059   Npc_face_info(ShapeID &sid, int num) : shape(sid), face_num(num), text_pending(0)
00060   {  }
00061 };
00062 
00063 Conversation::Conversation() :
00064   num_faces(0), last_face_shown(0), conv_choices(0), avatar_face(0,0,0,0)
00065 {
00066 
00067   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00068   for (int i = 0; i < max_faces; i++)
00069     face_info[i] = 0;
00070 }
00071 
00072 Conversation::~Conversation()
00073 {
00074   delete [] conv_choices;
00075 }
00076 
00077 
00078 void Conversation::clear_answers(void)
00079 {
00080   answers.clear();
00081 }
00082 
00083 void Conversation::add_answer(const char *str)
00084 {
00085   remove_answer(str);
00086   string s(str);
00087   answers.push_back(s);
00088 }
00089 
00090 /*
00091  *  Add an answer to the list.
00092  */
00093 
00094 void Conversation::add_answer(Usecode_value& val)
00095 {
00096   const char *str;
00097   int size = val.get_array_size();
00098   if (size)     // An array?
00099     {
00100     for (int i = 0; i < size; i++)
00101       add_answer(val.get_elem(i));
00102     }
00103   else if ((str = val.get_str_value()) != 0)
00104     add_answer(str);
00105 }
00106 
00107 void Conversation::remove_answer(const char *str)
00108 {
00109   std::vector<string>::iterator it;
00110 
00111   for(it=answers.begin(); it!=answers.end(); ++it)
00112     if(*it==str)
00113       break;
00114 
00115   if(it!=answers.end())
00116     answers.erase(it);
00117 }
00118 
00119 /*
00120  *  Remove an answer from the list.
00121  */
00122 
00123 void Conversation::remove_answer(Usecode_value& val)
00124 {
00125   const char *str;
00126   if (val.is_array()) {
00127     int size = val.get_array_size();
00128     for (int i=0; i < size; i++) {
00129       str = val.get_elem(i).get_str_value();
00130       if (str) remove_answer(str);
00131     }
00132   } else {
00133     str = val.get_str_value();
00134     remove_answer(str);
00135   }
00136 }
00137 
00138 /*
00139  *  Initialize face list.
00140  */
00141 
00142 void Conversation::init_faces()
00143 {
00144   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00145   for (int i = 0; i < max_faces; i++)
00146     {
00147     if( face_info[i] )
00148       delete face_info[i];
00149     face_info[i] = 0;
00150     }
00151   num_faces = 0;
00152   last_face_shown = -1;
00153 }
00154 
00155 /*
00156  *  Get face frame in Serpent Isle.
00157  */
00158 
00159 static int SI_get_frame
00160   (
00161   Actor *main_actor
00162   )
00163   {
00164   int frame;
00165   if (main_actor->get_skin_color() == 0) // WH
00166   {
00167     frame = 1 - main_actor->get_type_flag(Actor::tf_sex);
00168   }
00169   else if (main_actor->get_skin_color() == 1) // BN
00170   {
00171     frame = 3 - main_actor->get_type_flag(Actor::tf_sex);
00172   }
00173   else if (main_actor->get_skin_color() == 2) // BK
00174   {
00175     frame = 5 - main_actor->get_type_flag(Actor::tf_sex);
00176   }
00177   else // None
00178   {
00179     frame = main_actor->get_type_flag(Actor::tf_sex);
00180   }
00181   return frame;
00182   }
00183 
00184 
00185 /*
00186  *  Show a "face" on the screen.  Npc_text_rect is also set.
00187  *  If shape < 0, an empty space is shown.
00188  */
00189 
00190 void Conversation::show_face(int shape, int frame, int slot)
00191 {
00192   ShapeID face_sid(shape, frame, SF_FACES_VGA);
00193 
00194         bool SI = Game::get_game_type()==SERPENT_ISLE;
00195   Main_actor* main_actor = gwin->get_main_actor();
00196   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00197 
00198   // Make sure mode is set right.
00199   Palette *pal = gwin->get_pal(); // Watch for wierdness (lightning).
00200   if (pal->get_brightness() >= 300)
00201     pal->set(-1, 100);
00202 
00203   if (SI)       // Serpent Isle?
00204   {       // Petra?  ???+++Is this right?
00205     if (shape == 28 && main_actor->get_flag(Obj_flags::petra))
00206     {
00207       shape = main_actor->get_face_shapenum();
00208       face_sid.set_shape(shape);
00209     }
00210     if (shape == 0)   // In any case, get correct frame.
00211     {
00212       frame = SI_get_frame(main_actor);
00213       if (main_actor->get_flag(Obj_flags::tattooed))
00214         shape = 299;
00215 
00216       face_sid.set_shape(shape, frame);
00217     }
00218   }
00219   // BG Multiracial Hack
00220   else if (shape == 0)
00221   {
00222     if (main_actor->get_skin_color() != 3)
00223     {
00224       shape = EXULT_BG_FLX_MR_FACES_SHP;
00225       frame = SI_get_frame(main_actor);
00226       face_sid.set_file(SF_GAME_FLX);
00227       face_sid.set_shape(shape, frame);
00228     }
00229   }
00230           // Get screen dims.
00231   int screenw = gwin->get_width(), screenh = gwin->get_height();
00232   Npc_face_info *info = 0;
00233           // See if already on screen.
00234   for (int i = 0; i < max_faces; i++)
00235     if (face_info[i] && face_info[i]->face_num == shape)
00236       {
00237       info = face_info[i];
00238       last_face_shown = i;
00239       break;
00240       }
00241   if (!info)      // New one?
00242     {
00243     if (num_faces == max_faces)
00244           // None free?  Steal last one.
00245       remove_slot_face(max_faces - 1);
00246     info = new Npc_face_info(face_sid, shape);
00247     if (slot == -1)   // Want next one?
00248       slot = num_faces;
00249           // Get last one shown.
00250     Npc_face_info *prev = slot ? face_info[slot - 1] : 0;
00251     last_face_shown = slot;
00252     if (!face_info[slot])
00253       num_faces++;  // We're adding one (not replacing).
00254     else
00255       delete face_info[slot];
00256     face_info[slot] = info;
00257           // Get text height.
00258     int text_height = sman->get_text_height(0);
00259           // Figure starting y-coord.
00260           // Get character's portrait.
00261     Shape_frame *face = shape >= 0 ? face_sid.get_shape() : 0;
00262     int face_w = 32, face_h = 32;
00263     if (face)
00264       {
00265       face_w = face->get_width(); 
00266       face_h = face->get_height();
00267       }
00268     int starty;
00269     if (prev)
00270       {
00271       starty = prev->text_rect.y + prev->last_text_height;
00272       if (starty < prev->face_rect.y + prev->face_rect.h)
00273         starty = prev->face_rect.y + prev->face_rect.h;
00274       starty += 2*text_height;
00275       if (starty + face_h > screenh - 1)
00276         starty = screenh - face_h - 1;
00277       }
00278     else
00279       starty = 1;
00280     info->face_rect = gwin->clip_to_win(Rectangle(8, starty,
00281       face_w + 4, face_h + 4));
00282     Rectangle& fbox = info->face_rect;
00283           // This is where NPC text will go.
00284     info->text_rect = gwin->clip_to_win(Rectangle(
00285       fbox.x + fbox.w + 3, fbox.y + 3,
00286       screenw - fbox.x - fbox.w - 6, 4*text_height));
00287           // No room?  (Serpent?)
00288     if (info->text_rect.w < 16 || info->text_rect.h < 16)
00289           // Show in lower center.
00290       info->text_rect = Rectangle(screenw/4, screenh/2,
00291             screenw/2, screenh/4);
00292     info->last_text_height = info->text_rect.h;
00293     }
00294   gwin->get_win()->set_clip(0, 0, screenw, screenh);
00295   paint_faces();      // Paint all faces.
00296   gwin->get_win()->clear_clip();
00297   }
00298 
00299 /*
00300  *  Remove face from screen.
00301  */
00302 
00303 void Conversation::remove_face(int shape)
00304 {
00305   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00306   int i;        // See if already on screen.
00307   for (i = 0; i < max_faces; i++)
00308     if (face_info[i] && face_info[i]->face_num == shape)
00309       break;
00310   if (i == max_faces)
00311     return;     // Not found.
00312   remove_slot_face(i);
00313 }
00314 
00315 /*
00316  *  Remove face from indicated slot (SI).
00317  */
00318 
00319 void Conversation::remove_slot_face
00320   (
00321   int slot
00322   )
00323   {
00324   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00325   if (slot >= max_faces || !face_info[slot])
00326     return;     // Invalid.
00327   Npc_face_info *info = face_info[slot];
00328           // These are needed in case conversa-
00329           //   tion is done.
00330   gwin->add_dirty(info->face_rect);
00331   gwin->add_dirty(info->text_rect);
00332   delete face_info[slot];
00333   face_info[slot] = 0;
00334   num_faces--;
00335   if (last_face_shown == slot)  // Just in case.
00336     {
00337     int j;
00338     for (j = max_faces - 1; j >= 0; j--)
00339       if (face_info[j])
00340         break;
00341     last_face_shown = j;
00342     }
00343   }
00344 
00345 
00346 /*
00347  *  Show what the NPC had to say.
00348  */
00349 
00350 void Conversation::show_npc_message(const char *msg)
00351 {
00352   if (last_face_shown == -1)
00353     return;
00354   Npc_face_info *info = face_info[last_face_shown];
00355   info->cur_text = "";
00356   Rectangle& box = info->text_rect;
00357 //  gwin->paint(box);   // Clear what was there before.
00358 //  paint_faces();
00359   gwin->paint();
00360   int height;     // Break at punctuation.
00361   while ((height = sman->paint_text_box(0, msg, box.x,box.y,box.w,box.h, 
00362                 -1, 1, gwin->get_text_bg())) < 0)
00363     {     // More to do?
00364     info->cur_text = string(msg, -height);
00365     int x, y; char c;
00366     gwin->paint();    // Paint scenery beneath.
00367     Get_click(x, y, Mouse::hand, &c, false, this);
00368 //    gwin->paint(box); // Clear area again.
00369     gwin->paint();
00370     msg += -height;
00371     }
00372           // All fit?  Store height painted.
00373   info->last_text_height = height;
00374   info->cur_text = msg;
00375   info->text_pending = 1;
00376   gwin->set_painted();
00377 //  gwin->show();
00378 }
00379 
00380 
00381 /*
00382  *  Is there NPC text that the user hasn't had a chance to read?
00383  */
00384 
00385 bool Conversation::is_npc_text_pending()
00386 {
00387   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00388   for (int i = 0; i < max_faces; i++)
00389     if (face_info[i] && face_info[i]->text_pending)
00390       return true;
00391   return false;
00392 }
00393 
00394 /*
00395  *  Clear text-pending flags.
00396  */
00397 
00398 void Conversation::clear_text_pending()
00399 {
00400   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00401   for (int i = 0; i < max_faces; i++) // Clear 'pending' flags.
00402     if (face_info[i])
00403       face_info[i]->text_pending = 0;
00404 }
00405 
00406 /*
00407  *  Show the Avatar's conversation choices (and face).
00408  */
00409 
00410 void Conversation::show_avatar_choices(int num_choices, char **choices)
00411 {
00412        bool SI = Game::get_game_type()==SERPENT_ISLE;
00413   Main_actor *main_actor = gwin->get_main_actor();
00414   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00415           // Get screen rectangle.
00416   Rectangle sbox = gwin->get_win_rect();
00417   int x = 0, y = 0;   // Keep track of coords. in box.
00418   int height = sman->get_text_height(0);
00419   int space_width = sman->get_text_width(0, " ");
00420 
00421 
00422           // Get main actor's portrait.
00423   int shape = main_actor->get_face_shapenum();
00424   int frame;
00425   ShapeID face_sid(shape, 0, SF_FACES_VGA);
00426 
00427  
00428   if (SI && (main_actor->get_flag(Obj_flags::petra) || (main_actor->get_skin_color() == 3 && main_actor->get_type_flag(Actor::tf_sex)))) // Petra
00429   {
00430     shape = 28;
00431     frame = 0;
00432     face_sid.set_shape(shape, frame);
00433   }
00434   else if (SI && main_actor->get_skin_color() == 3) // Automaton
00435   {
00436     shape = 298;
00437     frame = 0;
00438     face_sid.set_shape(shape, frame);
00439   }
00440   else if (SI)
00441   {
00442     frame = SI_get_frame(main_actor);
00443     if (shape == 0 && main_actor->get_flag(Obj_flags::tattooed))
00444     {
00445       shape = 299;
00446       face_sid.set_shape(shape);
00447     }
00448 
00449   }
00450   else
00451   {
00452     // BG Multiracial Hack
00453     if (main_actor->get_skin_color() >= 0 && main_actor->get_skin_color() <= 2 && GAME_BG)
00454     {
00455       shape = EXULT_BG_FLX_MR_FACES_SHP;
00456       frame = SI_get_frame(main_actor);
00457       face_sid.set_file(SF_GAME_FLX);
00458       face_sid.set_shape(shape, frame);
00459     }
00460     else
00461       frame = main_actor->get_type_flag(Actor::tf_sex);
00462   }
00463 
00464 
00465   face_sid.set_frame(frame);
00466 
00467   Shape_frame *face = face_sid.get_shape();
00468   int empty;      // Find face prev. to 1st empty slot.
00469   for (empty = 0; empty < max_faces; empty++)
00470     if (!face_info[empty])
00471       break;
00472           // Get last one shown.
00473   Npc_face_info *prev = empty ? face_info[empty - 1] : 0;
00474   int fx = prev ? prev->face_rect.x + prev->face_rect.w + 4 : 16;
00475   int fy;
00476   if (SI)
00477   {
00478     if (num_faces == max_faces)
00479           // Remove face #1 if still there.
00480       remove_slot_face(max_faces - 1);
00481     fy = sbox.h - 2 - face->get_height();
00482     fx = 8;
00483   }
00484   else if (!prev)
00485     fy = sbox.h - face->get_height() - 3*height;
00486   else
00487     {
00488     fy = prev->text_rect.y + prev->last_text_height;
00489     if (fy < prev->face_rect.y + prev->face_rect.h)
00490       fy = prev->face_rect.y + prev->face_rect.h;
00491     fy += height;
00492     }
00493   Rectangle mbox(fx, fy, face->get_width(), face->get_height());
00494   mbox = mbox.intersect(sbox);
00495   avatar_face = mbox;   // Repaint entire width.
00496           // Set to where to draw sentences.
00497   Rectangle tbox(mbox.x + mbox.w + 8, mbox.y + 4,
00498         sbox.w - mbox.x - mbox.w - 16,
00499 //        sbox.h - mbox.y - 16);
00500         5*height);// Try 5 lines.
00501   tbox = tbox.intersect(sbox);
00502 //  gwin->paint(tbox);              // Paint background.
00503           // Draw portrait.
00504   sman->paint_shape(mbox.x + face->get_xleft(), 
00505         mbox.y + face->get_yabove(), face);
00506   delete [] conv_choices;   // Set up new list of choices.
00507   conv_choices = new Rectangle[num_choices + 1];
00508   for (int i = 0; i < num_choices; i++)
00509     {
00510     char text[256];
00511     text[0] = 127;    // A circle.
00512     strcpy(&text[1], choices[i]);
00513     int width = sman->get_text_width(0, text);
00514     if (x > 0 && x + width >= tbox.w)
00515       {   // Start a new line.
00516       x = 0;
00517       y += height - 1;
00518       }
00519           // Store info.
00520     conv_choices[i] = Rectangle(tbox.x + x, tbox.y + y,
00521           width, height);
00522     conv_choices[i] = conv_choices[i].intersect(sbox);
00523     avatar_face = avatar_face.add(conv_choices[i]);
00524     sman->paint_text_box(0, text, tbox.x + x, tbox.y + y,
00525       width + space_width, height, 0, 0, gwin->get_text_bg());
00526     x += width + space_width;
00527     }
00528   avatar_face.enlarge(6);   // Encloses entire area.
00529   avatar_face = avatar_face.intersect(sbox);
00530           // Terminate the list.
00531   conv_choices[num_choices] = Rectangle(0, 0, 0, 0);
00532   clear_text_pending();
00533   gwin->set_painted();
00534 }
00535 
00536 void Conversation::show_avatar_choices()
00537 {
00538   char  **result;
00539   size_t i; // Blame MSVC
00540 
00541   result=new char *[answers.size()];
00542   for(i=0;i<answers.size();i++)
00543     {
00544     result[i]=new char[answers[i].size()+1];
00545     strcpy(result[i],answers[i].c_str());
00546     }
00547   show_avatar_choices(answers.size(),result);
00548   for(i=0;i<answers.size();i++)
00549     {
00550     delete [] result[i];
00551     }
00552   delete [] result;
00553   }
00554 
00555 void Conversation::clear_avatar_choices()
00556 {
00557 //  gwin->paint(avatar_face); // Paint over face and answers.
00558   gwin->add_dirty(avatar_face);
00559   avatar_face.w = 0;
00560 }
00561 
00562 
00563 /*
00564  *  User clicked during a conversation.
00565  *
00566  *  Output: Index (0-n) of choice, or -1 if not on a choice.
00567  */
00568 
00569 int Conversation::conversation_choice(int x, int y)
00570 {
00571   int i;
00572   for (i = 0; conv_choices[i].w != 0 &&
00573       !conv_choices[i].has_point(x, y); i++)
00574     ;
00575   if (conv_choices[i].w != 0) // Found one?
00576     return (i);
00577   else
00578     return (-1);
00579 }
00580 
00581 /*
00582  *  Repaint everything.
00583  */
00584 
00585 void Conversation::paint
00586   (
00587   )
00588   {
00589   paint_faces(true);
00590   if (avatar_face.w)    // Choices?
00591     show_avatar_choices();
00592   }
00593 
00594 /*
00595  *  Repaint the faces.   Assumes clip has already been set to screen.
00596  */
00597 
00598 void Conversation::paint_faces
00599   (
00600   bool text     // Show text too.
00601   )
00602   {
00603   if (!num_faces)
00604     return;
00605   const int max_faces = sizeof(face_info)/sizeof(face_info[0]);
00606   for (int i = 0; i < max_faces; i++)
00607     {
00608     Npc_face_info *finfo = face_info[i];
00609     if (!finfo)
00610       continue;
00611     Shape_frame *face = finfo->face_num >= 0 ? 
00612         finfo->shape.get_shape() : 0;
00613     int face_xleft = 0, face_yabove = 0;
00614     if (face)
00615       {
00616       face_xleft = face->get_xleft();
00617       face_yabove = face->get_yabove();
00618           // Use translucency.
00619       sman->paint_shape(
00620         finfo->face_rect.x + face_xleft,
00621         finfo->face_rect.y + face_yabove, face, 1);
00622       }
00623     if (text)   // Show text too?
00624       {
00625       Rectangle& box = finfo->text_rect;
00626       sman->paint_text_box(0, finfo->cur_text.c_str(), 
00627         box.x,box.y,box.w,box.h, -1, 1, 
00628               gwin->get_text_bg());
00629       }
00630     }
00631   }
00632 
00633 
00634 /*
00635  *  return nr. of conversation option 'str'. -1 if not found
00636  */
00637 
00638 int Conversation::locate_answer(const char *str)
00639 {
00640   int num;
00641   std::vector<string>::iterator it;
00642   num = 0;
00643   for(it=answers.begin(); it!=answers.end(); ++it) {
00644     if(*it==str)
00645       return num;
00646     num++;
00647   }
00648 
00649   return -1;
00650 }
00651 
00652 void Conversation::push_answers()
00653 {
00654   answer_stack.push_front(answers);
00655   answers.clear();
00656 }
00657 
00658 void Conversation::pop_answers()
00659 {
00660   answers=answer_stack.front();
00661   answer_stack.pop_front();
00662   gwin->paint();      // Really just need to figure tbox.
00663 }

Generated on Mon Jul 9 14:42:51 2007 for ExultEngine by  doxygen 1.5.1