/* * The Mana World * Copyright 2004 The Mana World Development Team * * This file is part of The Mana World. * * The Mana World is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * The Mana World is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with The Mana World; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id$ */ #include "game.h" #include "playerinfo.h" #include "floor_item.h" #include "main.h" #include "engine.h" #include "log.h" #include "map.h" #include "gui/chat.h" #include "gui/gui.h" #include "gui/inventory.h" #include "gui/shop.h" #include "gui/npc.h" #include "gui/stats.h" #include "gui/setup.h" #include "gui/equipment.h" #include "gui/popupmenu.h" #include "gui/npc_text.h" #include "gui/trade.h" #include "gui/status.h" #include "gui/buy.h" #include "gui/sell.h" #include "gui/buysell.h" #include "gui/ok_dialog.h" #include "gui/confirm_dialog.h" #include "gui/requesttrade.h" #include "gui/help.h" #include "gui/browserbox.h" #include "net/protocol.h" #include "net/network.h" #include "resources/mapreader.h" #include #include #include extern Graphics *graphics; char map_path[480]; bool refresh_beings = false; unsigned char keyb_state; volatile int tick_time; volatile bool action_time = false; int server_tick; int fps = 0, frame = 0, current_npc = 0; bool displayPathToMouse = false; int startX = 0, startY = 0; int gameTime = 0; Being *autoTarget = NULL; Engine *engine = NULL; SDL_Joystick *joypad = NULL; /**< Joypad object */ OkDialog *deathNotice = NULL; ConfirmDialog *exitConfirm = NULL; Being *target = NULL; const int EMOTION_TIME = 150; /**< Duration of emotion icon */ const int MAX_TIME = 10000; /** * Listener used for handling death message. */ class DeatchNoticeListener : public gcn::ActionListener { public: void action(const std::string &eventId) { WFIFOW(0) = net_w_value(0x00b2); WFIFOB(2) = 0; WFIFOSET(3); deathNotice = NULL; } } deathNoticeListener; /** * Listener used for exitting handling. */ class ExitListener : public gcn::ActionListener { void action(const std::string &eventId) { if (eventId == "yes") { state = EXIT; } exitConfirm = NULL; } } exitListener; /** * Advances game logic counter. */ Uint32 nextTick(Uint32 interval, void *param) { tick_time++; if (tick_time == MAX_TIME) tick_time = 0; return interval; } /** * Lets u only trigger an action every other second * tmp. counts fps */ Uint32 nextSecond(Uint32 interval, void *param) { action_time = true; fps = frame; frame = 0; return interval; } int get_elapsed_time(int start_time) { if (start_time <= tick_time) { return (tick_time - start_time) * 10; } else { return (tick_time + (MAX_TIME - start_time)) * 10; } } void do_init() { std::string path(map_path); std::string pathDir = path.substr(0, path.rfind(".")); // Try .tmx.gz map file pathDir.insert(pathDir.size(), ".tmx.gz"); Map *tiledMap = MapReader::readMap(pathDir); if (!tiledMap) { logger->error("Could not find map file!"); } else { engine->setCurrentMap(tiledMap); } // Initialize timers tick_time = 0; SDL_AddTimer(10, nextTick, NULL); // Logic counter SDL_AddTimer(1000, nextSecond, NULL); // Seconds counter // Initialize beings player_node = new Being(); player_node->id = account_ID; player_node->x = startX; player_node->y = startY; player_node->speed = 150; player_node->setHairColor(char_info->hair_color); player_node->setHairStyle(char_info->hair_style); if (char_info->weapon == 11) { char_info->weapon = 2; } player_node->weapon = char_info->weapon; add_node(player_node); remove("packet.list"); // Initialize joypad SDL_InitSubSystem(SDL_INIT_JOYSTICK); //SDL_JoystickEventState(SDL_ENABLE); int num_joy = SDL_NumJoysticks(); logger->log("%i joysticks/gamepads found", num_joy); for (int i = 0; i < num_joy; i++) logger->log("- %s", SDL_JoystickName(i)); // TODO: The user should be able to choose which one to use // Open the first device if (num_joy > 0) { joypad = SDL_JoystickOpen(0); if (joypad == NULL) { logger->log("Couldn't open joystick: %s", SDL_GetError()); } else { logger->log("Axes: %i ", SDL_JoystickNumAxes(joypad)); logger->log("Balls: %i", SDL_JoystickNumBalls(joypad)); logger->log("Hats: %i", SDL_JoystickNumHats(joypad)); logger->log("Buttons: %i", SDL_JoystickNumButtons(joypad)); } } } void game() { engine = new Engine(); do_init(); gameTime = tick_time; while (state != EXIT) { // Handle all necessary game logic while (get_elapsed_time(gameTime) > 0) { do_input(); engine->logic(); gui->logic(); gameTime++; } gameTime = tick_time; // Update the screen when application is active, delay otherwise if (SDL_GetAppState() & SDL_APPACTIVE) { engine->draw(); graphics->updateScreen(); } else { SDL_Delay(10); } // Handle network stuff and flush it do_parse(); flush(); } delete engine; close_session(); } void do_exit() { if (joypad != NULL) { SDL_JoystickClose(joypad); } } void do_input() { // Get the state of the keyboard keys Uint8* keys; keys = SDL_GetKeyState(NULL); // Get the state of the joypad buttons // TODO: Only 6- buttons joypads are allowed bool joy[10]; for (int i=0; i<10; i++) { joy[i] = false; } if (joypad != NULL) { // TODO: one different value of tolerance is needed for each direction // This probably means the need for a tuning utility/window int tolerance = (int)config.getValue("joytolerance", 10); SDL_JoystickUpdate(); if (SDL_JoystickGetAxis(joypad, 0) > tolerance) { joy[JOY_RIGHT] = true; } if (SDL_JoystickGetAxis(joypad, 0) < -tolerance) { joy[JOY_LEFT] = true; } if (SDL_JoystickGetAxis(joypad, 1) < -tolerance) { joy[JOY_UP] = true; } if (SDL_JoystickGetAxis(joypad, 1) > tolerance) { joy[JOY_DOWN] = true; } for (int i=0; i<6; i++) { if (SDL_JoystickGetButton(joypad, i) == 1) { joy[JOY_BTN0 + i] = true; } } } // Events SDL_Event event; while (SDL_PollEvent(&event)) { bool used = false; // Keyboard events (for discontinuous keys) if (event.type == SDL_KEYDOWN) { SDL_keysym keysym = event.key.keysym; // In-game Help if (keysym.sym == SDLK_F1) { if (helpWindow->isVisible()) { helpWindow->setVisible(false); } else { helpWindow->loadHelp("index"); } used = true; } // Player sit action else if ((keysym.sym == SDLK_F5) && action_time) { if (player_node->action == STAND) action(2, 0); else if (player_node->action == SIT) action(3, 0); action_time = false; } // Display path to mouse (debug purpose) else if ((keysym.sym == SDLK_F6)) { displayPathToMouse = !displayPathToMouse; } // Input chat window else if ((keysym.sym == SDLK_RETURN) && !chatWindow->isFocused()) { // Quit by pressing Enter if the exit confirm is there if ( exitConfirm ) { state = EXIT; } else if ( helpWindow->isVisible() ) // Close the Browser if opened { helpWindow->setVisible(false); } else if ( setupWindow->isVisible() ) // Close the config window, applying changes if opened { setupWindow->action("apply"); } else // Else, open the chat edit box { chatWindow->requestChatFocus(); used = true; } } // Emotions and some internal gui windows else if (keysym.mod & KMOD_ALT) { // Inventory window if (keysym.sym == SDLK_i) { inventoryWindow->setVisible(!inventoryWindow->isVisible()); used = true; } // Statistics window else if (keysym.sym == SDLK_s) { statsWindow->setVisible(!statsWindow->isVisible()); used = true; } /* // New skills window else if (keysym.sym == SDLK_n) { newSkillWindow->setVisible(!newSkillWindow->isVisible()); used = true; }*/ // Skill window else if (keysym.sym == SDLK_k) { skillDialog->setVisible(!skillDialog->isVisible()); used = true; } // Setup window else if (keysym.sym == SDLK_c) { setupWindow->setVisible(true); used = true; } // Equipment window else if (keysym.sym == SDLK_e) { equipmentWindow->setVisible(!equipmentWindow->isVisible()); used = true; } /* // Buddy window else if (keysym.sym == SDLK_b) { buddyWindow->setVisible(!buddyWindow->isVisible()); used = true; }*/ // Emotions else if (action_time && (player_node->emotion == 0)) { unsigned char emotion = 0; if (keysym.sym == SDLK_1) emotion = 1; else if (keysym.sym == SDLK_2) emotion = 2; else if (keysym.sym == SDLK_3) emotion = 3; else if (keysym.sym == SDLK_4) emotion = 4; else if (keysym.sym == SDLK_5) emotion = 5; else if (keysym.sym == SDLK_6) emotion = 6; else if (keysym.sym == SDLK_7) emotion = 7; else if (keysym.sym == SDLK_8) emotion = 8; else if (keysym.sym == SDLK_9) emotion = 9; else if (keysym.sym == SDLK_0) emotion = 10; if (emotion != 0) { WFIFOW(0) = net_w_value(0x00bf); WFIFOB(2) = emotion; WFIFOSET(3); action_time = false; used = true; } } } // Picking up items on the floor else if ((keysym.sym == SDLK_g || keysym.sym == SDLK_z) && !chatWindow->isFocused()) { unsigned short x = player_node->x; unsigned short y = player_node->y; int id = find_floor_item_by_cor(x, y); if (id != 0) { WFIFOW(0) = net_w_value(0x009f); WFIFOL(2) = net_l_value(id); WFIFOSET(6); } else { switch (player_node->direction) { case NORTH: y--; break; case SOUTH: y++; break; case WEST: x--; break; case EAST: x++; break; case NW: x--; y--; break; case NE: x++; y--; break; case SW: x--; y++; break; case SE: x++; y++; break; } id = find_floor_item_by_cor(x, y); WFIFOW(0) = net_w_value(0x009f); WFIFOL(2) = net_l_value(id); WFIFOSET(6); } used = true; } // Quitting confirmation dialog else if ((keysym.sym == SDLK_ESCAPE) && (!exitConfirm)) { exitConfirm = new ConfirmDialog( "Quit", "Are you sure you want to quit?", (gcn::ActionListener*)&exitListener); } } // Mouse events else if (event.type == SDL_MOUSEBUTTONDOWN) { int mx = event.button.x / 32 + camera_x; int my = event.button.y / 32 + camera_y; // Mouse button left if (event.button.button == SDL_BUTTON_LEFT) { // Don't move when shift is pressed // XXX Is this too hackish? I'm not sure if making the Gui // class a KeyListener is a good idea and works as expected // at all... if (keys[SDLK_LSHIFT] || keys[SDLK_RSHIFT]) { used = true; } // Check for default actions for NPC/Monster/Players Being *target = findNode(mx, my); unsigned int floorItemId = find_floor_item_by_cor(mx, my); if (target) { // NPC default: talk if ((target->isNpc()) && (current_npc == 0)) { WFIFOW(0) = net_w_value(0x0090); WFIFOL(2) = net_l_value(target->id); WFIFOB(6) = 0; WFIFOSET(7); current_npc = target->id; } // Monster default: attack else if (target->isMonster()) { /** * TODO: Move player to mouse click position before * attack the monster (maybe using follow mode). */ if (target->action != MONSTER_DEAD && player_node->action==STAND) { attack(target); // Autotarget by default with mouse //if (keys[SDLK_LSHIFT]) //{ autoTarget = target; //} } } // Player default: trade else if (target->isPlayer()) { WFIFOW(0) = net_w_value(0x00e4); WFIFOL(2) = net_l_value(target->id); WFIFOSET(6); } } // Check for default action to items on the floor else if (floorItemId != 0) { /** * TODO: Move player to mouse click position before * pick up the items on the floor. * * Provisory: pick up only items near of player. */ int dx = mx - player_node->x; int dy = my - player_node->y; if (sqrt(dx*dx + dy*dy) < 2) { WFIFOW(0) = net_w_value(0x009f); WFIFOL(2) = net_l_value(floorItemId); WFIFOSET(6); } } // Just cancel the popup menu if shown, and don't make the character walk if (popupMenu->isVisible() == true) { // We get the x, y coord of the click int clickX = (mx - camera_x) * 32 + 25; int clickY = (my - camera_y) * 32 + 25; // If we click elsewhere than in the window, do not use the event // The user wanted to close the popup. // Still buggy : Wonder if the x, y, width, and height aren't reported partially // with these functions. if ( clickX > (popupMenu->getX() + popupMenu->getWidth()) || clickX < popupMenu->getX() || clickY > (popupMenu->getY() + popupMenu->getHeight()) || clickY < popupMenu->getY() ) { used = true; popupMenu->setVisible(false); std::cout << "out" << std::endl; } } } // End Mouse left button // Mouse button middle else if (event.button.button == SDL_BUTTON_MIDDLE) { /** * Some people haven't a mouse with three buttons, * right Usiu??? ;-) */ } // Mouse button right else if (event.button.button == SDL_BUTTON_RIGHT) { popupMenu->showPopup(mx, my); } } // Quit event else if (event.type == SDL_QUIT) { state = EXIT; } // Push input to GUI when not used if (!used) { guiInput->pushInput(event); } } // End while // Moving player around if ((player_node->action != DEAD) && (current_npc == 0) && !chatWindow->isFocused()) { int x = player_node->x; int y = player_node->y; int xDirection = 0; int yDirection = 0; int Direction = DIR_NONE; // Translate pressed keys to movement and direction if (keys[SDLK_UP] || keys[SDLK_KP8] || joy[JOY_UP]) { yDirection = -1; if (player_node->action != WALK) Direction = NORTH; } if (keys[SDLK_DOWN] || keys[SDLK_KP2] || joy[JOY_DOWN]) { yDirection = 1; if (player_node->action != WALK) Direction = SOUTH; } if (keys[SDLK_LEFT] || keys[SDLK_KP4] || joy[JOY_LEFT]) { xDirection = -1; if (player_node->action != WALK) Direction = WEST; } if (keys[SDLK_RIGHT] || keys[SDLK_KP6] || joy[JOY_RIGHT]) { xDirection = 1; if (player_node->action != WALK) Direction = EAST; } if (keys[SDLK_KP1]) // Bottom Left { xDirection = -1; yDirection = 1; if (player_node->action != WALK) Direction = SW; } if (keys[SDLK_KP3]) // Bottom Right { xDirection = 1; yDirection = 1; if (player_node->action != WALK) Direction = SE; } if (keys[SDLK_KP7]) // Top Left { xDirection = -1; yDirection = -1; if (player_node->action != WALK) Direction = NW; } if (keys[SDLK_KP9]) // Top Right { xDirection = 1; yDirection = -1; if (player_node->action != WALK) Direction = NE; } // Allow keyboard control to interrupt an existing path if ((xDirection != 0 || yDirection != 0) && player_node->action == WALK) player_node->setDestination(x, y); Map *tiledMap = engine->getCurrentMap(); if (player_node->action != WALK) { // Prevent skipping corners over colliding tiles if ((xDirection != 0) && tiledMap->tileCollides(x + xDirection, y)) xDirection = 0; if ((yDirection != 0) && tiledMap->tileCollides(x, y + yDirection)) yDirection = 0; // Choose a straight direction when diagonal target is blocked if ((yDirection != 0) && (xDirection != 0) && !tiledMap->getWalk(x + xDirection, y + yDirection)) xDirection = 0; // Walk to where the player can actually go if (((xDirection != 0) || (yDirection != 0)) && tiledMap->getWalk(x + xDirection, y + yDirection)) { walk(x + xDirection, y + yDirection, Direction); player_node->setDestination(x + xDirection, y + yDirection); } else if (Direction != DIR_NONE) { // Update the player direction to where he wants to walk // Warning: Not communicated to the server yet player_node->direction = Direction; } } // Attacking monsters if (player_node->action == STAND) { if (keys[SDLK_LCTRL] || keys[SDLK_RCTRL] || joy[JOY_BTN0]) { Being *monster = attack(x, y, player_node->direction); if (monster == NULL && autoTarget != NULL) { attack(autoTarget); } else if (keys[SDLK_LSHIFT]) { autoTarget = monster; } } } if (joy[JOY_BTN1]) { unsigned short x = player_node->x; unsigned short y = player_node->y; int id = find_floor_item_by_cor(x, y); if (id != 0) { WFIFOW(0) = net_w_value(0x009f); WFIFOL(2) = net_l_value(id); WFIFOSET(6); } } else if (joy[JOY_BTN2] && action_time) { if (player_node->action == STAND) action(2, 0); else if (player_node->action == SIT) action(3, 0); action_time = false; } } } int get_packet_length(short id) { int len = get_length(id); if (len == -1) len = RFIFOW(2); return len; } void do_parse() { unsigned short id; char *temp; Being *being = NULL; FloorItem *floorItem = NULL; int len, n_items; Map *tiledMap = engine->getCurrentMap(); // We need at least 2 bytes to identify a packet if (in_size >= 2) { // Check if the received packet is complete while (in_size >= (len = get_packet_length(id = RFIFOW(0)))) { // Add infos to log file and dump the latest received packet char pkt_nfo[60]; sprintf(pkt_nfo,"In-buffer size: %i Packet id: %x Packet length: %i", in_size, RFIFOW(0), len); /* log_hex("Packet", "Packet_ID", RFIFOW(0)); log_int("Packet", "Packet_length", get_length(RFIFOW(0))); log_int("Packet", "Packet_in_size", RFIFOW(2)); log_int("Packet", "In_size", in_size); FILE *file = fopen("packet.dump", "wb"); for(int i=0;ix = get_x(RFIFOP(6)); player_node->y = get_y(RFIFOP(6)); break; // Received speech from being case SMSG_BEING_CHAT: being = findNode(RFIFOL(4)); if (being != NULL) { int length = RFIFOW(2) - 8; temp = (char*)malloc(length + 1); temp[length] = '\0'; memcpy(temp, RFIFOP(8), length); std::string msg = std::string(temp); msg.erase(0, msg.find(" : ", 0) + 3); being->setSpeech(msg, SPEECH_TIME); chatWindow->chat_log(temp, BY_OTHER); free(temp); } break; case SMSG_MY_BEING_CHAT: case SMSG_GM_CHAT: if (RFIFOW(2) > 4) { int length = RFIFOW(2) - 4; temp = (char*)malloc(length + 1); temp[length] = '\0'; memcpy(temp, RFIFOP(4), length); std::string msg = std::string(temp); unsigned int pos = msg.find(" : ", 0); if (id == 0x008e) { if (pos != std::string::npos) { msg.erase(0, pos + 3); } player_node->setSpeech(msg, SPEECH_TIME); chatWindow->chat_log(temp, BY_PLAYER); } else { chatWindow->chat_log(temp, BY_GM); } free(temp); } break; case SMSG_WALK_RESPONSE: // It is assumed by the client any request to walk actually // succeeds on the server. The plan is to have a correction // message when the server senses the client has the wrong // idea. break; // Add new being / stop monster case 0x0078: int beingId; beingId = RFIFOL(2); int beingJob; beingJob = RFIFOW(14); // Being with id >= 110000000 and job 0 are better known // as ghosts, so don't create those. if (beingJob == 0 && beingId >= 110000000) { break; } being = findNode(beingId); if (being == NULL) { being = new Being(); being->id = beingId; being->speed = RFIFOW(6); if (being->speed == 0) { // Else division by 0 when calculating frame being->speed = 150; } being->job = beingJob; being->setHairStyle(RFIFOW(16)); being->setHairColor(RFIFOW(28)); being->x = get_x(RFIFOP(46)); being->y = get_y(RFIFOP(46)); being->direction = get_direction(RFIFOP(46)); being->weapon = RFIFOW(18); add_node(being); } else { being->clearPath(); being->x = get_x(RFIFOP(46)); being->y = get_y(RFIFOP(46)); being->direction = get_direction(RFIFOP(46)); being->frame = 0; being->walk_time = tick_time; being->action = STAND; } break; case SMSG_REMOVE_BEING: // A being should be removed or has died being = findNode(RFIFOL(2)); if (being != NULL) { if (RFIFOB(6) == 1) { // Death if (being->isMonster()) { being->action = MONSTER_DEAD; being->frame = 0; being->walk_time = tick_time; } else { being->action = DEAD; } //remove_node(RFIFOL(2)); } else remove_node(RFIFOL(2)); } break; case SMSG_PLAYER_UPDATE_1: case SMSG_PLAYER_UPDATE_2: // A message about a player, doesn't include movement. being = findNode(RFIFOL(2)); if (being == NULL) { being = new Being(); being->id = RFIFOL(2); being->job = RFIFOW(14); add_node(being); } being->speed = RFIFOW(6); being->job = RFIFOW(14); being->setHairStyle(RFIFOW(16)); being->setHairColor(RFIFOW(28)); being->x = get_x(RFIFOP(46)); being->y = get_y(RFIFOP(46)); being->direction = get_direction(RFIFOP(46)); being->walk_time = tick_time; being->frame = 0; if (RFIFOB(51) == 2) { being->action = SIT; } break; case SMSG_MOVE_BEING: // A being nearby is moving being = findNode(RFIFOL(2)); if (being == NULL) { being = new Being(); being->id = RFIFOL(2); being->job = RFIFOW(14); add_node(being); } being->action = STAND; being->x = get_src_x(RFIFOP(50)); being->y = get_src_y(RFIFOP(50)); being->destX = get_dest_x(RFIFOP(50)); being->destY = get_dest_y(RFIFOP(50)); being->speed = RFIFOW(6); being->job = RFIFOW(14); being->weapon = RFIFOW(18); being->setDestination( get_dest_x(RFIFOP(50)), get_dest_y(RFIFOP(50))); break; case SMSG_MOVE_PLAYER_BEING: // A nearby player being moves being = findNode(RFIFOL(2)); if (being == NULL) { being = new Being(); being->id = RFIFOL(2); being->job = RFIFOW(14); add_node(being); } being->speed = RFIFOW(6); being->job = RFIFOW(14); being->x = get_src_x(RFIFOP(50)); being->y = get_src_y(RFIFOP(50)); being->destX = get_dest_x(RFIFOP(50)); being->destY = get_dest_y(RFIFOP(50)); being->setHairStyle(RFIFOW(16)); being->setHairColor(RFIFOW(32)); being->setDestination( get_dest_x(RFIFOP(50)), get_dest_y(RFIFOP(50))); break; // NPC dialog case 0x00b4: npcTextDialog->addText(RFIFOP(8)); npcListDialog->setVisible(false); npcTextDialog->setVisible(true); current_npc = RFIFOL(4); break; // Trade: Receiving a request to trade case 0x00e5: new RequestTradeDialog(RFIFOP(2)); break; // Trade: Response case 0x00e7: switch (RFIFOB(2)) { case 0: // too far away chatWindow->chat_log("Trading isn't possible. Trade partner is too far away.", BY_SERVER); break; case 1: // Character doesn't exist chatWindow->chat_log("Trading isn't possible. Character doesn't exist.", BY_SERVER); break; case 2: // invite request check failed... chatWindow->chat_log("Trade cancelled due to an unknown reason.", BY_SERVER); break; case 3: // Trade accepted tradeWindow->reset(); tradeWindow->setVisible(true); break; case 4: // Trade cancelled chatWindow->chat_log("Trade cancelled.", BY_SERVER); break; default: // Shouldn't happen as well, but to be sure chatWindow->chat_log("Unhandled trade cancel packet", BY_SERVER); break; } break; // Trade: Item added on trade partner's side case 0x00e9: // TODO: handle also identified, etc if (RFIFOW(6) == 0) { tradeWindow->addMoney(RFIFOL(2)); } else { tradeWindow->addItem( tradeWindow->partnerItems->getFreeSlot(), RFIFOW(6), false, RFIFOL(2), false); } break; // Trade: New Item add response case 0x01b1: switch (RFIFOB(6)) { case 0: // Successfully added item if (inventoryWindow->items->isEquipment(RFIFOW(2)) && inventoryWindow->items->isEquipped( RFIFOW(2))) { inventoryWindow->unequipItem(RFIFOW(2)); } tradeWindow->addItem( tradeWindow->myItems->getFreeSlot(), inventoryWindow->items->getId(RFIFOW(2)), true, RFIFOW(4), inventoryWindow->items->isEquipment( RFIFOW(2))); inventoryWindow->changeQuantity(RFIFOW(2), (inventoryWindow->items->getQuantity(RFIFOW(2)) - RFIFOW(4))); break; case 1: // Add item failed - player overweighted chatWindow->chat_log("Failed adding item. Trade " "partner is over weighted.", BY_SERVER); break; default: //printf("Unhandled 0x00ea byte!\n"); break; } break; // Trade received Ok message case 0x00ec: switch (RFIFOB(2)) { // Received ok from myself case 0: tradeWindow->receivedOk(true); break; // Received ok from the other case 1: tradeWindow->receivedOk(false); break; } break; // Trade cancelled case 0x00ee: chatWindow->chat_log("Trade cancelled.", BY_SERVER); tradeWindow->setVisible(false); tradeWindow->reset(); break; // Trade completed case 0x00f0: chatWindow->chat_log("Trade completed.", BY_SERVER); tradeWindow->setVisible(false); tradeWindow->reset(); break; // Get the items // Only called on map load / warp case 0x01ee: // Reset all items to not load them twice on map change inventoryWindow->items->resetItems(); for (int loop = 0; loop < (RFIFOW(2) - 4) / 18; loop++) { inventoryWindow->addItem(RFIFOW(4 + loop * 18), RFIFOW(4 + loop * 18 + 2), RFIFOW(4 + loop * 18 + 6), false); // Trick because arrows are not considered equipment if (RFIFOW(4 + loop * 18 + 2) == 1199 || RFIFOW(4 + loop * 18 + 2) == 529) { inventoryWindow->items->setEquipment( RFIFOW(4 + loop * 18), true); } } break; // Get the equipments case 0x00a4: for (int loop = 0; loop < ((RFIFOW(2) - 4) / 20); loop++) { inventoryWindow->addItem(RFIFOW(4 + loop * 20), RFIFOW(4 + loop * 20 + 2), 1, true); if (RFIFOW(4 + loop * 20 + 8)) { int mask = 1; int position = 0; while(!(RFIFOW(4+loop*20+8) & mask)) { mask *= 2; position++; } equipmentWindow->addEquipment(position - 1, RFIFOW(4+loop*20+2)); equipmentWindow->equipments[position - 1].inventoryIndex = RFIFOW(4+loop*20); inventoryWindow->items->setEquipped( RFIFOW(4+loop*20), true); } } break; // Can I use the item? case 0x00a8: if (RFIFOB(6) == 0) { chatWindow->chat_log("Failed to use item", BY_SERVER); } else { inventoryWindow->changeQuantity(RFIFOW(2), RFIFOW(4)); } break; // Warp case 0x0091: memset(map_path, '\0', 480); strcat(map_path, "maps/"); strncat(map_path, RFIFOP(2), 497 - strlen(map_path)); logger->log("Warping to %s (%d, %d)", map_path, RFIFOW(18), RFIFOW(20)); strcpy(strrchr(map_path, '.') + 1, "tmx.gz"); Map *oldMap; oldMap = tiledMap; tiledMap = MapReader::readMap(map_path); if (tiledMap) { // Delete all beings except the local player std::list::iterator i; for (i = beings.begin(); i != beings.end(); i++) { if ((*i) != player_node) { delete (*i); } } beings.clear(); // Re-add the local player node add_node(player_node); player_node->action = STAND; player_node->frame = 0; player_node->x = RFIFOW(18); player_node->y = RFIFOW(20); current_npc = 0; // Send "map loaded" WFIFOW(0) = net_w_value(0x007d); WFIFOSET(2); while (out_size > 0) flush(); engine->setCurrentMap(tiledMap); } else { logger->error("Could not find map file"); } if (oldMap) delete oldMap; break; // Skill ... case 0x011a: break; case 0x01a4: break; // Action failed (ex. sit because you have not reached the right level) case 0x0110: CHATSKILL action; action.skill = RFIFOW(2); action.bskill = RFIFOW(4); action.unused = RFIFOW(6); action.success = RFIFOB(8); action.reason = RFIFOB(9); if(action.success != SKILL_FAILED && action.bskill == BSKILL_EMOTE ) { printf("Action: %d/%d", action.bskill, action.success); } chatWindow->chat_log(action); break; // Update stat values case 0x00b0: switch(RFIFOW(2)) { //case 0x0000: // player_node->speed; // break; case 0x0005: char_info->hp = RFIFOW(4); break; case 0x0006: char_info->max_hp = RFIFOW(4); break; case 0x0007: char_info->sp = RFIFOW(4); break; case 0x0008: char_info->max_sp = RFIFOW(4); break; case 0x000b: char_info->lv = RFIFOW(4); break; case 0x000c: char_info->skill_point = RFIFOW(4); skillDialog->setPoints(char_info->skill_point); break; case 0x0018: char_info->totalWeight = RFIFOW(4); break; case 0x0019: char_info->maxWeight = RFIFOW(4); break; case 0x0037: char_info->job_lv = RFIFOW(4); break; case 0x0009: char_info->statsPointsToAttribute = RFIFOW(4); break; case 0x0035: player_node->aspd = RFIFOW(4); break; } statusWindow->update(); if (char_info->hp == 0 && deathNotice == NULL) { deathNotice = new OkDialog("Message", "You're now dead, press ok to restart", &deathNoticeListener); deathNotice->releaseModalFocus(); player_node->action = DEAD; } break; // Stop walking case 0x0088: // Disabled because giving some problems //if (being = findNode(RFIFOL(2))) { // if (being->id!=player_node->id) { // char ids[20]; // sprintf(ids,"%i",RFIFOL(2)); // alert(ids,"","","","",0,0); // being->action = STAND; // being->frame = 0; // set_coordinates(being->coordinates, RFIFOW(6), RFIFOW(8), get_direction(being->coordinates)); // } //} //break; // Damage, sit, stand up case 0x008a: switch (RFIFOB(26)) { case 0: // Damage being = findNode(RFIFOL(6)); if (being != NULL) { if (RFIFOW(22) == 0) { // Yellow being->setDamage("miss", SPEECH_TIME); } else { // Blue for monster, red for player std::stringstream ss; ss << RFIFOW(22); being->setDamage(ss.str(), SPEECH_TIME); } if (RFIFOL(2) != player_node->id) { // buggy being = findNode(RFIFOL(2)); if (being != NULL) { if (being->isPlayer()) { being->action = ATTACK; being->frame = 0; being->walk_time = tick_time; } else { being->action = MONSTER_ATTACK; being->frame = 0; being->walk_time = tick_time; } being->frame = 0; } } } break; case 2: // Sit case 3: // Stand up being = findNode(RFIFOL(2)); if (being != NULL) { being->frame = 0; if (RFIFOB(26) == 2) { being->action = SIT; } else if (RFIFOB(26) == 3) { being->action = STAND; } } break; } break; // Status change case 0x00b1: switch (RFIFOW(2)) { case 1: char_info->xp = RFIFOL(4); break; case 2: char_info->job_xp = RFIFOL(4); break; case 20: char_info->gp = RFIFOL(4); break; case 0x0016: char_info->xpForNextLevel = RFIFOL(4); break; case 0x0017: char_info->jobXpForNextLevel = RFIFOL(4); break; } break; // Level up case 0x019b: logger->log("Level up"); if (RFIFOL(2) == player_node->id) { sound.playSfx("sfx/levelup.ogg"); } break; // Emotion case 0x00c0: being = findNode(RFIFOL(2)); if(being) { being->emotion = RFIFOB(6); being->emotion_time = EMOTION_TIME; } break; // Update skill values case 0x0141: switch(RFIFOL(2)) { case 0x000d: char_info->STR = RFIFOL(6) + RFIFOL(10); // Base + Bonus break; case 0x000e: char_info->AGI = RFIFOL(6) + RFIFOL(10); break; case 0x000f: char_info->VIT = RFIFOL(6) + RFIFOL(10); break; case 0x0010: char_info->INT = RFIFOL(6) + RFIFOL(10); break; case 0x0011: char_info->DEX = RFIFOL(6) + RFIFOL(10); break; case 0x0012: char_info->LUK = RFIFOL(6) + RFIFOL(10); break; } break; // Buy/Sell dialog case 0x00c4: buyDialog->setVisible(false); sellDialog->setVisible(false); buySellDialog->setVisible(true); current_npc = RFIFOL(2); break; // Buy dialog case 0x00c6: n_items = (len - 4) / 11; buyDialog->reset(); buyDialog->setMoney(char_info->gp); buyDialog->setVisible(true); for (int k = 0; k < n_items; k++) { buyDialog->addItem(RFIFOW(4 + 11 * k + 9), RFIFOL(4 + 11 * k)); } break; // Sell dialog case 0x00c7: n_items = (len - 4) / 10; if (n_items > 0) { sellDialog->reset(); sellDialog->setVisible(true); for (int k = 0; k < n_items; k++) { sellDialog->addItem( RFIFOW(4 + 10 * k), RFIFOL(4 + 10 * k + 2)); } } else { chatWindow->chat_log("Nothing to sell", BY_SERVER); current_npc = 0; } break; // Answer to buy case 0x00ca: if (RFIFOB(2) == 0) chatWindow->chat_log("Thanks for buying", BY_SERVER); else chatWindow->chat_log("Unable to buy", BY_SERVER); break; // Answer to sell case 0x00cb: if (RFIFOB(2) == 0) chatWindow->chat_log("Thanks for selling", BY_SERVER); else chatWindow->chat_log("Unable to sell", BY_SERVER); break; // Add item to inventory after you bought it/picked up case 0x00a0: if (RFIFOB(22) > 0) chatWindow->chat_log("Unable to pick up item", BY_SERVER); else { if(RFIFOW(19)) { inventoryWindow->addItem(RFIFOW(2), RFIFOW(6), RFIFOW(4), true); } else { inventoryWindow->addItem(RFIFOW(2), RFIFOW(6), RFIFOW(4), false); } if (equipmentWindow->getArrows() == RFIFOW(6)) { equipmentWindow->arrowsNumber += RFIFOW(4); } } break; // Decrease quantity of an item in inventory case 0x00af: inventoryWindow->increaseQuantity(RFIFOW(2), -RFIFOW(4)); // If the item is arrow decrease number from equipment // window when equipped if (inventoryWindow->items->isEquipped(RFIFOW(2)) && ( inventoryWindow->items->getId(RFIFOW(2)) == 529 || inventoryWindow->items->getId(RFIFOW(2)) == 1199 ) ) equipmentWindow->arrowsNumber -= RFIFOW(4); break; // Use an item case 0x01c8: inventoryWindow->changeQuantity(RFIFOW(2), RFIFOW(10)); break; // Skill list TAG case 0x010f: { int n_skills = (len - 4) / 37; for (int k = 0; k < n_skills; k++) { if (RFIFOW(4 + k * 37 + 6) != 0 || RFIFOB(4 + k * 37 + 36)!=0) { int skillId = RFIFOW(4 + k * 37); if (skillDialog->hasSkill(skillId)) { skillDialog->setSkill(skillId, RFIFOW(4 + k * 37 + 6), RFIFOW(4 + k * 37 + 36)); } else { skillDialog->addSkill( RFIFOW(4 + k * 37), RFIFOW(4 + k * 37 + 6), RFIFOW(4 + k * 37 + 8)); } } } } break; // Display MVP player case 0x010c: chatWindow->chat_log("MVP player", BY_SERVER); break; // Item found/dropped case 0x009d: case 0x009e: floorItem = new FloorItem(); floorItem->id = net_w_value(RFIFOW(6)); floorItem->x = net_w_value(RFIFOW(9)); floorItem->y = net_w_value(RFIFOW(11)); floorItem->int_id = net_l_value(RFIFOL(2)); add_floor_item(floorItem); break; // Item disappearing case 0x00a1: floorItem = find_floor_item_by_id(net_l_value(RFIFOL(2))); if (floorItem != NULL) { remove_floor_item(net_l_value(RFIFOL(2))); } else { remove_floor_item(net_l_value(RFIFOL(2))); } break; // Next/Close button in NPC dialog case 0x00b5: case 0x00b6: // Unused break; // List in NPC dialog case 0x00b7: current_npc = RFIFOL(4); // Hammerbear: Second argument here shouldn't be neccesary, // instead make sure the string is \0 terminated. //parse_items(RFIFOP(8), RFIFOW(2)); npcListDialog->parseItems(RFIFOP(8)); npcListDialog->setVisible(true); break; case SMSG_CHANGE_BEING_LOOKS: being = findNode(RFIFOL(2)); if (being) { switch (RFIFOB(6)) { case 1: being->setHairStyle(RFIFOB(7)); break; case 2: being->weapon = RFIFOB(7); break; case 6: being->setHairColor(RFIFOB(7)); break; } } break; // Answer to equip item case 0x00aa: logger->log("Equipping: %i %i %i", RFIFOW(2), RFIFOW(4), RFIFOB(6)); if (RFIFOB(6) == 0) chatWindow->chat_log("Unable to equip.", BY_SERVER); else { if(RFIFOW(4)) { int mask = 1; int position = 0; while(!(RFIFOW(4) & mask)) { mask *= 2; position++; } logger->log("Position %i", position-1); int equippedId = equipmentWindow->equipments[position - 1].id; if (equippedId > 0) inventoryWindow->items->setEquipped( equipmentWindow->equipments[position - 1].inventoryIndex, false); inventoryWindow->items->setEquipped(RFIFOW(2), true); equipmentWindow->addEquipment(position - 1, inventoryWindow->items->getId(RFIFOW(2))); equipmentWindow->equipments[position - 1].inventoryIndex = RFIFOW(2); // Trick to use the proper graphic until I find // the right packet switch (inventoryWindow->items->getId(RFIFOW(2))) { case 521: case 522: case 536: case 1201: player_node->weapon = 1; break; case 530: case 1200: player_node->weapon = 2; break; } } } break; // Equipment related case 0x01d7: logger->log("1d7 %i %i %i %i", RFIFOL(2), RFIFOB(6), RFIFOW(7), RFIFOW(9)); break; // Answer to unequip item case 0x00ac: if (RFIFOB(6) == 0) chatWindow->chat_log("Unable to unequip.", BY_SERVER); else { if(RFIFOW(4)) { int mask = 1; int position = 0; while(!(RFIFOW(4) & mask)) { mask *= 2; position++; } inventoryWindow->items->setEquipped(RFIFOW(2), false); switch (inventoryWindow->items->getId(RFIFOW(2))) { case 529: case 1199: equipmentWindow->setArrows(0); equipmentWindow->arrowsNumber = 0; break; case 521: case 522: case 530: case 536: case 1200: case 1201: player_node->weapon = 0; break; default: equipmentWindow->removeEquipment(position - 1); break; } logger->log("Unequipping: %i %i(%i) %i", RFIFOW(2),RFIFOW(4),RFIFOB(6), position -1); } } break; // Arrows equipped case 0x013c: if (RFIFOW(2) > 1) { inventoryWindow->items->setEquipped(RFIFOW(2), true); equipmentWindow->setArrows( inventoryWindow->items->getId(RFIFOW(2))); equipmentWindow->arrowsNumber = inventoryWindow->items->getQuantity(RFIFOW(2)); logger->log("Arrows equipped: %i", RFIFOW(2)); } break; // Various messages case 0x013b: if (RFIFOW(2) == 0) chatWindow->chat_log("Equip arrows first", BY_SERVER); else logger->log("0x013b: Unhandled message %i", RFIFOW(2)); break; // Updates a stat value case 0x00bc: /*char stat[20]; sprintf(stat, "bc %x %i %i", RFIFOW(2),RFIFOB(4),RFIFOB(5)); chatWindow->chat_log(stat, BY_SERVER);*/ if(RFIFOB(4)) { switch(RFIFOW(2)) { case 0x000d: char_info->STR = RFIFOB(5); break; case 0x000e: char_info->AGI = RFIFOB(5); break; case 0x000f: char_info->VIT = RFIFOB(5); break; case 0x0010: char_info->INT = RFIFOB(5); break; case 0x0011: char_info->DEX = RFIFOB(5); break; case 0x0012: char_info->LUK = RFIFOB(5); break; } } break; // Updates stats and status points case 0x00bd: char_info->statsPointsToAttribute = RFIFOW(2); char_info->STR = RFIFOB(4); char_info->STRUp = RFIFOB(5); char_info->AGI = RFIFOB(6); char_info->AGIUp = RFIFOB(7); char_info->VIT = RFIFOB(8); char_info->VITUp = RFIFOB(9); char_info->INT = RFIFOB(10); char_info->INTUp = RFIFOB(11); char_info->DEX = RFIFOB(12); char_info->DEXUp = RFIFOB(13); char_info->LUK = RFIFOB(14); char_info->LUKUp = RFIFOB(15); /*char stats[100]; sprintf(stats, "%i %i %i %i %i %i %i %i %i %i %i %i", RFIFOB(4),RFIFOB(5),RFIFOB(6),RFIFOB(7),RFIFOB(8),RFIFOB(9), RFIFOB(10),RFIFOB(11),RFIFOB(12),RFIFOB(13),RFIFOB(14),RFIFOB(15)); chatWindow->chat_log(stats,BY_SERVER);*/ statusWindow->update(); break; // Updates status point case 0x00be: switch(RFIFOW(2)) { case 0x0020: char_info->STRUp = RFIFOB(4); break; case 0x0021: char_info->AGIUp = RFIFOB(4); break; case 0x0022: char_info->VITUp = RFIFOB(4); break; case 0x0023: char_info->INTUp = RFIFOB(4); break; case 0x0024: char_info->DEXUp = RFIFOB(4); break; case 0x0025: char_info->LUKUp = RFIFOB(4); break; } break; // Get being name case 0x0095: being = findNode(RFIFOL(2)); if (being) strcpy(being->name, RFIFOP(6)); break; // Manage non implemented packets default: logger->log("Unhandled packet: %x", id); break; } RFIFOSKIP(len); } } }