/*
* 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 <sstream>
#include <guichan/sdl/sdlinput.hpp>
#include "being.h"
#include "configuration.h"
#include "engine.h"
#include "equipment.h"
#include "floor_item.h"
#include "graphics.h"
#include "inventory.h"
#include "item.h"
#include "log.h"
#include "main.h"
#include "map.h"
#include "playerinfo.h"
#include "sound.h"
#include "gui/buy.h"
#include "gui/buysell.h"
#include "gui/chargedialog.h"
#include "gui/chat.h"
#include "gui/confirm_dialog.h"
#include "gui/equipmentwindow.h"
#include "gui/gui.h"
#include "gui/help.h"
#include "gui/inventorywindow.h"
#include "gui/minimap.h"
#include "gui/npc.h"
#include "gui/npc_text.h"
#include "gui/ok_dialog.h"
#include "gui/popupmenu.h"
#include "gui/requesttrade.h"
#include "gui/sell.h"
#include "gui/setup.h"
#include "gui/skill.h"
#include "gui/stats.h"
#include "gui/status.h"
#include "gui/trade.h"
#include "net/network.h"
#include "net/protocol.h"
#include "resources/mapreader.h"
extern Graphics *graphics;
char map_path[480];
char tradePartnerName[24];
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;
ChatWindow *chatWindow;
StatusWindow *statusWindow;
BuyDialog *buyDialog;
SellDialog *sellDialog;
BuySellDialog *buySellDialog;
InventoryWindow *inventoryWindow;
NpcListDialog *npcListDialog;
NpcTextDialog *npcTextDialog;
SkillDialog *skillDialog;
//NewSkillDialog *newSkillWindow;
StatsWindow *statsWindow;
Setup* setupWindow;
Minimap *minimap;
EquipmentWindow *equipmentWindow;
ChargeDialog *chargeDialog;
TradeWindow *tradeWindow;
//BuddyWindow *buddyWindow;
HelpWindow *helpWindow;
PopupMenu *popupMenu;
Inventory *inventory = 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;
}
}
Being* createBeing(unsigned int id, unsigned short job, Map *map)
{
Being *being = new Being;
being->setId(id);
being->job = job;
being->setMap(map);
return being;
}
/**
* Create all the various globally accessible gui windows
*/
void createGuiWindows()
{
// Create dialogs
chatWindow = new ChatWindow(
config.getValue("homeDir", "") + std::string("/chatlog.txt"));
statusWindow = new StatusWindow();
buyDialog = new BuyDialog();
sellDialog = new SellDialog();
buySellDialog = new BuySellDialog();
inventoryWindow = new InventoryWindow();
npcTextDialog = new NpcTextDialog();
npcListDialog = new NpcListDialog();
skillDialog = new SkillDialog();
//newSkillWindow = new NewSkillDialog();
statsWindow = new StatsWindow();
setupWindow = new Setup();
minimap = new Minimap();
equipmentWindow = new EquipmentWindow();
chargeDialog = new ChargeDialog();
tradeWindow = new TradeWindow();
//buddyWindow = new BuddyWindow();
helpWindow = new HelpWindow();
popupMenu = new PopupMenu();
// Initialize window posisitons
int screenW = graphics->getWidth();
int screenH = graphics->getHeight();
chatWindow->setPosition(0, screenH - chatWindow->getHeight());
statusWindow->setPosition(screenW - statusWindow->getWidth() - 5, 5);
inventoryWindow->setPosition(screenW - statusWindow->getWidth() -
inventoryWindow->getWidth() - 10, 5);
statsWindow->setPosition(
screenW - 5 - statsWindow->getWidth(),
statusWindow->getHeight() + 20);
chargeDialog->setPosition(
screenW - 5 - chargeDialog->getWidth(),
screenH - chargeDialog->getHeight() - 15);
tradeWindow->setPosition(screenW - statusWindow->getWidth() -
tradeWindow->getWidth() - 10,
inventoryWindow->getY() + inventoryWindow->getHeight());
/*buddyWindow->setPosition(10,
minimap->getHeight() + 30);*/
equipmentWindow->setPosition(5,140);
// Set initial window visibility
chatWindow->setVisible(true);
statusWindow->setVisible(true);
buyDialog->setVisible(false);
sellDialog->setVisible(false);
buySellDialog->setVisible(false);
inventoryWindow->setVisible(false);
npcTextDialog->setVisible(false);
npcListDialog->setVisible(false);
skillDialog->setVisible(false);
//newSkillWindow->setVisible(false);
statsWindow->setVisible(false);
setupWindow->setVisible(false);
equipmentWindow->setVisible(false);
chargeDialog->setVisible(false);
tradeWindow->setVisible(false);
//buddyWindow->setVisible(false);
helpWindow->setVisible(false);
popupMenu->setVisible(false);
// Do not focus any text field
gui->focusNone();
}
/**
* Destroy all the globally accessible gui windows
*/
void destroyGuiWindows()
{
delete chatWindow;
delete statusWindow;
delete buyDialog;
delete sellDialog;
delete buySellDialog;
delete inventoryWindow;
delete npcListDialog;
delete npcTextDialog;
delete skillDialog;
delete statsWindow;
delete setupWindow;
delete minimap;
delete equipmentWindow;
delete chargeDialog;
//delete newSkillWindow;
delete tradeWindow;
//delete buddyWindow;
delete helpWindow;
delete popupMenu;
}
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 = createBeing(account_ID, 0, tiledMap);
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->setWeapon(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()
{
// Needs to be initialised _before_ the engine is created...
inventory = new Inventory();
createGuiWindows();
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;
destroyGuiWindows();
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)
{
switch (event.key.keysym.sym)
{
// In-game Help
case SDLK_F1:
if (helpWindow->isVisible()) {
helpWindow->setVisible(false);
} else {
helpWindow->loadHelp("index");
}
used = true;
break;
// Player sit action
case SDLK_F5:
if (!action_time) {
break;
}
switch (player_node->action)
{
case Being::STAND:
action(2, 0);
break;
case Being::SIT:
action(3, 0);
break;
}
break;
// Display path to mouse (debug purpose)
case SDLK_F6:
displayPathToMouse = !displayPathToMouse;
break;
// Input chat window
case SDLK_RETURN:
if (chatWindow->isFocused()) {
break;
}
// Quit by pressing Enter if the exit confirm is there
if (exitConfirm)
{
state = EXIT;
}
// Accept the Death Notice...
else if (deathNotice)
{
deathNotice->action("ok");
WFIFOW(0) = net_w_value(0x00b2);
WFIFOB(2) = 0;
WFIFOSET(3);
deathNotice = NULL;
}
// Close the Browser if opened
else if (helpWindow->isVisible())
{
helpWindow->setVisible(false);
}
// Close the config window, cancelling changes if opened
else if (setupWindow->isVisible())
{
setupWindow->action("cancel");
}
// Else, open the chat edit box
else
{
chatWindow->requestChatFocus();
used = true;
}
break;
// Picking up items on the floor
case SDLK_g:
case SDLK_z:
if (!chatWindow->isFocused()) {
unsigned short x = player_node->x;
unsigned short y = player_node->y;
int id = find_floor_item_by_cor(x, y);
// If none below the player, try the tile in front of the player
if (!id) {
switch (player_node->direction)
{
case Being::NORTH: y--; break;
case Being::SOUTH: y++; break;
case Being::WEST: x--; break;
case Being::EAST: x++; break;
case Being::NW: x--; y--; break;
case Being::NE: x++; y--; break;
case Being::SW: x--; y++; break;
case Being::SE: x++; y++; break;
default: break;
}
id = find_floor_item_by_cor(x, y);
}
if (id)
{
WFIFOW(0) = net_w_value(0x009f);
WFIFOL(2) = net_l_value(id);
WFIFOSET(6);
}
used = true;
}
break;
// Quitting confirmation dialog
case SDLK_ESCAPE:
if (!exitConfirm) {
exitConfirm = new ConfirmDialog(
"Quit", "Are you sure you want to quit?",
(gcn::ActionListener*)&exitListener);
}
break;
default:
break;
}
// Keys pressed together with Alt/Meta
// Emotions and some internal gui windows
if (event.key.keysym.mod & KMOD_ALT)
{
switch (event.key.keysym.sym)
{
// Inventory window
case SDLK_i:
inventoryWindow->setVisible(
!inventoryWindow->isVisible());
used = true;
break;
// Statistics window
case SDLK_s:
statsWindow->setVisible(!statsWindow->isVisible());
used = true;
break;
/*
// New skills window
case SDLK_n:
newSkillWindow->setVisible(!newSkillWindow->isVisible());
used = true;
break;
*/
// Skill window
case SDLK_k:
skillDialog->setVisible(!skillDialog->isVisible());
used = true;
break;
// Setup window
case SDLK_c:
setupWindow->setVisible(true);
used = true;
break;
// Equipment window
case SDLK_e:
equipmentWindow->setVisible(
!equipmentWindow->isVisible());
used = true;
break;
/*
// Buddy window
case SDLK_b:
buddyWindow->setVisible(!buddyWindow->isVisible());
used = true;
break;
*/
default:
break;
}
// Emotions
if (action_time && !player_node->emotion)
{
unsigned char emotion = 0;
switch (event.key.keysym.sym)
{
case SDLK_1: emotion = 1; break;
case SDLK_2: emotion = 2; break;
case SDLK_3: emotion = 3; break;
case SDLK_4: emotion = 4; break;
case SDLK_5: emotion = 5; break;
case SDLK_6: emotion = 6; break;
case SDLK_7: emotion = 7; break;
case SDLK_8: emotion = 8; break;
case SDLK_9: emotion = 9; break;
case SDLK_0: emotion = 10; break;
default: break;
}
if (emotion)
{
WFIFOW(0) = net_w_value(0x00bf);
WFIFOB(2) = emotion;
WFIFOSET(3);
action_time = false;
used = true;
}
}
}
}
// 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)
{
switch (target->getType())
{
// Player default: trade
case Being::PLAYER:
WFIFOW(0) = net_w_value(0x00e4);
WFIFOL(2) = net_l_value(target->getId());
WFIFOSET(6);
strcpy(tradePartnerName, target->name);
break;
// NPC default: talk
case Being::NPC:
if (!current_npc)
{
WFIFOW(0) = net_w_value(0x0090);
WFIFOL(2) = net_l_value(target->getId());
WFIFOB(6) = 0;
WFIFOSET(7);
current_npc = target->getId();
}
break;
// Monster default: attack
case Being::MONSTER:
/**
* TODO: Move player to mouse click position before
* attack the monster (maybe using follow mode).
*/
if (target->action != Being::MONSTER_DEAD &&
player_node->action == Being::STAND)
{
attack(target);
// Autotarget by default with mouse
//if (keys[SDLK_LSHIFT])
//{
autoTarget = target;
//}
}
break;
default:
break;
}
}
// 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;
// "sqrt(dx*dx + dy*dy) < 2" is equal to "dx*dx + dy*dy < 4"
if ((dx*dx + dy*dy) < 4)
{
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)
{
// 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 (event.button.x >= (popupMenu->getX() + popupMenu->getWidth()) ||
event.button.x < popupMenu->getX() ||
event.button.y >= (popupMenu->getY() + popupMenu->getHeight()) ||
event.button.y < popupMenu->getY())
{
used = true;
popupMenu->setVisible(false);
}
}
} // 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)
{
Being *being;
FloorItem *floorItem;
if ((being = findNode(mx, my))) {
popupMenu->showPopup(event.button.x, event.button.y,
being);
} else if ((floorItem = find_floor_item_by_id(
find_floor_item_by_cor(mx, my)))) {
popupMenu->showPopup(event.button.x, event.button.y,
floorItem);
} else {
popupMenu->setVisible(false);
}
}
}
// 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 != Being::DEAD) && (current_npc == 0) &&
!chatWindow->isFocused())
{
int x = player_node->x;
int y = player_node->y;
int xDirection = 0;
int yDirection = 0;
Being::Direction Direction = Being::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 != Being::WALK)
Direction = Being::NORTH;
}
if (keys[SDLK_DOWN] || keys[SDLK_KP2] || joy[JOY_DOWN])
{
yDirection = 1;
if (player_node->action != Being::WALK)
Direction = Being::SOUTH;
}
if (keys[SDLK_LEFT] || keys[SDLK_KP4] || joy[JOY_LEFT])
{
xDirection = -1;
if (player_node->action != Being::WALK)
Direction = Being::WEST;
}
if (keys[SDLK_RIGHT] || keys[SDLK_KP6] || joy[JOY_RIGHT])
{
xDirection = 1;
if (player_node->action != Being::WALK)
Direction = Being::EAST;
}
if (keys[SDLK_KP1]) // Bottom Left
{
xDirection = -1;
yDirection = 1;
if (player_node->action != Being::WALK)
Direction = Being::SW;
}
if (keys[SDLK_KP3]) // Bottom Right
{
xDirection = 1;
yDirection = 1;
if (player_node->action != Being::WALK)
Direction = Being::SE;
}
if (keys[SDLK_KP7]) // Top Left
{
xDirection = -1;
yDirection = -1;
if (player_node->action != Being::WALK)
Direction = Being::NW;
}
if (keys[SDLK_KP9]) // Top Right
{
xDirection = 1;
yDirection = -1;
if (player_node->action != Being::WALK)
Direction = Being::NE;
}
Map *tiledMap = engine->getCurrentMap();
// Allow keyboard control to interrupt an existing path
if ((xDirection != 0 || yDirection != 0) && player_node->action == Being::WALK)
player_node->setDestination(x, y);
if (player_node->action != Being::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 != Being::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 == Being::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 == Being::STAND)
action(2, 0);
else if (player_node->action == Being::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();
Equipment *equipment = Equipment::getInstance();
// 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)))) {
#ifdef DEBUG
printf("Packet_ID: %x\n", RFIFOW(0));
#endif
// Parse packet based on their id
switch (id)
{
case SMSG_LOGIN_SUCCESS:
// Connected to game server succesfully, set spawn point
player_node->x = 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 = createBeing(beingId, beingJob, tiledMap);
being->speed = RFIFOW(6);
if (being->speed == 0) {
// Else division by 0 when calculating frame
being->speed = 150;
}
being->setHairStyle(RFIFOW(16));
being->setHairColor(RFIFOW(28));
being->setWeapon(RFIFOW(18));
being->setMap(tiledMap);
add_node(being);
}
else
{
being->clearPath();
//being->setWeapon(RFIFOW(18));
being->frame = 0;
being->walk_time = tick_time;
being->action = Being::STAND;
}
being->x = get_x(RFIFOP(46));
being->y = get_y(RFIFOP(46));
being->direction = get_direction(RFIFOP(46));
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
switch (being->getType())
{
case Being::MONSTER:
being->action = Being::MONSTER_DEAD;
being->frame = 0;
being->walk_time = tick_time;
break;
default:
being->action = Being::DEAD;
break;
}
//remove_node(RFIFOL(2));
}
else {
remove_node(RFIFOL(2));
}
if (being == autoTarget) {
autoTarget = NULL;
}
}
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 = createBeing(RFIFOL(2), RFIFOW(14), tiledMap);
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;
being->setWeaponById(RFIFOW(18));
if (RFIFOB(51) == 2)
{
being->action = Being::SIT;
}
break;
case SMSG_MOVE_BEING:
// A being nearby is moving
being = findNode(RFIFOL(2));
if (being == NULL)
{
being = createBeing(RFIFOL(2), RFIFOW(14), tiledMap);
add_node(being);
}
being->action = Being::STAND;
being->x = get_src_x(RFIFOP(50));
being->y = get_src_y(RFIFOP(50));
being->speed = RFIFOW(6);
being->job = RFIFOW(14);
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 = createBeing(RFIFOL(2), RFIFOW(14), tiledMap);
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->setHairStyle(RFIFOW(16));
being->setWeaponById(RFIFOW(18));
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:
// If a trade window is already open, send a trade cancel
// to any other trade request.
// It would still be nice to implement an independent message
// that the person you want to trade with can't do that now.
if (tradeWindow->isVisible() == true)
{
// 0xff packet means cancel
WFIFOW(0) = net_w_value(0x00e6);
WFIFOB(2) = net_b_value(4);
WFIFOSET(3);
break;
}
if (!requestTradeDialogOpen)
{
requestTradeDialogOpen = true;
strcpy(tradePartnerName, RFIFOP(2));
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->setCaption((std::string)"Trade: You and " + (std::string)tradePartnerName);
tradeWindow->setVisible(true);
requestTradeDialogOpen = false;
break;
case 4:
// Trade cancelled
chatWindow->chat_log("Trade cancelled.", BY_SERVER);
tradeWindow->setVisible(false);
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(RFIFOW(6), false, RFIFOL(2), false);
}
break;
// Trade: New Item add response
case 0x01b1:
{
Item *item = inventory->getItem(RFIFOW(2));
switch (RFIFOB(6))
{
case 0:
// Successfully added item
if (item->isEquipment() && item->isEquipped())
{
inventory->unequipItem(item);
}
tradeWindow->addItem(
item->getId(), true, RFIFOW(4),
item->isEquipment());
item->increaseQuantity(-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:
// 0 means ok from myself, 1 means ok from other;
tradeWindow->receivedOk((RFIFOB(2) == 0));
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
inventory->resetItems();
for (int loop = 0; loop < (RFIFOW(2) - 4) / 18; loop++)
{
inventory->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)
{
inventory->getItem(
RFIFOW(4 + loop * 18))->setEquipment(true);
}
}
break;
// Get the equipments
case 0x00a4:
for (int loop = 0; loop < ((RFIFOW(2) - 4) / 20); loop++)
{
inventory->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++;
}
Item *item = inventory->getItem(RFIFOW(4+loop*20));
item->setEquipped(true);
equipment->setEquipment(position - 1, item);
}
}
break;
// Can I use the item?
case 0x00a8:
if (RFIFOB(6) == 0)
{
chatWindow->chat_log("Failed to use item", BY_SERVER);
}
else
{
inventory->getItem(RFIFOW(2))->setQuantity(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)
{
empty_floor_items();
// Delete all beings except the local player
std::list<Being *>::iterator i;
for (i = beings.begin(); i != beings.end(); i++)
{
if ((*i) != player_node)
{
delete (*i);
}
}
beings.clear();
autoTarget = NULL;
// Re-add the local player node
add_node(player_node);
player_node->action = Being::STAND;
player_node->frame = 0;
player_node->x = RFIFOW(18);
player_node->y = RFIFOW(20);
player_node->setMap(tiledMap);
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;
// 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;
}
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 = Being::DEAD;
}
break;
// Stop walking
// case 0x0088: // Disabled because giving some problems
//if (being = findNode(RFIFOL(2))) {
// if (being->getId()!=player_node->getId()) {
// 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->getId()) { // buggy
being = findNode(RFIFOL(2));
if (being) {
being->action = Being::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 = Being::SIT;
}
else if (RFIFOB(26) == 3) {
being->action = Being::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->getId()) {
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);
buyDialog->reset();
sellDialog->setVisible(false);
sellDialog->reset();
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++) {
Item *item = inventory->getItem(RFIFOW(4 + 10 * k));
if (item && !(item->isEquipped())) {
sellDialog->addItem(item, 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 {
inventory->addItem(RFIFOW(2), RFIFOW(6),
RFIFOW(4), RFIFOW(19) != 0);
}
break;
// Decrease quantity of an item in inventory
case 0x00af:
inventory->getItem(RFIFOW(2))->increaseQuantity(-RFIFOW(4));
break;
// Use an item
case 0x01c8:
inventory->getItem(RFIFOW(2))->setQuantity( RFIFOW(10));
break;
// Skill list TAG
case 0x010f:
n_items = (len - 4) / 37;
skillDialog->cleanList();
for (int k = 0; k < n_items; 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),
RFIFOB(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 = RFIFOW(6);
floorItem->x = RFIFOW(9);
floorItem->y = RFIFOW(11);
floorItem->int_id = RFIFOL(2);
add_floor_item(floorItem);
break;
// Item disappearing
case 0x00a1:
remove_floor_item(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);
// RFIFOW(2) seems to be the full packet length, thus -8
npcListDialog->parseItems(RFIFOP(8), RFIFOW(2)-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->setWeapon(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);
Item *item = equipment->getEquipment(position - 1);
if (item)
item->setEquipped(false);
item = inventory->getItem(RFIFOW(2));
item->setEquipped(true);
equipment->setEquipment(position - 1, item);
player_node->setWeaponById(item->getId());
}
}
break;
// Equipment related
case 0x01d7:
being = findNode(RFIFOL(2));
if (being != NULL)
{
being->setWeaponById(RFIFOW(7));
}
//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++;
}
Item *item = inventory->getItem(RFIFOW(2));
item->setEquipped(false);
switch (item->getId()) {
case 529:
case 1199:
equipment->setArrows(NULL);
break;
case 521:
case 522:
case 530:
case 536:
case 1200:
case 1201:
player_node->setWeapon(0);
break; // TODO : why this break ? shouldn't a weapon be unequipped in inventory too ?
default:
equipment->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) {
Item *item = inventory->getItem(RFIFOW(2));
item->setEquipped(true);
equipment->setArrows(item);
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:
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);
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)
{
//std::cout << RFIFOL(2) << " is " << RFIFOP(6) << std::endl;
strcpy(being->name, RFIFOP(6));
}
break;
// Change in players look
case 0x0119:
break;
// Manage non implemented packets
default:
logger->log("Unhandled packet: %x", id);
break;
}
RFIFOSKIP(len);
}
}
}