From cfdc19df45c807cd69849273e676598187b30ced Mon Sep 17 00:00:00 2001 From: purpleyouko Date: Mon, 11 Aug 2025 11:09:09 -0500 Subject: [PATCH] added source folders --- Src/3rd_party/mysql/include/my_alloc.h | 52 + Src/3rd_party/mysql/include/my_dbug.h | 101 + Src/3rd_party/mysql/include/my_getopt.h | 77 + Src/3rd_party/mysql/include/my_global.h | 1276 +++++ Src/3rd_party/mysql/include/my_list.h | 46 + Src/3rd_party/mysql/include/my_sys.h | 903 ++++ Src/3rd_party/mysql/include/mysql.h | 839 +++ Src/3rd_party/mysql/include/mysql_com.h | 444 ++ Src/3rd_party/mysql/include/mysql_embed.h | 32 + Src/3rd_party/mysql/include/mysql_time.h | 56 + Src/3rd_party/mysql/include/mysql_version.h | 29 + Src/3rd_party/mysql/include/mysqld_ername.h | 456 ++ Src/3rd_party/mysql/include/mysqld_error.h | 458 ++ Src/3rd_party/mysql/include/sched.h | 183 + Src/3rd_party/mysql/include/typelib.h | 34 + Src/3rd_party/mysql/lib/libmysql.dll | Bin 0 -> 1155072 bytes Src/3rd_party/mysql/lib/libmysql.lib | Bin 0 -> 34876 bytes Src/3rd_party/pthread/bin/x64/pthreadGC2.dll | Bin 0 -> 185976 bytes Src/3rd_party/pthread/bin/x64/pthreadVC2.dll | Bin 0 -> 82944 bytes Src/3rd_party/pthread/bin/x86/pthreadGC2.dll | Bin 0 -> 119888 bytes Src/3rd_party/pthread/bin/x86/pthreadGCE2.dll | Bin 0 -> 121953 bytes Src/3rd_party/pthread/bin/x86/pthreadVC2.dll | Bin 0 -> 55808 bytes Src/3rd_party/pthread/bin/x86/pthreadVCE2.dll | Bin 0 -> 61952 bytes Src/3rd_party/pthread/bin/x86/pthreadVSE2.dll | Bin 0 -> 57344 bytes Src/3rd_party/pthread/include/pthread.h | 1368 +++++ Src/3rd_party/pthread/lib/x64/pthreadVC2.lib | Bin 0 -> 29738 bytes Src/3rd_party/pthread/lib/x86/pthreadVC2.lib | Bin 0 -> 30334 bytes Src/3rd_party/pthread/lib/x86/pthreadVCE2.lib | Bin 0 -> 30460 bytes Src/3rd_party/pthread/lib/x86/pthreadVSE2.lib | Bin 0 -> 30460 bytes Src/Char Server/charclient.cpp | 46 + Src/Char Server/charpackets.cpp | 579 +++ Src/Char Server/charserver.cpp | 204 + Src/Char Server/charserver.h | 115 + Src/Char Server/clan.cpp | 1076 ++++ Src/Char Server/community.cpp | 215 + Src/Char Server/datatypes.h | 68 + Src/Char Server/extrafunctions.cpp | 184 + Src/Char Server/main.cpp | 67 + Src/Char Server/signal.cpp | 109 + Src/Common/SocketBase.cpp | 66 + Src/Common/SocketClient.cpp | 216 + Src/Common/SocketServer.cpp | 549 ++ Src/Common/config.cpp | 114 + Src/Common/config.h | 24 + Src/Common/database/database.cpp | 188 + Src/Common/database/database.h | 33 + Src/Common/globals.cpp | 91 + Src/Common/log.cpp | 167 + Src/Common/log.h | 58 + Src/Common/mysql/mysql.h | 739 +++ Src/Common/rosecrypt.cpp | 530 ++ Src/Common/rosecrypt.hpp | 50 + Src/Common/seh.cpp | 29 + Src/Common/seh.hpp | 96 + Src/Common/sockets.h | 438 ++ Src/Login Server/datatype.h | 28 + Src/Login Server/extrafunction.cpp | 21 + Src/Login Server/loginclient.cpp | 37 + Src/Login Server/loginpackets.cpp | 240 + Src/Login Server/loginserver.cpp | 142 + Src/Login Server/loginserver.h | 77 + Src/Login Server/main.cpp | 65 + Src/Login Server/signal.cpp | 109 + Src/World Server/Buff.cpp | 946 ++++ Src/World Server/Copy of battle.cpp | 847 +++ Src/World Server/Copy of charfunctions.cpp | 570 ++ Src/World Server/MainProcess.cpp | 478 ++ Src/World Server/Monster.cpp | 195 + Src/World Server/MonsterDamage.cpp | 21 + Src/World Server/MonsterEvents.cpp | 435 ++ Src/World Server/MonsterStats.cpp | 195 + Src/World Server/Party.cpp | 375 ++ Src/World Server/QuestActions.cpp | 296 ++ Src/World Server/QuestConditions.cpp | 229 + Src/World Server/QuestConditions.h | 200 + Src/World Server/QuestTrigger.h | 61 + Src/World Server/ServerFunctions.cpp | 898 ++++ Src/World Server/Signal.cpp | 113 + Src/World Server/SpawnMonsterCode.txt | 53 + Src/World Server/WorldMonster.cpp | 70 + Src/World Server/WorldMonster.h | 110 + Src/World Server/WorldServer.h | 458 ++ Src/World Server/Worldprocess.cpp | 165 + Src/World Server/aip/AiActions.cpp | 1344 +++++ Src/World Server/aip/AiActions.cpp.bak | 1472 ++++++ Src/World Server/aip/AiActions.h | 247 + Src/World Server/aip/AiConditions.cpp | 1078 ++++ Src/World Server/aip/AiConditions.h | 266 + Src/World Server/aip/Aip.cpp | 167 + Src/World Server/aip/Aip.h | 29 + Src/World Server/battle.cpp | 975 ++++ Src/World Server/battle_old_version.cpp | 802 +++ Src/World Server/character.cpp | 205 + Src/World Server/character.h | 118 + Src/World Server/charevent.cpp | 63 + Src/World Server/charfunctions.cpp | 606 +++ Src/World Server/charstats.cpp | 111 + Src/World Server/chartype.h | 303 ++ Src/World Server/clan.cpp | 257 + Src/World Server/datatypes.h | 1414 +++++ Src/World Server/extrafunctions.cpp | 1173 +++++ Src/World Server/gmcmds.cpp | 3877 ++++++++++++++ Src/World Server/itemdata.cpp | 45 + Src/World Server/ltbhandler.cpp | 168 + Src/World Server/ltbhandler.h | 28 + Src/World Server/main.cpp | 81 + Src/World Server/mapfunctions.cpp | 550 ++ Src/World Server/monsterfunctions.cpp | 151 + Src/World Server/party.h | 52 + Src/World Server/partyfunctions.cpp | 120 + Src/World Server/player.cpp | 519 ++ Src/World Server/player.h | 170 + Src/World Server/playerdata.cpp | 1379 +++++ Src/World Server/playerfunctions.cpp | 1055 ++++ Src/World Server/playerquest.cpp | 2 + Src/World Server/playerstats.cpp | 1975 +++++++ Src/World Server/quest.cpp | 125 + Src/World Server/quest/CIntStb.hpp | 55 + Src/World Server/quest/CRoseArray.hpp | 120 + Src/World Server/quest/CRoseFile.hpp | 941 ++++ Src/World Server/quest/CStrStb.hpp | 82 + Src/World Server/quest/QuestActions.cpp | 1052 ++++ Src/World Server/quest/QuestActions.cpp.wrong | 1138 ++++ Src/World Server/quest/QuestActions.h | 242 + Src/World Server/quest/QuestConditions.cpp | 771 +++ .../quest/QuestConditions.cpp.wrong | 763 +++ Src/World Server/quest/QuestConditions.h | 239 + Src/World Server/quest/QuestTrigger.h | 98 + Src/World Server/quest/Quests.cpp | 157 + Src/World Server/quest/strhashing.cpp | 17 + Src/World Server/quest/strhashing.h | 20 + Src/World Server/questactions.h.wrong | 196 + Src/World Server/startup.cpp | 1297 +++++ Src/World Server/stb_manager.cpp | 48 + Src/World Server/stbhandler.cpp | 53 + Src/World Server/stbhandler.h | 11 + Src/World Server/stlhandler.cpp | 137 + Src/World Server/stlhandler.h | 42 + Src/World Server/strhashing.cpp | 19 + Src/World Server/strhashing.h | 20 + Src/World Server/worldmap.cpp | 379 ++ Src/World Server/worldmap.h | 119 + Src/World Server/worldmap.h.bak | 119 + Src/World Server/worldpackets.cpp | 4598 +++++++++++++++++ Src/World Server/worldserver.cpp | 1075 ++++ 145 files changed, 54574 insertions(+) create mode 100644 Src/3rd_party/mysql/include/my_alloc.h create mode 100644 Src/3rd_party/mysql/include/my_dbug.h create mode 100644 Src/3rd_party/mysql/include/my_getopt.h create mode 100644 Src/3rd_party/mysql/include/my_global.h create mode 100644 Src/3rd_party/mysql/include/my_list.h create mode 100644 Src/3rd_party/mysql/include/my_sys.h create mode 100644 Src/3rd_party/mysql/include/mysql.h create mode 100644 Src/3rd_party/mysql/include/mysql_com.h create mode 100644 Src/3rd_party/mysql/include/mysql_embed.h create mode 100644 Src/3rd_party/mysql/include/mysql_time.h create mode 100644 Src/3rd_party/mysql/include/mysql_version.h create mode 100644 Src/3rd_party/mysql/include/mysqld_ername.h create mode 100644 Src/3rd_party/mysql/include/mysqld_error.h create mode 100644 Src/3rd_party/mysql/include/sched.h create mode 100644 Src/3rd_party/mysql/include/typelib.h create mode 100644 Src/3rd_party/mysql/lib/libmysql.dll create mode 100644 Src/3rd_party/mysql/lib/libmysql.lib create mode 100644 Src/3rd_party/pthread/bin/x64/pthreadGC2.dll create mode 100644 Src/3rd_party/pthread/bin/x64/pthreadVC2.dll create mode 100644 Src/3rd_party/pthread/bin/x86/pthreadGC2.dll create mode 100644 Src/3rd_party/pthread/bin/x86/pthreadGCE2.dll create mode 100644 Src/3rd_party/pthread/bin/x86/pthreadVC2.dll create mode 100644 Src/3rd_party/pthread/bin/x86/pthreadVCE2.dll create mode 100644 Src/3rd_party/pthread/bin/x86/pthreadVSE2.dll create mode 100644 Src/3rd_party/pthread/include/pthread.h create mode 100644 Src/3rd_party/pthread/lib/x64/pthreadVC2.lib create mode 100644 Src/3rd_party/pthread/lib/x86/pthreadVC2.lib create mode 100644 Src/3rd_party/pthread/lib/x86/pthreadVCE2.lib create mode 100644 Src/3rd_party/pthread/lib/x86/pthreadVSE2.lib create mode 100644 Src/Char Server/charclient.cpp create mode 100644 Src/Char Server/charpackets.cpp create mode 100644 Src/Char Server/charserver.cpp create mode 100644 Src/Char Server/charserver.h create mode 100644 Src/Char Server/clan.cpp create mode 100644 Src/Char Server/community.cpp create mode 100644 Src/Char Server/datatypes.h create mode 100644 Src/Char Server/extrafunctions.cpp create mode 100644 Src/Char Server/main.cpp create mode 100644 Src/Char Server/signal.cpp create mode 100644 Src/Common/SocketBase.cpp create mode 100644 Src/Common/SocketClient.cpp create mode 100644 Src/Common/SocketServer.cpp create mode 100644 Src/Common/config.cpp create mode 100644 Src/Common/config.h create mode 100644 Src/Common/database/database.cpp create mode 100644 Src/Common/database/database.h create mode 100644 Src/Common/globals.cpp create mode 100644 Src/Common/log.cpp create mode 100644 Src/Common/log.h create mode 100644 Src/Common/mysql/mysql.h create mode 100644 Src/Common/rosecrypt.cpp create mode 100644 Src/Common/rosecrypt.hpp create mode 100644 Src/Common/seh.cpp create mode 100644 Src/Common/seh.hpp create mode 100644 Src/Common/sockets.h create mode 100644 Src/Login Server/datatype.h create mode 100644 Src/Login Server/extrafunction.cpp create mode 100644 Src/Login Server/loginclient.cpp create mode 100644 Src/Login Server/loginpackets.cpp create mode 100644 Src/Login Server/loginserver.cpp create mode 100644 Src/Login Server/loginserver.h create mode 100644 Src/Login Server/main.cpp create mode 100644 Src/Login Server/signal.cpp create mode 100644 Src/World Server/Buff.cpp create mode 100644 Src/World Server/Copy of battle.cpp create mode 100644 Src/World Server/Copy of charfunctions.cpp create mode 100644 Src/World Server/MainProcess.cpp create mode 100644 Src/World Server/Monster.cpp create mode 100644 Src/World Server/MonsterDamage.cpp create mode 100644 Src/World Server/MonsterEvents.cpp create mode 100644 Src/World Server/MonsterStats.cpp create mode 100644 Src/World Server/Party.cpp create mode 100644 Src/World Server/QuestActions.cpp create mode 100644 Src/World Server/QuestConditions.cpp create mode 100644 Src/World Server/QuestConditions.h create mode 100644 Src/World Server/QuestTrigger.h create mode 100644 Src/World Server/ServerFunctions.cpp create mode 100644 Src/World Server/Signal.cpp create mode 100644 Src/World Server/SpawnMonsterCode.txt create mode 100644 Src/World Server/WorldMonster.cpp create mode 100644 Src/World Server/WorldMonster.h create mode 100644 Src/World Server/WorldServer.h create mode 100644 Src/World Server/Worldprocess.cpp create mode 100644 Src/World Server/aip/AiActions.cpp create mode 100644 Src/World Server/aip/AiActions.cpp.bak create mode 100644 Src/World Server/aip/AiActions.h create mode 100644 Src/World Server/aip/AiConditions.cpp create mode 100644 Src/World Server/aip/AiConditions.h create mode 100644 Src/World Server/aip/Aip.cpp create mode 100644 Src/World Server/aip/Aip.h create mode 100644 Src/World Server/battle.cpp create mode 100644 Src/World Server/battle_old_version.cpp create mode 100644 Src/World Server/character.cpp create mode 100644 Src/World Server/character.h create mode 100644 Src/World Server/charevent.cpp create mode 100644 Src/World Server/charfunctions.cpp create mode 100644 Src/World Server/charstats.cpp create mode 100644 Src/World Server/chartype.h create mode 100644 Src/World Server/clan.cpp create mode 100644 Src/World Server/datatypes.h create mode 100644 Src/World Server/extrafunctions.cpp create mode 100644 Src/World Server/gmcmds.cpp create mode 100644 Src/World Server/itemdata.cpp create mode 100644 Src/World Server/ltbhandler.cpp create mode 100644 Src/World Server/ltbhandler.h create mode 100644 Src/World Server/main.cpp create mode 100644 Src/World Server/mapfunctions.cpp create mode 100644 Src/World Server/monsterfunctions.cpp create mode 100644 Src/World Server/party.h create mode 100644 Src/World Server/partyfunctions.cpp create mode 100644 Src/World Server/player.cpp create mode 100644 Src/World Server/player.h create mode 100644 Src/World Server/playerdata.cpp create mode 100644 Src/World Server/playerfunctions.cpp create mode 100644 Src/World Server/playerquest.cpp create mode 100644 Src/World Server/playerstats.cpp create mode 100644 Src/World Server/quest.cpp create mode 100644 Src/World Server/quest/CIntStb.hpp create mode 100644 Src/World Server/quest/CRoseArray.hpp create mode 100644 Src/World Server/quest/CRoseFile.hpp create mode 100644 Src/World Server/quest/CStrStb.hpp create mode 100644 Src/World Server/quest/QuestActions.cpp create mode 100644 Src/World Server/quest/QuestActions.cpp.wrong create mode 100644 Src/World Server/quest/QuestActions.h create mode 100644 Src/World Server/quest/QuestConditions.cpp create mode 100644 Src/World Server/quest/QuestConditions.cpp.wrong create mode 100644 Src/World Server/quest/QuestConditions.h create mode 100644 Src/World Server/quest/QuestTrigger.h create mode 100644 Src/World Server/quest/Quests.cpp create mode 100644 Src/World Server/quest/strhashing.cpp create mode 100644 Src/World Server/quest/strhashing.h create mode 100644 Src/World Server/questactions.h.wrong create mode 100644 Src/World Server/startup.cpp create mode 100644 Src/World Server/stb_manager.cpp create mode 100644 Src/World Server/stbhandler.cpp create mode 100644 Src/World Server/stbhandler.h create mode 100644 Src/World Server/stlhandler.cpp create mode 100644 Src/World Server/stlhandler.h create mode 100644 Src/World Server/strhashing.cpp create mode 100644 Src/World Server/strhashing.h create mode 100644 Src/World Server/worldmap.cpp create mode 100644 Src/World Server/worldmap.h create mode 100644 Src/World Server/worldmap.h.bak create mode 100644 Src/World Server/worldpackets.cpp create mode 100644 Src/World Server/worldserver.cpp diff --git a/Src/3rd_party/mysql/include/my_alloc.h b/Src/3rd_party/mysql/include/my_alloc.h new file mode 100644 index 0000000..1641b3a --- /dev/null +++ b/Src/3rd_party/mysql/include/my_alloc.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Data structures for mysys/my_alloc.c (root memory allocator) +*/ + +#ifndef _my_alloc_h +#define _my_alloc_h + +#define ALLOC_MAX_BLOCK_TO_DROP 4096 +#define ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP 10 + +typedef struct st_used_mem +{ /* struct for once_alloc (block) */ + struct st_used_mem *next; /* Next block in use */ + unsigned int left; /* memory left in block */ + unsigned int size; /* size of block */ +} USED_MEM; + + +typedef struct st_mem_root +{ + USED_MEM *free; /* blocks with free memory in it */ + USED_MEM *used; /* blocks almost without free memory */ + USED_MEM *pre_alloc; /* preallocated block */ + /* if block have less memory it will be put in 'used' list */ + unsigned int min_malloc; + unsigned int block_size; /* initial block size */ + unsigned int block_num; /* allocated blocks counter */ + /* + first free block in queue test counter (if it exceed + MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list) + */ + unsigned int first_block_usage; + + void (*error_handler)(void); +} MEM_ROOT; +#endif diff --git a/Src/3rd_party/mysql/include/my_dbug.h b/Src/3rd_party/mysql/include/my_dbug.h new file mode 100644 index 0000000..b76a3fc --- /dev/null +++ b/Src/3rd_party/mysql/include/my_dbug.h @@ -0,0 +1,101 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _dbug_h +#define _dbug_h + +#ifdef __cplusplus +extern "C" { +#endif +#if !defined(DBUG_OFF) && !defined(_lint) +extern int _db_on_,_no_db_; +extern FILE *_db_fp_; +extern char *_db_process_; +extern int _db_keyword_(const char *keyword); +extern int _db_strict_keyword_(const char *keyword); +extern void _db_setjmp_(void); +extern void _db_longjmp_(void); +extern void _db_push_(const char *control); +extern void _db_pop_(void); +extern void _db_enter_(const char *_func_,const char *_file_,uint _line_, + const char **_sfunc_,const char **_sfile_, + uint *_slevel_, char ***); +extern void _db_return_(uint _line_,const char **_sfunc_,const char **_sfile_, + uint *_slevel_); +extern void _db_pargs_(uint _line_,const char *keyword); +extern void _db_doprnt_ _VARARGS((const char *format,...)); +extern void _db_dump_(uint _line_,const char *keyword,const char *memory, + uint length); +extern void _db_output_(uint flag); +extern void _db_lock_file(void); +extern void _db_unlock_file(void); + +#define DBUG_ENTER(a) const char *_db_func_, *_db_file_; uint _db_level_; \ + char **_db_framep_; \ + _db_enter_ (a,__FILE__,__LINE__,&_db_func_,&_db_file_,&_db_level_, \ + &_db_framep_) +#define DBUG_LEAVE \ + (_db_return_ (__LINE__, &_db_func_, &_db_file_, &_db_level_)) +#define DBUG_RETURN(a1) {DBUG_LEAVE; return(a1);} +#define DBUG_VOID_RETURN {DBUG_LEAVE; return;} +#define DBUG_EXECUTE(keyword,a1) \ + {if (_db_on_) {if (_db_keyword_ (keyword)) { a1 }}} +#define DBUG_PRINT(keyword,arglist) \ + {if (_db_on_) {_db_pargs_(__LINE__,keyword); _db_doprnt_ arglist;}} +#define DBUG_PUSH(a1) _db_push_ (a1) +#define DBUG_POP() _db_pop_ () +#define DBUG_PROCESS(a1) (_db_process_ = a1) +#define DBUG_FILE (_db_fp_) +#define DBUG_SETJMP(a1) (_db_setjmp_ (), setjmp (a1)) +#define DBUG_LONGJMP(a1,a2) (_db_longjmp_ (), longjmp (a1, a2)) +#define DBUG_DUMP(keyword,a1,a2)\ + {if (_db_on_) {_db_dump_(__LINE__,keyword,a1,a2);}} +#define DBUG_IN_USE (_db_fp_ && _db_fp_ != stderr) +#define DEBUGGER_OFF _no_db_=1;_db_on_=0; +#define DEBUGGER_ON _no_db_=0 +#define DBUG_LOCK_FILE { _db_lock_file(); } +#define DBUG_UNLOCK_FILE { _db_unlock_file(); } +#define DBUG_OUTPUT(A) { _db_output_(A); } +#define DBUG_ASSERT(A) assert(A) +#define DBUG_EXECUTE_IF(keyword,a1) \ + {if (_db_on_) {if (_db_strict_keyword_ (keyword)) { a1 }}} +#else /* No debugger */ + +#define DBUG_ENTER(a1) +#define DBUG_RETURN(a1) return(a1) +#define DBUG_VOID_RETURN return +#define DBUG_EXECUTE(keyword,a1) {} +#define DBUG_EXECUTE_IF(keyword,a1) {} +#define DBUG_PRINT(keyword,arglist) {} +#define DBUG_PUSH(a1) {} +#define DBUG_POP() {} +#define DBUG_PROCESS(a1) {} +#define DBUG_FILE (stderr) +#define DBUG_SETJMP setjmp +#define DBUG_LONGJMP longjmp +#define DBUG_DUMP(keyword,a1,a2) {} +#define DBUG_IN_USE 0 +#define DEBUGGER_OFF +#define DEBUGGER_ON +#define DBUG_LOCK_FILE +#define DBUG_UNLOCK_FILE +#define DBUG_OUTPUT(A) +#define DBUG_ASSERT(A) {} +#endif +#ifdef __cplusplus +} +#endif +#endif diff --git a/Src/3rd_party/mysql/include/my_getopt.h b/Src/3rd_party/mysql/include/my_getopt.h new file mode 100644 index 0000000..e6ca113 --- /dev/null +++ b/Src/3rd_party/mysql/include/my_getopt.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _my_getopt_h +#define _my_getopt_h + +C_MODE_START + +#define GET_NO_ARG 1 +#define GET_BOOL 2 +#define GET_INT 3 +#define GET_UINT 4 +#define GET_LONG 5 +#define GET_ULONG 6 +#define GET_LL 7 +#define GET_ULL 8 +#define GET_STR 9 +#define GET_STR_ALLOC 10 +#define GET_DISABLED 11 + +#define GET_ASK_ADDR 128 +#define GET_TYPE_MASK 127 + +enum get_opt_arg_type { NO_ARG, OPT_ARG, REQUIRED_ARG }; + +struct my_option +{ + const char *name; /* Name of the option */ + int id; /* unique id or short option */ + const char *comment; /* option comment, for autom. --help */ + gptr *value; /* The variable value */ + gptr *u_max_value; /* The user def. max variable value */ + const char **str_values; /* Pointer to possible values */ + ulong var_type; + enum get_opt_arg_type arg_type; + longlong def_value; /* Default value */ + longlong min_value; /* Min allowed value */ + longlong max_value; /* Max allowed value */ + longlong sub_size; /* Subtract this from given value */ + long block_size; /* Value should be a mult. of this */ + int app_type; /* To be used by an application */ +}; + +typedef my_bool (* my_get_one_option) (int, const struct my_option *, char * ); +typedef void (* my_error_reporter) (enum loglevel level, const char *format, ... ); + +extern char *disabled_my_option; +extern my_bool my_getopt_print_errors; +extern my_error_reporter my_getopt_error_reporter; + +extern int handle_options (int *argc, char ***argv, + const struct my_option *longopts, my_get_one_option); +extern void my_print_help(const struct my_option *options); +extern void my_print_variables(const struct my_option *options); +extern void my_getopt_register_get_addr(gptr* (*func_addr)(const char *, uint, + const struct my_option *)); + +ulonglong getopt_ull_limit_value(ulonglong num, const struct my_option *optp); +my_bool getopt_compare_strings(const char *s, const char *t, uint length); + +C_MODE_END + +#endif /* _my_getopt_h */ + diff --git a/Src/3rd_party/mysql/include/my_global.h b/Src/3rd_party/mysql/include/my_global.h new file mode 100644 index 0000000..b32a8fe --- /dev/null +++ b/Src/3rd_party/mysql/include/my_global.h @@ -0,0 +1,1276 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* This is the include file that should be included 'first' in every C file. */ + +#ifndef _global_h +#define _global_h + +#ifndef EMBEDDED_LIBRARY +#define HAVE_REPLICATION +#define HAVE_EXTERNAL_CLIENT +#endif + +#if defined( __EMX__) && !defined( MYSQL_SERVER) +/* moved here to use below VOID macro redefinition */ +#define INCL_BASE +#define INCL_NOPMAPI +#include +#endif /* __EMX__ */ + +#ifdef __CYGWIN__ +/* We use a Unix API, so pretend it's not Windows */ +#undef WIN +#undef WIN32 +#undef _WIN +#undef _WIN32 +#undef _WIN64 +#undef __WIN__ +#undef __WIN32__ +#define HAVE_ERRNO_AS_DEFINE +#endif /* __CYGWIN__ */ + +/* to make command line shorter we'll define USE_PRAGMA_INTERFACE here */ +#ifdef USE_PRAGMA_IMPLEMENTATION +#define USE_PRAGMA_INTERFACE +#endif + +#if defined(i386) && !defined(__i386__) +#define __i386__ +#endif + +/* Macros to make switching between C and C++ mode easier */ +#ifdef __cplusplus +#define C_MODE_START extern "C" { +#define C_MODE_END } +#else +#define C_MODE_START +#define C_MODE_END +#endif + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +#include +#elif defined(OS2) +#include +#elif defined(__NETWARE__) +#include +#include +#if defined(__cplusplus) && defined(inline) +#undef inline /* fix configure problem */ +#endif +#else +#include +#if defined(__cplusplus) && defined(inline) +#undef inline /* fix configure problem */ +#endif +#endif /* _WIN32... */ + +/* Some defines to avoid ifdefs in the code */ +#ifndef NETWARE_YIELD +#define NETWARE_YIELD +#define NETWARE_SET_SCREEN_MODE(A) +#endif + +/* + The macros below are borrowed from include/linux/compiler.h in the + Linux kernel. Use them to indicate the likelyhood of the truthfulness + of a condition. This serves two purposes - newer versions of gcc will be + able to optimize for branch predication, which could yield siginficant + performance gains in frequently executed sections of the code, and the + other reason to use them is for documentation +*/ + +#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) +#define __builtin_expect(x, expected_value) (x) +#endif + +#define likely(x) __builtin_expect((x),1) +#define unlikely(x) __builtin_expect((x),0) + + +/* Fix problem with S_ISLNK() on Linux */ +#if defined(TARGET_OS_LINUX) +#undef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +/* + Temporary solution to solve bug#7156. Include "sys/types.h" before + the thread headers, else the function madvise() will not be defined +*/ +#if defined(HAVE_SYS_TYPES_H) && ( defined(sun) || defined(__sun) ) +#include +#endif + +/* The client defines this to avoid all thread code */ +#if defined(UNDEF_THREADS_HACK) +#undef THREAD +#undef HAVE_mit_thread +#undef HAVE_LINUXTHREADS +#undef HAVE_NPTL +#undef HAVE_UNIXWARE7_THREADS +#endif + +#ifdef HAVE_THREADS_WITHOUT_SOCKETS +/* MIT pthreads does not work with unix sockets */ +#undef HAVE_SYS_UN_H +#endif + +#define __EXTENSIONS__ 1 /* We want some extension */ +#ifndef __STDC_EXT__ +#define __STDC_EXT__ 1 /* To get large file support on hpux */ +#endif + +/* + Solaris 9 include file refers to X/Open document + + System Interfaces and Headers, Issue 5 + + saying we should define _XOPEN_SOURCE=500 to get POSIX.1c prototypes, + but apparently other systems (namely FreeBSD) don't agree. + + On a newer Solaris 10, the above file recognizes also _XOPEN_SOURCE=600. + Furthermore, it tests that if a program requires older standard + (_XOPEN_SOURCE<600 or _POSIX_C_SOURCE<200112L) it cannot be + run on a new compiler (that defines _STDC_C99) and issues an #error. + It's also an #error if a program requires new standard (_XOPEN_SOURCE=600 + or _POSIX_C_SOURCE=200112L) and a compiler does not define _STDC_C99. + + To add more to this mess, Sun Studio C compiler defines _STDC_C99 while + C++ compiler does not! + + So, in a desperate attempt to get correct prototypes for both + C and C++ code, we define either _XOPEN_SOURCE=600 or _XOPEN_SOURCE=500 + depending on the compiler's announced C standard support. + + Cleaner solutions are welcome. +*/ +#ifdef __sun +#if __STDC_VERSION__ - 0 >= 199901L +#define _XOPEN_SOURCE 600 +#else +#define _XOPEN_SOURCE 500 +#endif +#endif + +#if defined(THREAD) && !defined(__WIN__) && !defined(OS2) +#ifndef _POSIX_PTHREAD_SEMANTICS +#define _POSIX_PTHREAD_SEMANTICS /* We want posix threads */ +#endif + +#if !defined(SCO) +#define _REENTRANT 1 /* Some thread libraries require this */ +#endif +#if !defined(_THREAD_SAFE) && !defined(_AIX) +#define _THREAD_SAFE /* Required for OSF1 */ +#endif +#ifndef HAVE_mit_thread +#ifdef HAVE_UNIXWARE7_THREADS +#include +#else +#if defined(HPUX10) || defined(HPUX11) +C_MODE_START /* HPUX needs this, signal.h bug */ +#include +C_MODE_END +#else +#include /* AIX must have this included first */ +#endif +#endif /* HAVE_UNIXWARE7_THREADS */ +#endif /* HAVE_mit_thread */ +#if !defined(SCO) && !defined(_REENTRANT) +#define _REENTRANT 1 /* Threads requires reentrant code */ +#endif +#endif /* THREAD */ + +/* Go around some bugs in different OS and compilers */ +#ifdef _AIX /* By soren@t.dk */ +#define _H_STRINGS +#define _SYS_STREAM_H +/* #define _AIX32_CURSES */ /* XXX: this breaks AIX 4.3.3 (others?). */ +#define ulonglong2double(A) my_ulonglong2double(A) +#define my_off_t2double(A) my_ulonglong2double(A) +C_MODE_START +double my_ulonglong2double(unsigned long long A); +C_MODE_END +#endif /* _AIX */ + +#ifdef HAVE_BROKEN_SNPRINTF /* HPUX 10.20 don't have this defined */ +#undef HAVE_SNPRINTF +#endif +#ifdef HAVE_BROKEN_PREAD +/* + pread()/pwrite() are not 64 bit safe on HP-UX 11.0 without + installing the kernel patch PHKL_20349 or greater +*/ +#undef HAVE_PREAD +#undef HAVE_PWRITE +#endif +#if defined(HAVE_BROKEN_INLINE) && !defined(__cplusplus) +#undef inline +#define inline +#endif + +#ifdef UNDEF_HAVE_GETHOSTBYNAME_R /* For OSF4.x */ +#undef HAVE_GETHOSTBYNAME_R +#endif +#ifdef UNDEF_HAVE_INITGROUPS /* For AIX 4.3 */ +#undef HAVE_INITGROUPS +#endif + +/* gcc/egcs issues */ + +#if defined(__GNUC) && defined(__EXCEPTIONS) +#error "Please add -fno-exceptions to CXXFLAGS and reconfigure/recompile" +#endif + + +/* Fix a bug in gcc 2.8.0 on IRIX 6.2 */ +#if SIZEOF_LONG == 4 && defined(__LONG_MAX__) && (__GNUC__ == 2 && __GNUC_MINOR__ == 8) +#undef __LONG_MAX__ /* Is a longlong value in gcc 2.8.0 ??? */ +#define __LONG_MAX__ 2147483647 +#endif + +/* egcs 1.1.2 has a problem with memcpy on Alpha */ +#if defined(__GNUC__) && defined(__alpha__) && ! (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)) +#define BAD_MEMCPY +#endif + +#if defined(_lint) && !defined(lint) +#define lint +#endif +#if SIZEOF_LONG_LONG > 4 && !defined(_LONG_LONG) +#define _LONG_LONG 1 /* For AIX string library */ +#endif + +#ifndef stdin +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDDEF_H +#include +#endif + +#include +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_FLOAT_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_TIMEB_H +#include /* Avoid warnings on SCO */ +#endif +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif /* TIME_WITH_SYS_TIME */ +#ifdef HAVE_UNISTD_H +#include +#endif +#if defined(__cplusplus) && defined(NO_CPLUSPLUS_ALLOCA) +#undef HAVE_ALLOCA +#undef HAVE_ALLOCA_H +#endif +#ifdef HAVE_ALLOCA_H +#include +#endif +#ifdef HAVE_ATOMIC_ADD +#define new my_arg_new +#define need_to_restore_new 1 +C_MODE_START +#include +C_MODE_END +#ifdef need_to_restore_new /* probably safer than #ifdef new */ +#undef new +#undef need_to_restore_new +#endif +#endif +#include /* Recommended by debian */ +/* We need the following to go around a problem with openssl on solaris */ +#if defined(HAVE_CRYPT_H) +#include +#endif + +/* + A lot of our programs uses asserts, so better to always include it + This also fixes a problem when people uses DBUG_ASSERT without including + assert.h +*/ +#include + +/* Go around some bugs in different OS and compilers */ +#if defined(_HPUX_SOURCE) && defined(HAVE_SYS_STREAM_H) +#include /* HPUX 10.20 defines ulong here. UGLY !!! */ +#define HAVE_ULONG +#endif +#ifdef DONT_USE_FINITE /* HPUX 11.x has is_finite() */ +#undef HAVE_FINITE +#endif +#if defined(HPUX10) && defined(_LARGEFILE64_SOURCE) && defined(THREAD) +/* Fix bug in setrlimit */ +#undef setrlimit +#define setrlimit cma_setrlimit64 +#endif +/* Declare madvise where it is not declared for C++, like Solaris */ +#if HAVE_MADVISE && !HAVE_DECL_MADVISE && defined(__cplusplus) +extern "C" int madvise(void *addr, size_t len, int behav); +#endif + +#ifdef __QNXNTO__ +/* This has to be after include limits.h */ +#define HAVE_ERRNO_AS_DEFINE +#define HAVE_FCNTL_LOCK +#undef HAVE_FINITE +#undef LONGLONG_MIN /* These get wrongly defined in QNX 6.2 */ +#undef LONGLONG_MAX /* standard system library 'limits.h' */ +#ifdef __cplusplus +#ifndef HAVE_RINT +#define HAVE_RINT +#endif /* rint() and isnan() functions are not */ +#define rint(a) std::rint(a) /* visible in C++ scope due to an error */ +#define isnan(a) std::isnan(a) /* in the usr/include/math.h on QNX */ +#endif +#endif + +/* We can not live without the following defines */ + +#define USE_MYFUNC 1 /* Must use syscall indirection */ +#define MASTER 1 /* Compile without unireg */ +#define ENGLISH 1 /* Messages in English */ +#define POSIX_MISTAKE 1 /* regexp: Fix stupid spec error */ +#define USE_REGEX 1 /* We want the use the regex library */ +/* Do not define for ultra sparcs */ +#ifndef OS2 +#define USE_BMOVE512 1 /* Use this unless system bmove is faster */ +#endif + +#define QUOTE_ARG(x) #x /* Quote argument (before cpp) */ +#define STRINGIFY_ARG(x) QUOTE_ARG(x) /* Quote argument, after cpp */ + +/* Paranoid settings. Define I_AM_PARANOID if you are paranoid */ +#ifdef I_AM_PARANOID +#define DONT_ALLOW_USER_CHANGE 1 +#define DONT_USE_MYSQL_PWD 1 +#endif + +/* Does the system remember a signal handler after a signal ? */ +#ifndef HAVE_BSD_SIGNALS +#define DONT_REMEMBER_SIGNAL +#endif + +/* Define void to stop lint from generating "null effekt" comments */ +#ifndef DONT_DEFINE_VOID +#ifdef _lint +int __void__; +#define VOID(X) (__void__ = (int) (X)) +#else +#undef VOID +#define VOID(X) (X) +#endif +#endif /* DONT_DEFINE_VOID */ + +#if defined(_lint) || defined(FORCE_INIT_OF_VARS) +#define LINT_INIT(var) var=0 /* No uninitialize-warning */ +#else +#define LINT_INIT(var) +#endif + +#if defined(_lint) || defined(FORCE_INIT_OF_VARS) || defined(HAVE_purify) +#define PURIFY_OR_LINT_INIT(var) var=0 +#else +#define PURIFY_OR_LINT_INIT(var) +#endif + +/* Define some useful general macros */ +#if !defined(max) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#if defined(__EMX__) || !defined(HAVE_UINT) +#undef HAVE_UINT +#define HAVE_UINT +typedef unsigned int uint; +typedef unsigned short ushort; +#endif + +#define CMP_NUM(a,b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1) +#define sgn(a) (((a) < 0) ? -1 : ((a) > 0) ? 1 : 0) +#define swap_variables(t, a, b) { register t dummy; dummy= a; a= b; b= dummy; } +#define test(a) ((a) ? 1 : 0) +#define set_if_bigger(a,b) do { if ((a) < (b)) (a)=(b); } while(0) +#define set_if_smaller(a,b) do { if ((a) > (b)) (a)=(b); } while(0) +#define test_all_bits(a,b) (((a) & (b)) == (b)) +#define set_bits(type, bit_count) (sizeof(type)*8 <= (bit_count) ? ~(type) 0 : ((((type) 1) << (bit_count)) - (type) 1)) +#define array_elements(A) ((uint) (sizeof(A)/sizeof(A[0]))) +#ifndef HAVE_RINT +#define rint(A) floor((A)+(((A) < 0)? -0.5 : 0.5)) +#endif + +/* Define some general constants */ +#ifndef TRUE +#define TRUE (1) /* Logical true */ +#define FALSE (0) /* Logical false */ +#endif + +#if defined(__GNUC__) +#define function_volatile volatile +#define my_reinterpret_cast(A) reinterpret_cast +#define my_const_cast(A) const_cast +#elif !defined(my_reinterpret_cast) +#define my_reinterpret_cast(A) (A) +#define my_const_cast(A) (A) +#endif +#if !defined(__attribute__) && (defined(__cplusplus) || !defined(__GNUC__) || __GNUC__ == 2 && __GNUC_MINOR__ < 8) +#define __attribute__(A) +#endif + +/* From old s-system.h */ + +/* + Support macros for non ansi & other old compilers. Since such + things are no longer supported we do nothing. We keep then since + some of our code may still be needed to upgrade old customers. +*/ +#define _VARARGS(X) X +#define _STATIC_VARARGS(X) X +#define _PC(X) X + +#if defined(DBUG_ON) && defined(DBUG_OFF) +#undef DBUG_OFF +#endif + +#if defined(_lint) && !defined(DBUG_OFF) +#define DBUG_OFF +#endif + +#include + +#define MIN_ARRAY_SIZE 0 /* Zero or One. Gcc allows zero*/ +#define ASCII_BITS_USED 8 /* Bit char used */ +#define NEAR_F /* No near function handling */ + +/* Some types that is different between systems */ + +typedef int File; /* File descriptor */ +#ifndef Socket_defined +typedef int my_socket; /* File descriptor for sockets */ +#define INVALID_SOCKET -1 +#endif +/* Type for fuctions that handles signals */ +#define sig_handler RETSIGTYPE +C_MODE_START +typedef void (*sig_return)();/* Returns type from signal */ +C_MODE_END +#if defined(__GNUC__) && !defined(_lint) +typedef char pchar; /* Mixed prototypes can take char */ +typedef char puchar; /* Mixed prototypes can take char */ +typedef char pbool; /* Mixed prototypes can take char */ +typedef short pshort; /* Mixed prototypes can take short int */ +typedef float pfloat; /* Mixed prototypes can take float */ +#else +typedef int pchar; /* Mixed prototypes can't take char */ +typedef uint puchar; /* Mixed prototypes can't take char */ +typedef int pbool; /* Mixed prototypes can't take char */ +typedef int pshort; /* Mixed prototypes can't take short int */ +typedef double pfloat; /* Mixed prototypes can't take float */ +#endif +C_MODE_START +typedef int (*qsort_cmp)(const void *,const void *); +typedef int (*qsort_cmp2)(void*, const void *,const void *); +C_MODE_END +#ifdef HAVE_mit_thread +#define qsort_t void +#undef QSORT_TYPE_IS_VOID +#define QSORT_TYPE_IS_VOID +#else +#define qsort_t RETQSORTTYPE /* Broken GCC cant handle typedef !!!! */ +#endif +#ifdef HAVE_mit_thread +#define size_socket socklen_t /* Type of last arg to accept */ +#else +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +typedef SOCKET_SIZE_TYPE size_socket; +#endif + +#ifndef SOCKOPT_OPTLEN_TYPE +#define SOCKOPT_OPTLEN_TYPE size_socket +#endif + +/* file create flags */ + +#ifndef O_SHARE /* Probably not windows */ +#define O_SHARE 0 /* Flag to my_open for shared files */ +#ifndef O_BINARY +#define O_BINARY 0 /* Flag to my_open for binary files */ +#endif +#ifndef FILE_BINARY +#define FILE_BINARY O_BINARY /* Flag to my_fopen for binary streams */ +#endif +#ifdef HAVE_FCNTL +#define HAVE_FCNTL_LOCK +#define F_TO_EOF 0L /* Param to lockf() to lock rest of file */ +#endif +#endif /* O_SHARE */ + +#ifndef O_TEMPORARY +#define O_TEMPORARY 0 +#endif +#ifndef O_SHORT_LIVED +#define O_SHORT_LIVED 0 +#endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + +/* #define USE_RECORD_LOCK */ + + /* Unsigned types supported by the compiler */ +#define UNSINT8 /* unsigned int8 (char) */ +#define UNSINT16 /* unsigned int16 */ +#define UNSINT32 /* unsigned int32 */ + + /* General constants */ +#define SC_MAXWIDTH 256 /* Max width of screen (for error messages) */ +#define FN_LEN 256 /* Max file name len */ +#define FN_HEADLEN 253 /* Max length of filepart of file name */ +#define FN_EXTLEN 20 /* Max length of extension (part of FN_LEN) */ +#ifdef PATH_MAX +#define FN_REFLEN PATH_MAX/* Max length of full path-name */ +#else +#define FN_REFLEN 512 /* Max length of full path-name */ +#endif +#define FN_EXTCHAR '.' +#define FN_HOMELIB '~' /* ~/ is used as abbrev for home dir */ +#define FN_CURLIB '.' /* ./ is used as abbrev for current dir */ +#define FN_PARENTDIR ".." /* Parent directory; Must be a string */ +#define FN_DEVCHAR ':' + +#ifndef FN_LIBCHAR +#ifdef __EMX__ +#define FN_LIBCHAR '\\' +#define FN_ROOTDIR "\\" +#else +#define FN_LIBCHAR '/' +#define FN_ROOTDIR "/" +#endif +#endif +#define MY_NFILE 64 /* This is only used to save filenames */ +#ifndef OS_FILE_LIMIT +#define OS_FILE_LIMIT 65535 +#endif + +/* #define EXT_IN_LIBNAME */ +/* #define FN_NO_CASE_SENCE */ +/* #define FN_UPPER_CASE TRUE */ + +/* + Io buffer size; Must be a power of 2 and a multiple of 512. May be + smaller what the disk page size. This influences the speed of the + isam btree library. eg to big to slow. +*/ +#define IO_SIZE 4096 +/* + How much overhead does malloc have. The code often allocates + something like 1024-MALLOC_OVERHEAD bytes +*/ +#ifdef SAFEMALLOC +#define MALLOC_OVERHEAD (8+24+4) +#else +#define MALLOC_OVERHEAD 8 +#endif + /* get memory in huncs */ +#define ONCE_ALLOC_INIT (uint) (4096-MALLOC_OVERHEAD) + /* Typical record cash */ +#define RECORD_CACHE_SIZE (uint) (64*1024-MALLOC_OVERHEAD) + /* Typical key cash */ +#define KEY_CACHE_SIZE (uint) (8*1024*1024-MALLOC_OVERHEAD) + /* Default size of a key cache block */ +#define KEY_CACHE_BLOCK_SIZE (uint) 1024 + + + /* Some things that this system doesn't have */ + +#define NO_HASH /* Not needed anymore */ +#ifdef __WIN__ +#define NO_DIR_LIBRARY /* Not standar dir-library */ +#define USE_MY_STAT_STRUCT /* For my_lib */ +#endif + +/* Some defines of functions for portability */ + +#undef remove /* Crashes MySQL on SCO 5.0.0 */ +#ifndef __WIN__ +#ifdef OS2 +#define closesocket(A) soclose(A) +#else +#define closesocket(A) close(A) +#endif +#ifndef ulonglong2double +#define ulonglong2double(A) ((double) (ulonglong) (A)) +#define my_off_t2double(A) ((double) (my_off_t) (A)) +#endif +#endif + +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif +#define ulong_to_double(X) ((double) (ulong) (X)) +#define SET_STACK_SIZE(X) /* Not needed on real machines */ + +#if !defined(HAVE_mit_thread) && !defined(HAVE_STRTOK_R) +#define strtok_r(A,B,C) strtok((A),(B)) +#endif + +/* Remove some things that mit_thread break or doesn't support */ +#if defined(HAVE_mit_thread) && defined(THREAD) +#undef HAVE_PREAD +#undef HAVE_REALPATH +#undef HAVE_MLOCK +#undef HAVE_TEMPNAM /* Use ours */ +#undef HAVE_PTHREAD_SETPRIO +#undef HAVE_FTRUNCATE +#undef HAVE_READLINK +#endif + +/* This is from the old m-machine.h file */ + +#if SIZEOF_LONG_LONG > 4 +#define HAVE_LONG_LONG 1 +#endif + +/* + Some pre-ANSI-C99 systems like AIX 5.1 and Linux/GCC 2.95 define + ULONGLONG_MAX, LONGLONG_MIN, LONGLONG_MAX; we use them if they're defined. + Also on Windows we define these constants by hand in config-win.h. +*/ + +#if defined(HAVE_LONG_LONG) && !defined(LONGLONG_MIN) +#define LONGLONG_MIN ((long long) 0x8000000000000000LL) +#define LONGLONG_MAX ((long long) 0x7FFFFFFFFFFFFFFFLL) +#endif + +#if defined(HAVE_LONG_LONG) && !defined(ULONGLONG_MAX) +/* First check for ANSI C99 definition: */ +#ifdef ULLONG_MAX +#define ULONGLONG_MAX ULLONG_MAX +#else +#define ULONGLONG_MAX ((unsigned long long)(~0ULL)) +#endif +#endif /* defined (HAVE_LONG_LONG) && !defined(ULONGLONG_MAX)*/ + +#define INT_MIN32 (~0x7FFFFFFFL) +#define INT_MAX32 0x7FFFFFFFL +#define UINT_MAX32 0xFFFFFFFFL +#define INT_MIN24 (~0x007FFFFF) +#define INT_MAX24 0x007FFFFF +#define UINT_MAX24 0x00FFFFFF +#define INT_MIN16 (~0x7FFF) +#define INT_MAX16 0x7FFF +#define UINT_MAX16 0xFFFF +#define INT_MIN8 (~0x7F) +#define INT_MAX8 0x7F +#define UINT_MAX8 0xFF + +/* From limits.h instead */ +#ifndef DBL_MIN +#define DBL_MIN 4.94065645841246544e-324 +#define FLT_MIN ((float)1.40129846432481707e-45) +#endif +#ifndef DBL_MAX +#define DBL_MAX 1.79769313486231470e+308 +#define FLT_MAX ((float)3.40282346638528860e+38) +#endif + +#if !defined(HAVE_ISINF) && !defined(isinf) +#define isinf(X) 0 +#endif + +/* Define missing math constants. */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef M_E +#define M_E 2.7182818284590452354 +#endif +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 +#endif + +/* + Max size that must be added to a so that we know Size to make + adressable obj. +*/ +#if SIZEOF_CHARP == 4 +typedef long my_ptrdiff_t; +#else +typedef long long my_ptrdiff_t; +#endif + +#define MY_ALIGN(A,L) (((A) + (L) - 1) & ~((L) - 1)) +#define ALIGN_SIZE(A) MY_ALIGN((A),sizeof(double)) +/* Size to make adressable obj. */ +#define ALIGN_PTR(A, t) ((t*) MY_ALIGN((A),sizeof(t))) + /* Offset of field f in structure t */ +#define OFFSET(t, f) ((size_t)(char *)&((t *)0)->f) +#define ADD_TO_PTR(ptr,size,type) (type) ((byte*) (ptr)+size) +#define PTR_BYTE_DIFF(A,B) (my_ptrdiff_t) ((byte*) (A) - (byte*) (B)) + +#define NullS (char *) 0 +/* Nowdays we do not support MessyDos */ +#ifndef NEAR +#define NEAR /* Who needs segments ? */ +#define FAR /* On a good machine */ +#ifndef HUGE_PTR +#define HUGE_PTR +#endif +#endif +#if defined(__IBMC__) || defined(__IBMCPP__) +/* This was _System _Export but caused a lot of warnings on _AIX43 */ +#define STDCALL +#elif !defined( STDCALL) +#define STDCALL +#endif + +/* Typdefs for easyier portability */ + +#if defined(VOIDTYPE) +typedef void *gptr; /* Generic pointer */ +#else +typedef char *gptr; /* Generic pointer */ +#endif +#ifndef HAVE_INT_8_16_32 +typedef signed char int8; /* Signed integer >= 8 bits */ +typedef short int16; /* Signed integer >= 16 bits */ +#endif +#ifndef HAVE_UCHAR +typedef unsigned char uchar; /* Short for unsigned char */ +#endif +typedef unsigned char uint8; /* Short for unsigned integer >= 8 bits */ +typedef unsigned short uint16; /* Short for unsigned integer >= 16 bits */ + +#if SIZEOF_INT == 4 +#ifndef HAVE_INT_8_16_32 +typedef int int32; +#endif +typedef unsigned int uint32; /* Short for unsigned integer >= 32 bits */ +#elif SIZEOF_LONG == 4 +#ifndef HAVE_INT_8_16_32 +typedef long int32; +#endif +typedef unsigned long uint32; /* Short for unsigned integer >= 32 bits */ +#else +#error "Neither int or long is of 4 bytes width" +#endif + +#if !defined(HAVE_ULONG) && !defined(TARGET_OS_LINUX) && !defined(__USE_MISC) +typedef unsigned long ulong; /* Short for unsigned long */ +#endif +#ifndef longlong_defined +#if defined(HAVE_LONG_LONG) && SIZEOF_LONG != 8 +typedef unsigned long long int ulonglong; /* ulong or unsigned long long */ +typedef long long int longlong; +#else +typedef unsigned long ulonglong; /* ulong or unsigned long long */ +typedef long longlong; +#endif +#endif + +#if defined(NO_CLIENT_LONG_LONG) +typedef unsigned long my_ulonglong; +#elif defined (__WIN__) +typedef unsigned __int64 my_ulonglong; +#else +typedef unsigned long long my_ulonglong; +#endif + +#ifdef USE_RAID +/* + The following is done with a if to not get problems with pre-processors + with late define evaluation +*/ +#if SIZEOF_OFF_T == 4 +#define SYSTEM_SIZEOF_OFF_T 4 +#else +#define SYSTEM_SIZEOF_OFF_T 8 +#endif +#undef SIZEOF_OFF_T +#define SIZEOF_OFF_T 8 +#else +#define SYSTEM_SIZEOF_OFF_T SIZEOF_OFF_T +#endif /* USE_RAID */ + +#if SIZEOF_OFF_T > 4 +typedef ulonglong my_off_t; +#else +typedef unsigned long my_off_t; +#endif +#define MY_FILEPOS_ERROR (~(my_off_t) 0) +#if !defined(__WIN__) && !defined(OS2) +typedef off_t os_off_t; +#endif + +#if defined(__WIN__) +#define socket_errno WSAGetLastError() +#define SOCKET_EINTR WSAEINTR +#define SOCKET_EAGAIN WSAEINPROGRESS +#define SOCKET_ETIMEDOUT WSAETIMEDOUT +#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK +#define SOCKET_ENFILE ENFILE +#define SOCKET_EMFILE EMFILE +#elif defined(OS2) +#define socket_errno sock_errno() +#define SOCKET_EINTR SOCEINTR +#define SOCKET_EAGAIN SOCEINPROGRESS +#define SOCKET_ETIMEDOUT SOCKET_EINTR +#define SOCKET_EWOULDBLOCK SOCEWOULDBLOCK +#define SOCKET_ENFILE SOCENFILE +#define SOCKET_EMFILE SOCEMFILE +#define closesocket(A) soclose(A) +#else /* Unix */ +#define socket_errno errno +#define closesocket(A) close(A) +#define SOCKET_EINTR EINTR +#define SOCKET_EAGAIN EAGAIN +#define SOCKET_ETIMEDOUT SOCKET_EINTR +#define SOCKET_EWOULDBLOCK EWOULDBLOCK +#define SOCKET_ENFILE ENFILE +#define SOCKET_EMFILE EMFILE +#endif + +typedef uint8 int7; /* Most effective integer 0 <= x <= 127 */ +typedef short int15; /* Most effective integer 0 <= x <= 32767 */ +typedef char *my_string; /* String of characters */ +typedef unsigned long size_s; /* Size of strings (In string-funcs) */ +typedef int myf; /* Type of MyFlags in my_funcs */ +#ifndef byte_defined +typedef char byte; /* Smallest addressable unit */ +#endif +typedef char my_bool; /* Small bool */ +#if !defined(bool) && !defined(bool_defined) && (!defined(HAVE_BOOL) || !defined(__cplusplus)) +typedef char bool; /* Ordinary boolean values 0 1 */ +#endif + /* Macros for converting *constants* to the right type */ +#define INT8(v) (int8) (v) +#define INT16(v) (int16) (v) +#define INT32(v) (int32) (v) +#define MYF(v) (myf) (v) + +#ifndef LL +#ifdef HAVE_LONG_LONG +#define LL(A) A ## LL +#else +#define LL(A) A ## L +#endif +#endif + +#ifndef ULL +#ifdef HAVE_LONG_LONG +#define ULL(A) A ## ULL +#else +#define ULL(A) A ## UL +#endif +#endif + +/* + Defines to make it possible to prioritize register assignments. No + longer that important with modern compilers. +*/ +#ifndef USING_X +#define reg1 register +#define reg2 register +#define reg3 register +#define reg4 register +#define reg5 register +#define reg6 register +#define reg7 register +#define reg8 register +#define reg9 register +#define reg10 register +#define reg11 register +#define reg12 register +#define reg13 register +#define reg14 register +#define reg15 register +#define reg16 register +#endif + +/* + Sometimes we want to make sure that the variable is not put into + a register in debugging mode so we can see its value in the core +*/ + +#ifndef DBUG_OFF +#define dbug_volatile volatile +#else +#define dbug_volatile +#endif + +/* Defines for time function */ +#define SCALE_SEC 100 +#define SCALE_USEC 10000 +#define MY_HOW_OFTEN_TO_ALARM 2 /* How often we want info on screen */ +#define MY_HOW_OFTEN_TO_WRITE 1000 /* How often we want info on screen */ + +#ifdef HAVE_TIMESPEC_TS_SEC +#ifndef set_timespec +#define set_timespec(ABSTIME,SEC) \ +{ \ + (ABSTIME).ts_sec=time(0) + (time_t) (SEC); \ + (ABSTIME).ts_nsec=0; \ +} +#endif /* !set_timespec */ +#ifndef set_timespec_nsec +#define set_timespec_nsec(ABSTIME,NSEC) \ +{ \ + ulonglong now= my_getsystime() + (NSEC/100); \ + (ABSTIME).ts_sec= (now / ULL(10000000)); \ + (ABSTIME).ts_nsec= (now % ULL(10000000) * 100 + ((NSEC) % 100)); \ +} +#endif /* !set_timespec_nsec */ +#else +#ifndef set_timespec +#define set_timespec(ABSTIME,SEC) \ +{\ + struct timeval tv;\ + gettimeofday(&tv,0);\ + (ABSTIME).tv_sec=tv.tv_sec+(time_t) (SEC);\ + (ABSTIME).tv_nsec=tv.tv_usec*1000;\ +} +#endif /* !set_timespec */ +#ifndef set_timespec_nsec +#define set_timespec_nsec(ABSTIME,NSEC) \ +{\ + ulonglong now= my_getsystime() + (NSEC/100); \ + (ABSTIME).tv_sec= (now / ULL(10000000)); \ + (ABSTIME).tv_nsec= (now % ULL(10000000) * 100 + ((NSEC) % 100)); \ +} +#endif /* !set_timespec_nsec */ +#endif /* HAVE_TIMESPEC_TS_SEC */ + +/* + Define-funktions for reading and storing in machine independent format + (low byte first) +*/ + +/* Optimized store functions for Intel x86 */ +#if defined(__i386__) && !defined(_WIN64) +#define sint2korr(A) (*((int16 *) (A))) +#define sint3korr(A) ((int32) ((((uchar) (A)[2]) & 128) ? \ + (((uint32) 255L << 24) | \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0])) : \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0]))) +#define sint4korr(A) (*((long *) (A))) +#define uint2korr(A) (*((uint16 *) (A))) +#ifdef HAVE_purify +#define uint3korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16)) +#else +/* + ATTENTION ! + + Please, note, uint3korr reads 4 bytes (not 3) ! + It means, that you have to provide enough allocated space ! +*/ +#define uint3korr(A) (long) (*((unsigned int *) (A)) & 0xFFFFFF) +#endif +#define uint4korr(A) (*((unsigned long *) (A))) +#define uint5korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) ((uchar) (A)[4])) << 32)) +#define uint8korr(A) (*((ulonglong *) (A))) +#define sint8korr(A) (*((longlong *) (A))) +#define int2store(T,A) *((uint16*) (T))= (uint16) (A) +#define int3store(T,A) do { *(T)= (uchar) ((A));\ + *(T+1)=(uchar) (((uint) (A) >> 8));\ + *(T+2)=(uchar) (((A) >> 16)); } while (0) +#define int4store(T,A) *((long *) (T))= (long) (A) +#define int5store(T,A) do { *(T)= (uchar)((A));\ + *((T)+1)=(uchar) (((A) >> 8));\ + *((T)+2)=(uchar) (((A) >> 16));\ + *((T)+3)=(uchar) (((A) >> 24)); \ + *((T)+4)=(uchar) (((A) >> 32)); } while(0) +#define int8store(T,A) *((ulonglong *) (T))= (ulonglong) (A) + +typedef union { + double v; + long m[2]; +} doubleget_union; +#define doubleget(V,M) \ +do { doubleget_union _tmp; \ + _tmp.m[0] = *((long*)(M)); \ + _tmp.m[1] = *(((long*) (M))+1); \ + (V) = _tmp.v; } while(0) +#define doublestore(T,V) do { *((long *) T) = ((doubleget_union *)&V)->m[0]; \ + *(((long *) T)+1) = ((doubleget_union *)&V)->m[1]; \ + } while (0) +#define float4get(V,M) do { *((long *) &(V)) = *((long*) (M)); } while(0) +#define float8get(V,M) doubleget((V),(M)) +#define float4store(V,M) memcpy((byte*) V,(byte*) (&M),sizeof(float)) +#define floatstore(T,V) memcpy((byte*)(T), (byte*)(&V),sizeof(float)) +#define floatget(V,M) memcpy((byte*) &V,(byte*) (M),sizeof(float)) +#define float8store(V,M) doublestore((V),(M)) +#endif /* __i386__ */ + +#ifndef sint2korr +/* + We're here if it's not a IA-32 architecture (Win32 and UNIX IA-32 defines + were done before) +*/ +#define sint2korr(A) (int16) (((int16) ((uchar) (A)[0])) +\ + ((int16) ((int16) (A)[1]) << 8)) +#define sint3korr(A) ((int32) ((((uchar) (A)[2]) & 128) ? \ + (((uint32) 255L << 24) | \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0])) : \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0]))) +#define sint4korr(A) (int32) (((int32) ((uchar) (A)[0])) +\ + (((int32) ((uchar) (A)[1]) << 8)) +\ + (((int32) ((uchar) (A)[2]) << 16)) +\ + (((int32) ((int16) (A)[3]) << 24))) +#define sint8korr(A) (longlong) uint8korr(A) +#define uint2korr(A) (uint16) (((uint16) ((uchar) (A)[0])) +\ + ((uint16) ((uchar) (A)[1]) << 8)) +#define uint3korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16)) +#define uint4korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +#define uint5korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) ((uchar) (A)[4])) << 32)) +#define uint8korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) (((uint32) ((uchar) (A)[4])) +\ + (((uint32) ((uchar) (A)[5])) << 8) +\ + (((uint32) ((uchar) (A)[6])) << 16) +\ + (((uint32) ((uchar) (A)[7])) << 24))) <<\ + 32)) +#define int2store(T,A) do { uint def_temp= (uint) (A) ;\ + *((uchar*) (T))= (uchar)(def_temp); \ + *((uchar*) (T)+1)=(uchar)((def_temp >> 8)); \ + } while(0) +#define int3store(T,A) do { /*lint -save -e734 */\ + *((uchar*)(T))=(uchar) ((A));\ + *((uchar*) (T)+1)=(uchar) (((A) >> 8));\ + *((uchar*)(T)+2)=(uchar) (((A) >> 16)); \ + /*lint -restore */} while(0) +#define int4store(T,A) do { *((char *)(T))=(char) ((A));\ + *(((char *)(T))+1)=(char) (((A) >> 8));\ + *(((char *)(T))+2)=(char) (((A) >> 16));\ + *(((char *)(T))+3)=(char) (((A) >> 24)); } while(0) +#define int5store(T,A) do { *((char *)(T))=((A));\ + *(((char *)(T))+1)=(((A) >> 8));\ + *(((char *)(T))+2)=(((A) >> 16));\ + *(((char *)(T))+3)=(((A) >> 24)); \ + *(((char *)(T))+4)=(((A) >> 32)); } while(0) +#define int8store(T,A) do { uint def_temp= (uint) (A), def_temp2= (uint) ((A) >> 32); \ + int4store((T),def_temp); \ + int4store((T+4),def_temp2); } while(0) +#ifdef WORDS_BIGENDIAN +#define float4store(T,A) do { *(T)= ((byte *) &A)[3];\ + *((T)+1)=(char) ((byte *) &A)[2];\ + *((T)+2)=(char) ((byte *) &A)[1];\ + *((T)+3)=(char) ((byte *) &A)[0]; } while(0) + +#define float4get(V,M) do { float def_temp;\ + ((byte*) &def_temp)[0]=(M)[3];\ + ((byte*) &def_temp)[1]=(M)[2];\ + ((byte*) &def_temp)[2]=(M)[1];\ + ((byte*) &def_temp)[3]=(M)[0];\ + (V)=def_temp; } while(0) +#define float8store(T,V) do { *(T)= ((byte *) &V)[7];\ + *((T)+1)=(char) ((byte *) &V)[6];\ + *((T)+2)=(char) ((byte *) &V)[5];\ + *((T)+3)=(char) ((byte *) &V)[4];\ + *((T)+4)=(char) ((byte *) &V)[3];\ + *((T)+5)=(char) ((byte *) &V)[2];\ + *((T)+6)=(char) ((byte *) &V)[1];\ + *((T)+7)=(char) ((byte *) &V)[0]; } while(0) + +#define float8get(V,M) do { double def_temp;\ + ((byte*) &def_temp)[0]=(M)[7];\ + ((byte*) &def_temp)[1]=(M)[6];\ + ((byte*) &def_temp)[2]=(M)[5];\ + ((byte*) &def_temp)[3]=(M)[4];\ + ((byte*) &def_temp)[4]=(M)[3];\ + ((byte*) &def_temp)[5]=(M)[2];\ + ((byte*) &def_temp)[6]=(M)[1];\ + ((byte*) &def_temp)[7]=(M)[0];\ + (V) = def_temp; } while(0) +#else +#define float4get(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(float)) +#define float4store(V,M) memcpy_fixed((byte*) V,(byte*) (&M),sizeof(float)) + +#if defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN) +#define doublestore(T,V) do { *(((char*)T)+0)=(char) ((byte *) &V)[4];\ + *(((char*)T)+1)=(char) ((byte *) &V)[5];\ + *(((char*)T)+2)=(char) ((byte *) &V)[6];\ + *(((char*)T)+3)=(char) ((byte *) &V)[7];\ + *(((char*)T)+4)=(char) ((byte *) &V)[0];\ + *(((char*)T)+5)=(char) ((byte *) &V)[1];\ + *(((char*)T)+6)=(char) ((byte *) &V)[2];\ + *(((char*)T)+7)=(char) ((byte *) &V)[3]; }\ + while(0) +#define doubleget(V,M) do { double def_temp;\ + ((byte*) &def_temp)[0]=(M)[4];\ + ((byte*) &def_temp)[1]=(M)[5];\ + ((byte*) &def_temp)[2]=(M)[6];\ + ((byte*) &def_temp)[3]=(M)[7];\ + ((byte*) &def_temp)[4]=(M)[0];\ + ((byte*) &def_temp)[5]=(M)[1];\ + ((byte*) &def_temp)[6]=(M)[2];\ + ((byte*) &def_temp)[7]=(M)[3];\ + (V) = def_temp; } while(0) +#endif /* __FLOAT_WORD_ORDER */ + +#define float8get(V,M) doubleget((V),(M)) +#define float8store(V,M) doublestore((V),(M)) +#endif /* WORDS_BIGENDIAN */ + +#endif /* sint2korr */ + +/* + Macro for reading 32-bit integer from network byte order (big-endian) + from unaligned memory location. +*/ +#define int4net(A) (int32) (((uint32) ((uchar) (A)[3])) |\ + (((uint32) ((uchar) (A)[2])) << 8) |\ + (((uint32) ((uchar) (A)[1])) << 16) |\ + (((uint32) ((uchar) (A)[0])) << 24)) +/* + Define-funktions for reading and storing in machine format from/to + short/long to/from some place in memory V should be a (not + register) variable, M is a pointer to byte +*/ + +#ifdef WORDS_BIGENDIAN + +#define ushortget(V,M) do { V = (uint16) (((uint16) ((uchar) (M)[1]))+\ + ((uint16) ((uint16) (M)[0]) << 8)); } while(0) +#define shortget(V,M) do { V = (short) (((short) ((uchar) (M)[1]))+\ + ((short) ((short) (M)[0]) << 8)); } while(0) +#define longget(V,M) do { int32 def_temp;\ + ((byte*) &def_temp)[0]=(M)[0];\ + ((byte*) &def_temp)[1]=(M)[1];\ + ((byte*) &def_temp)[2]=(M)[2];\ + ((byte*) &def_temp)[3]=(M)[3];\ + (V)=def_temp; } while(0) +#define ulongget(V,M) do { uint32 def_temp;\ + ((byte*) &def_temp)[0]=(M)[0];\ + ((byte*) &def_temp)[1]=(M)[1];\ + ((byte*) &def_temp)[2]=(M)[2];\ + ((byte*) &def_temp)[3]=(M)[3];\ + (V)=def_temp; } while(0) +#define shortstore(T,A) do { uint def_temp=(uint) (A) ;\ + *(((char*)T)+1)=(char)(def_temp); \ + *(((char*)T)+0)=(char)(def_temp >> 8); } while(0) +#define longstore(T,A) do { *(((char*)T)+3)=((A));\ + *(((char*)T)+2)=(((A) >> 8));\ + *(((char*)T)+1)=(((A) >> 16));\ + *(((char*)T)+0)=(((A) >> 24)); } while(0) + +#define floatget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(float)) +#define floatstore(T,V) memcpy_fixed((byte*) (T),(byte*)(&V),sizeof(float)) +#define doubleget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(double)) +#define doublestore(T,V) memcpy_fixed((byte*) (T),(byte*) &V,sizeof(double)) +#define longlongget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(ulonglong)) +#define longlongstore(T,V) memcpy_fixed((byte*) (T),(byte*) &V,sizeof(ulonglong)) + +#else + +#define ushortget(V,M) do { V = uint2korr(M); } while(0) +#define shortget(V,M) do { V = sint2korr(M); } while(0) +#define longget(V,M) do { V = sint4korr(M); } while(0) +#define ulongget(V,M) do { V = uint4korr(M); } while(0) +#define shortstore(T,V) int2store(T,V) +#define longstore(T,V) int4store(T,V) +#ifndef floatstore +#define floatstore(T,V) memcpy_fixed((byte*) (T),(byte*) (&V),sizeof(float)) +#define floatget(V,M) memcpy_fixed((byte*) &V, (byte*) (M), sizeof(float)) +#endif +#ifndef doubleget +#define doubleget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(double)) +#define doublestore(T,V) memcpy_fixed((byte*) (T),(byte*) &V,sizeof(double)) +#endif /* doubleget */ +#define longlongget(V,M) memcpy_fixed((byte*) &V,(byte*) (M),sizeof(ulonglong)) +#define longlongstore(T,V) memcpy_fixed((byte*) (T),(byte*) &V,sizeof(ulonglong)) + +#endif /* WORDS_BIGENDIAN */ + +/* sprintf does not always return the number of bytes :- */ +#ifdef SPRINTF_RETURNS_INT +#define my_sprintf(buff,args) sprintf args +#else +#ifdef SPRINTF_RETURNS_PTR +#define my_sprintf(buff,args) ((int)(sprintf args - buff)) +#else +#define my_sprintf(buff,args) ((ulong) sprintf args, (ulong) strlen(buff)) +#endif +#endif + +#ifndef THREAD +#define thread_safe_increment(V,L) (V)++ +#define thread_safe_add(V,C,L) (V)+=(C) +#define thread_safe_sub(V,C,L) (V)-=(C) +#define statistic_increment(V,L) (V)++ +#define statistic_add(V,C,L) (V)+=(C) +#endif + +#ifdef HAVE_CHARSET_utf8 +#define MYSQL_UNIVERSAL_CLIENT_CHARSET "utf8" +#else +#define MYSQL_UNIVERSAL_CLIENT_CHARSET MYSQL_DEFAULT_CHARSET_NAME +#endif + +#if defined(EMBEDDED_LIBRARY) && !defined(HAVE_EMBEDDED_PRIVILEGE_CONTROL) +#define NO_EMBEDDED_ACCESS_CHECKS +#endif + +#endif /* my_global_h */ diff --git a/Src/3rd_party/mysql/include/my_list.h b/Src/3rd_party/mysql/include/my_list.h new file mode 100644 index 0000000..9259869 --- /dev/null +++ b/Src/3rd_party/mysql/include/my_list.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _list_h_ +#define _list_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct st_list { + struct st_list *prev,*next; + void *data; +} LIST; + +typedef int (*list_walk_action)(void *,void *); + +extern LIST *list_add(LIST *root,LIST *element); +extern LIST *list_delete(LIST *root,LIST *element); +extern LIST *list_cons(void *data,LIST *root); +extern LIST *list_reverse(LIST *root); +extern void list_free(LIST *root,unsigned int free_data); +extern unsigned int list_length(LIST *); +extern int list_walk(LIST *,list_walk_action action,gptr argument); + +#define list_rest(a) ((a)->next) +#define list_push(a,b) (a)=list_cons((b),(a)) +#define list_pop(A) {LIST *old=(A); (A)=list_delete(old,old) ; my_free((gptr) old,MYF(MY_FAE)); } + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Src/3rd_party/mysql/include/my_sys.h b/Src/3rd_party/mysql/include/my_sys.h new file mode 100644 index 0000000..7603180 --- /dev/null +++ b/Src/3rd_party/mysql/include/my_sys.h @@ -0,0 +1,903 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _my_sys_h +#define _my_sys_h +C_MODE_START + +#ifdef HAVE_AIOWAIT +#include /* Used by record-cache */ +typedef struct my_aio_result { + aio_result_t result; + int pending; +} my_aio_result; +#endif + +#ifndef THREAD +extern int NEAR my_errno; /* Last error in mysys */ +#else +#include +#endif + +#ifndef _m_ctype_h +#include /* for CHARSET_INFO */ +#endif + +#include +#include + +#define MYSYS_PROGRAM_USES_CURSES() { error_handler_hook = my_message_curses; mysys_uses_curses=1; } +#define MYSYS_PROGRAM_DONT_USE_CURSES() { error_handler_hook = my_message_no_curses; mysys_uses_curses=0;} +#define MY_INIT(name); { my_progname= name; my_init(); } + +#define ERRMSGSIZE (SC_MAXWIDTH) /* Max length of a error message */ +#define NRERRBUFFS (2) /* Buffers for parameters */ +#define MY_FILE_ERROR ((uint) ~0) + + /* General bitmaps for my_func's */ +#define MY_FFNF 1 /* Fatal if file not found */ +#define MY_FNABP 2 /* Fatal if not all bytes read/writen */ +#define MY_NABP 4 /* Error if not all bytes read/writen */ +#define MY_FAE 8 /* Fatal if any error */ +#define MY_WME 16 /* Write message on error */ +#define MY_WAIT_IF_FULL 32 /* Wait and try again if disk full error */ +#define MY_IGNORE_BADFD 32 /* my_sync: ignore 'bad descriptor' errors */ +#define MY_RAID 64 /* Support for RAID */ +#define MY_FULL_IO 512 /* For my_read - loop intil I/O is complete */ +#define MY_DONT_CHECK_FILESIZE 128 /* Option to init_io_cache() */ +#define MY_LINK_WARNING 32 /* my_redel() gives warning if links */ +#define MY_COPYTIME 64 /* my_redel() copys time */ +#define MY_DELETE_OLD 256 /* my_create_with_symlink() */ +#define MY_RESOLVE_LINK 128 /* my_realpath(); Only resolve links */ +#define MY_HOLD_ORIGINAL_MODES 128 /* my_copy() holds to file modes */ +#define MY_REDEL_MAKE_BACKUP 256 +#define MY_SEEK_NOT_DONE 32 /* my_lock may have to do a seek */ +#define MY_DONT_WAIT 64 /* my_lock() don't wait if can't lock */ +#define MY_ZEROFILL 32 /* my_malloc(), fill array with zero */ +#define MY_ALLOW_ZERO_PTR 64 /* my_realloc() ; zero ptr -> malloc */ +#define MY_FREE_ON_ERROR 128 /* my_realloc() ; Free old ptr on error */ +#define MY_HOLD_ON_ERROR 256 /* my_realloc() ; Return old ptr on error */ +#define MY_THREADSAFE 128 /* pread/pwrite: Don't allow interrupts */ +#define MY_DONT_OVERWRITE_FILE 1024 /* my_copy: Don't overwrite file */ + +#define MY_CHECK_ERROR 1 /* Params to my_end; Check open-close */ +#define MY_GIVE_INFO 2 /* Give time info about process*/ + +#define ME_HIGHBYTE 8 /* Shift for colours */ +#define ME_NOCUR 1 /* Don't use curses message */ +#define ME_OLDWIN 2 /* Use old window */ +#define ME_BELL 4 /* Ring bell then printing message */ +#define ME_HOLDTANG 8 /* Don't delete last keys */ +#define ME_WAITTOT 16 /* Wait for errtime secs of for a action */ +#define ME_WAITTANG 32 /* Wait for a user action */ +#define ME_NOREFRESH 64 /* Dont refresh screen */ +#define ME_NOINPUT 128 /* Dont use the input libary */ +#define ME_COLOUR1 ((1 << ME_HIGHBYTE)) /* Possibly error-colours */ +#define ME_COLOUR2 ((2 << ME_HIGHBYTE)) +#define ME_COLOUR3 ((3 << ME_HIGHBYTE)) + + /* Bits in last argument to fn_format */ +#define MY_REPLACE_DIR 1 /* replace dir in name with 'dir' */ +#define MY_REPLACE_EXT 2 /* replace extension with 'ext' */ +#define MY_UNPACK_FILENAME 4 /* Unpack name (~ -> home) */ +#define MY_PACK_FILENAME 8 /* Pack name (home -> ~) */ +#define MY_RESOLVE_SYMLINKS 16 /* Resolve all symbolic links */ +#define MY_RETURN_REAL_PATH 32 /* return full path for file */ +#define MY_SAFE_PATH 64 /* Return NULL if too long path */ +#define MY_RELATIVE_PATH 128 /* name is relative to 'dir' */ + + /* My seek flags */ +#define MY_SEEK_SET 0 +#define MY_SEEK_CUR 1 +#define MY_SEEK_END 2 + + /* Some constants */ +#define MY_WAIT_FOR_USER_TO_FIX_PANIC 60 /* in seconds */ +#define MY_WAIT_GIVE_USER_A_MESSAGE 10 /* Every 10 times of prev */ +#define MIN_COMPRESS_LENGTH 50 /* Don't compress small bl. */ +#define DFLT_INIT_HITS 3 + + /* root_alloc flags */ +#define MY_KEEP_PREALLOC 1 +#define MY_MARK_BLOCKS_FREE 2 /* move used to free list and reuse them */ + + /* Internal error numbers (for assembler functions) */ +#define MY_ERRNO_EDOM 33 +#define MY_ERRNO_ERANGE 34 + + /* Bits for get_date timeflag */ +#define GETDATE_DATE_TIME 1 +#define GETDATE_SHORT_DATE 2 +#define GETDATE_HHMMSSTIME 4 +#define GETDATE_GMT 8 +#define GETDATE_FIXEDLENGTH 16 + + /* defines when allocating data */ +#ifdef SAFEMALLOC +#define my_malloc(SZ,FLAG) _mymalloc((SZ), __FILE__, __LINE__, FLAG ) +#define my_malloc_ci(SZ,FLAG) _mymalloc((SZ), sFile, uLine, FLAG ) +#define my_realloc(PTR,SZ,FLAG) _myrealloc((PTR), (SZ), __FILE__, __LINE__, FLAG ) +#define my_checkmalloc() _sanity( __FILE__, __LINE__ ) +#define my_free(PTR,FLAG) _myfree((PTR), __FILE__, __LINE__,FLAG) +#define my_memdup(A,B,C) _my_memdup((A),(B), __FILE__,__LINE__,C) +#define my_strdup(A,C) _my_strdup((A), __FILE__,__LINE__,C) +#define my_strdup_with_length(A,B,C) _my_strdup_with_length((A),(B),__FILE__,__LINE__,C) +#define TRASH(A,B) bfill(A, B, 0x8F) +#define QUICK_SAFEMALLOC sf_malloc_quick=1 +#define NORMAL_SAFEMALLOC sf_malloc_quick=0 +extern uint sf_malloc_prehunc,sf_malloc_endhunc,sf_malloc_quick; +extern ulonglong sf_malloc_mem_limit; + +#define CALLER_INFO_PROTO , const char *sFile, uint uLine +#define CALLER_INFO , __FILE__, __LINE__ +#define ORIG_CALLER_INFO , sFile, uLine +#else +#define my_checkmalloc() +#undef TERMINATE +#define TERMINATE(A) {} +#define QUICK_SAFEMALLOC +#define NORMAL_SAFEMALLOC +extern gptr my_malloc(uint Size,myf MyFlags); +#define my_malloc_ci(SZ,FLAG) my_malloc( SZ, FLAG ) +extern gptr my_realloc(gptr oldpoint,uint Size,myf MyFlags); +extern void my_no_flags_free(gptr ptr); +extern gptr my_memdup(const byte *from,uint length,myf MyFlags); +extern char *my_strdup(const char *from,myf MyFlags); +extern char *my_strdup_with_length(const byte *from, uint length, + myf MyFlags); +/* we do use FG (as a no-op) in below so that a typo on FG is caught */ +#define my_free(PTR,FG) ((void)FG,my_no_flags_free(PTR)) +#define CALLER_INFO_PROTO /* nothing */ +#define CALLER_INFO /* nothing */ +#define ORIG_CALLER_INFO /* nothing */ +#define TRASH(A,B) /* nothing */ +#endif + +#ifdef HAVE_LARGE_PAGES +extern uint my_get_large_page_size(void); +extern gptr my_large_malloc(uint size, myf my_flags); +extern void my_large_free(gptr ptr, myf my_flags); +#else +#define my_get_large_page_size() (0) +#define my_large_malloc(A,B) my_malloc_lock((A),(B)) +#define my_large_free(A,B) my_free_lock((A),(B)) +#endif /* HAVE_LARGE_PAGES */ + +#ifdef HAVE_ALLOCA +#if defined(_AIX) && !defined(__GNUC__) && !defined(_AIX43) +#pragma alloca +#endif /* _AIX */ +#if defined(__MWERKS__) +#undef alloca +#define alloca _alloca +#endif /* __MWERKS__ */ +#if defined(__GNUC__) && !defined(HAVE_ALLOCA_H) && ! defined(alloca) +#define alloca __builtin_alloca +#endif /* GNUC */ +#define my_alloca(SZ) alloca((size_t) (SZ)) +#define my_afree(PTR) {} +#else +#define my_alloca(SZ) my_malloc(SZ,MYF(0)) +#define my_afree(PTR) my_free(PTR,MYF(MY_WME)) +#endif /* HAVE_ALLOCA */ + +#ifdef MSDOS +#ifdef __ZTC__ +void * __CDECL halloc(long count,size_t length); +void __CDECL hfree(void *ptr); +#endif +#if defined(USE_HALLOC) +#if defined(_VCM_) || defined(M_IC80386) +#undef USE_HALLOC +#endif +#endif +#ifdef USE_HALLOC +#define malloc(a) halloc((long) (a),1) +#define free(a) hfree(a) +#endif +#endif /* MSDOS */ + +#ifndef errno /* did we already get it? */ +#ifdef HAVE_ERRNO_AS_DEFINE +#include /* errno is a define */ +#else +extern int errno; /* declare errno */ +#endif +#endif /* #ifndef errno */ +extern char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE]; +extern char *home_dir; /* Home directory for user */ +extern const char *my_progname; /* program-name (printed in errors) */ +extern char NEAR curr_dir[]; /* Current directory for user */ +extern int (*error_handler_hook)(uint my_err, const char *str,myf MyFlags); +extern int (*fatal_error_handler_hook)(uint my_err, const char *str, + myf MyFlags); +extern uint my_file_limit; + +#ifdef HAVE_LARGE_PAGES +extern my_bool my_use_large_pages; +extern uint my_large_page_size; +#endif + +/* charsets */ +extern CHARSET_INFO *default_charset_info; +extern CHARSET_INFO *all_charsets[256]; +extern CHARSET_INFO compiled_charsets[]; + +/* statistics */ +extern ulong my_file_opened,my_stream_opened, my_tmp_file_created; +extern uint mysys_usage_id; +extern my_bool my_init_done; + + /* Point to current my_message() */ +extern void (*my_sigtstp_cleanup)(void), + /* Executed before jump to shell */ + (*my_sigtstp_restart)(void), + (*my_abort_hook)(int); + /* Executed when comming from shell */ +extern int NEAR my_umask, /* Default creation mask */ + NEAR my_umask_dir, + NEAR my_recived_signals, /* Signals we have got */ + NEAR my_safe_to_handle_signal, /* Set when allowed to SIGTSTP */ + NEAR my_dont_interrupt; /* call remember_intr when set */ +extern my_bool NEAR mysys_uses_curses, my_use_symdir; +extern ulong sf_malloc_cur_memory, sf_malloc_max_memory; + +extern ulong my_default_record_cache_size; +extern my_bool NEAR my_disable_locking,NEAR my_disable_async_io, + NEAR my_disable_flush_key_blocks, NEAR my_disable_symlinks; +extern char wild_many,wild_one,wild_prefix; +extern const char *charsets_dir; +extern char *defaults_extra_file; +extern const char *defaults_group_suffix; +extern const char *defaults_file; + +extern my_bool timed_mutexes; + +typedef struct wild_file_pack /* Struct to hold info when selecting files */ +{ + uint wilds; /* How many wildcards */ + uint not_pos; /* Start of not-theese-files */ + my_string *wild; /* Pointer to wildcards */ +} WF_PACK; + +enum loglevel { + ERROR_LEVEL, + WARNING_LEVEL, + INFORMATION_LEVEL +}; + +enum cache_type +{ + TYPE_NOT_SET= 0, READ_CACHE, WRITE_CACHE, + SEQ_READ_APPEND /* sequential read or append */, + READ_FIFO, READ_NET,WRITE_NET}; + +enum flush_type +{ + FLUSH_KEEP, FLUSH_RELEASE, FLUSH_IGNORE_CHANGED, FLUSH_FORCE_WRITE +}; + +typedef struct st_record_cache /* Used when cacheing records */ +{ + File file; + int rc_seek,error,inited; + uint rc_length,read_length,reclength; + my_off_t rc_record_pos,end_of_file; + byte *rc_buff,*rc_buff2,*rc_pos,*rc_end,*rc_request_pos; +#ifdef HAVE_AIOWAIT + int use_async_io; + my_aio_result aio_result; +#endif + enum cache_type type; +} RECORD_CACHE; + +enum file_type +{ + UNOPEN = 0, FILE_BY_OPEN, FILE_BY_CREATE, STREAM_BY_FOPEN, STREAM_BY_FDOPEN, + FILE_BY_MKSTEMP, FILE_BY_DUP +}; + +struct st_my_file_info +{ + my_string name; + enum file_type type; +#if defined(THREAD) && !defined(HAVE_PREAD) + pthread_mutex_t mutex; +#endif +}; + +extern struct st_my_file_info *my_file_info; + +typedef struct st_my_tmpdir +{ + char **list; + uint cur, max; +#ifdef THREAD + pthread_mutex_t mutex; +#endif +} MY_TMPDIR; + +typedef struct st_dynamic_array +{ + char *buffer; + uint elements,max_element; + uint alloc_increment; + uint size_of_element; +} DYNAMIC_ARRAY; + +typedef struct st_dynamic_string +{ + char *str; + uint length,max_length,alloc_increment; +} DYNAMIC_STRING; + +struct st_io_cache; +typedef int (*IO_CACHE_CALLBACK)(struct st_io_cache*); + +#ifdef THREAD +typedef struct st_io_cache_share +{ + /* to sync on reads into buffer */ + pthread_mutex_t mutex; + pthread_cond_t cond; + int count, total; + /* actual IO_CACHE that filled the buffer */ + struct st_io_cache *active; +#ifdef NOT_YET_IMPLEMENTED + /* whether the structure should be free'd */ + my_bool alloced; +#endif +} IO_CACHE_SHARE; +#endif + +typedef struct st_io_cache /* Used when cacheing files */ +{ + /* Offset in file corresponding to the first byte of byte* buffer. */ + my_off_t pos_in_file; + /* + The offset of end of file for READ_CACHE and WRITE_CACHE. + For SEQ_READ_APPEND it the maximum of the actual end of file and + the position represented by read_end. + */ + my_off_t end_of_file; + /* Points to current read position in the buffer */ + byte *read_pos; + /* the non-inclusive boundary in the buffer for the currently valid read */ + byte *read_end; + byte *buffer; /* The read buffer */ + /* Used in ASYNC_IO */ + byte *request_pos; + + /* Only used in WRITE caches and in SEQ_READ_APPEND to buffer writes */ + byte *write_buffer; + /* + Only used in SEQ_READ_APPEND, and points to the current read position + in the write buffer. Note that reads in SEQ_READ_APPEND caches can + happen from both read buffer (byte* buffer) and write buffer + (byte* write_buffer). + */ + byte *append_read_pos; + /* Points to current write position in the write buffer */ + byte *write_pos; + /* The non-inclusive boundary of the valid write area */ + byte *write_end; + + /* + Current_pos and current_end are convenience variables used by + my_b_tell() and other routines that need to know the current offset + current_pos points to &write_pos, and current_end to &write_end in a + WRITE_CACHE, and &read_pos and &read_end respectively otherwise + */ + byte **current_pos, **current_end; +#ifdef THREAD + /* + The lock is for append buffer used in SEQ_READ_APPEND cache + need mutex copying from append buffer to read buffer. + */ + pthread_mutex_t append_buffer_lock; + /* + The following is used when several threads are reading the + same file in parallel. They are synchronized on disk + accesses reading the cached part of the file asynchronously. + It should be set to NULL to disable the feature. Only + READ_CACHE mode is supported. + */ + IO_CACHE_SHARE *share; +#endif + /* + A caller will use my_b_read() macro to read from the cache + if the data is already in cache, it will be simply copied with + memcpy() and internal variables will be accordinging updated with + no functions invoked. However, if the data is not fully in the cache, + my_b_read() will call read_function to fetch the data. read_function + must never be invoked directly. + */ + int (*read_function)(struct st_io_cache *,byte *,uint); + /* + Same idea as in the case of read_function, except my_b_write() needs to + be replaced with my_b_append() for a SEQ_READ_APPEND cache + */ + int (*write_function)(struct st_io_cache *,const byte *,uint); + /* + Specifies the type of the cache. Depending on the type of the cache + certain operations might not be available and yield unpredicatable + results. Details to be documented later + */ + enum cache_type type; + /* + Callbacks when the actual read I/O happens. These were added and + are currently used for binary logging of LOAD DATA INFILE - when a + block is read from the file, we create a block create/append event, and + when IO_CACHE is closed, we create an end event. These functions could, + of course be used for other things + */ + IO_CACHE_CALLBACK pre_read; + IO_CACHE_CALLBACK post_read; + IO_CACHE_CALLBACK pre_close; + /* + Counts the number of times, when we were forced to use disk. We use it to + increase the binlog_cache_disk_use status variable. + */ + ulong disk_writes; + void* arg; /* for use by pre/post_read */ + char *file_name; /* if used with 'open_cached_file' */ + char *dir,*prefix; + File file; /* file descriptor */ + /* + seek_not_done is set by my_b_seek() to inform the upcoming read/write + operation that a seek needs to be preformed prior to the actual I/O + error is 0 if the cache operation was successful, -1 if there was a + "hard" error, and the actual number of I/O-ed bytes if the read/write was + partial. + */ + int seek_not_done,error; + /* buffer_length is memory size allocated for buffer or write_buffer */ + uint buffer_length; + /* read_length is the same as buffer_length except when we use async io */ + uint read_length; + myf myflags; /* Flags used to my_read/my_write */ + /* + alloced_buffer is 1 if the buffer was allocated by init_io_cache() and + 0 if it was supplied by the user. + Currently READ_NET is the only one that will use a buffer allocated + somewhere else + */ + my_bool alloced_buffer; +#ifdef HAVE_AIOWAIT + /* + As inidicated by ifdef, this is for async I/O, which is not currently + used (because it's not reliable on all systems) + */ + uint inited; + my_off_t aio_read_pos; + my_aio_result aio_result; +#endif +} IO_CACHE; + +typedef int (*qsort2_cmp)(const void *, const void *, const void *); + + /* defines for mf_iocache */ + + /* Test if buffer is inited */ +#define my_b_clear(info) (info)->buffer=0 +#define my_b_inited(info) (info)->buffer +#define my_b_EOF INT_MIN + +#define my_b_read(info,Buffer,Count) \ + ((info)->read_pos + (Count) <= (info)->read_end ?\ + (memcpy(Buffer,(info)->read_pos,(size_t) (Count)), \ + ((info)->read_pos+=(Count)),0) :\ + (*(info)->read_function)((info),Buffer,Count)) + +#define my_b_write(info,Buffer,Count) \ + ((info)->write_pos + (Count) <=(info)->write_end ?\ + (memcpy((info)->write_pos, (Buffer), (size_t)(Count)),\ + ((info)->write_pos+=(Count)),0) : \ + (*(info)->write_function)((info),(Buffer),(Count))) + +#define my_b_get(info) \ + ((info)->read_pos != (info)->read_end ?\ + ((info)->read_pos++, (int) (uchar) (info)->read_pos[-1]) :\ + _my_b_get(info)) + + /* my_b_write_byte dosn't have any err-check */ +#define my_b_write_byte(info,chr) \ + (((info)->write_pos < (info)->write_end) ?\ + ((*(info)->write_pos++)=(chr)) :\ + (_my_b_write(info,0,0) , ((*(info)->write_pos++)=(chr)))) + +#define my_b_fill_cache(info) \ + (((info)->read_end=(info)->read_pos),(*(info)->read_function)(info,0,0)) + +#define my_b_tell(info) ((info)->pos_in_file + \ + (uint) (*(info)->current_pos - (info)->request_pos)) + +/* tell write offset in the SEQ_APPEND cache */ +my_off_t my_b_append_tell(IO_CACHE* info); +my_off_t my_b_safe_tell(IO_CACHE* info); /* picks the correct tell() */ + +#define my_b_bytes_in_cache(info) (uint) (*(info)->current_end - \ + *(info)->current_pos) + +typedef uint32 ha_checksum; + +/* Define the type of function to be passed to process_default_option_files */ +typedef int (*Process_option_func)(void *ctx, const char *group_name, + const char *option); + +#include + + /* Prototypes for mysys and my_func functions */ + +extern int my_copy(const char *from,const char *to,myf MyFlags); +extern int my_append(const char *from,const char *to,myf MyFlags); +extern int my_delete(const char *name,myf MyFlags); +extern int my_getwd(my_string buf,uint size,myf MyFlags); +extern int my_setwd(const char *dir,myf MyFlags); +extern int my_lock(File fd,int op,my_off_t start, my_off_t length,myf MyFlags); +extern gptr my_once_alloc(uint Size,myf MyFlags); +extern void my_once_free(void); +extern char *my_once_strdup(const char *src,myf myflags); +extern char *my_once_memdup(const char *src, uint len, myf myflags); +extern File my_open(const char *FileName,int Flags,myf MyFlags); +extern File my_register_filename(File fd, const char *FileName, + enum file_type type_of_file, + uint error_message_number, myf MyFlags); +extern File my_create(const char *FileName,int CreateFlags, + int AccsesFlags, myf MyFlags); +extern int my_close(File Filedes,myf MyFlags); +extern File my_dup(File file, myf MyFlags); +extern int my_mkdir(const char *dir, int Flags, myf MyFlags); +extern int my_readlink(char *to, const char *filename, myf MyFlags); +extern int my_realpath(char *to, const char *filename, myf MyFlags); +extern File my_create_with_symlink(const char *linkname, const char *filename, + int createflags, int access_flags, + myf MyFlags); +extern int my_delete_with_symlink(const char *name, myf MyFlags); +extern int my_rename_with_symlink(const char *from,const char *to,myf MyFlags); +extern int my_symlink(const char *content, const char *linkname, myf MyFlags); +extern uint my_read(File Filedes,byte *Buffer,uint Count,myf MyFlags); +extern uint my_pread(File Filedes,byte *Buffer,uint Count,my_off_t offset, + myf MyFlags); +extern int my_rename(const char *from,const char *to,myf MyFlags); +extern my_off_t my_seek(File fd,my_off_t pos,int whence,myf MyFlags); +extern my_off_t my_tell(File fd,myf MyFlags); +extern uint my_write(File Filedes,const byte *Buffer,uint Count, + myf MyFlags); +extern uint my_pwrite(File Filedes,const byte *Buffer,uint Count, + my_off_t offset,myf MyFlags); +extern uint my_fread(FILE *stream,byte *Buffer,uint Count,myf MyFlags); +extern uint my_fwrite(FILE *stream,const byte *Buffer,uint Count, + myf MyFlags); +extern my_off_t my_fseek(FILE *stream,my_off_t pos,int whence,myf MyFlags); +extern my_off_t my_ftell(FILE *stream,myf MyFlags); +extern gptr _mymalloc(uint uSize,const char *sFile, + uint uLine, myf MyFlag); +extern gptr _myrealloc(gptr pPtr,uint uSize,const char *sFile, + uint uLine, myf MyFlag); +extern gptr my_multi_malloc _VARARGS((myf MyFlags, ...)); +extern void _myfree(gptr pPtr,const char *sFile,uint uLine, myf MyFlag); +extern int _sanity(const char *sFile,unsigned int uLine); +extern gptr _my_memdup(const byte *from,uint length, + const char *sFile, uint uLine,myf MyFlag); +extern my_string _my_strdup(const char *from, const char *sFile, uint uLine, + myf MyFlag); +extern char *_my_strdup_with_length(const byte *from, uint length, + const char *sFile, uint uLine, + myf MyFlag); + +#ifdef __WIN__ +extern int my_access(const char *path, int amode); +#else +#define my_access access +#endif +extern int check_if_legal_filename(const char *path); + +#ifndef TERMINATE +extern void TERMINATE(FILE *file); +#endif +extern void init_glob_errs(void); +extern FILE *my_fopen(const char *FileName,int Flags,myf MyFlags); +extern FILE *my_fdopen(File Filedes,const char *name, int Flags,myf MyFlags); +extern int my_fclose(FILE *fd,myf MyFlags); +extern int my_chsize(File fd,my_off_t newlength, int filler, myf MyFlags); +extern int my_sync(File fd, myf my_flags); +extern int my_error _VARARGS((int nr,myf MyFlags, ...)); +extern int my_printf_error _VARARGS((uint my_err, const char *format, + myf MyFlags, ...) + __attribute__ ((format (printf, 2, 4)))); +extern int my_error_register(const char **errmsgs, int first, int last); +extern const char **my_error_unregister(int first, int last); +extern int my_message(uint my_err, const char *str,myf MyFlags); +extern int my_message_no_curses(uint my_err, const char *str,myf MyFlags); +extern int my_message_curses(uint my_err, const char *str,myf MyFlags); +extern my_bool my_init(void); +extern void my_end(int infoflag); +extern int my_redel(const char *from, const char *to, int MyFlags); +extern int my_copystat(const char *from, const char *to, int MyFlags); +extern my_string my_filename(File fd); + +#ifndef THREAD +extern void dont_break(void); +extern void allow_break(void); +#else +#define dont_break() +#define allow_break() +#endif + +extern my_bool init_tmpdir(MY_TMPDIR *tmpdir, const char *pathlist); +extern char *my_tmpdir(MY_TMPDIR *tmpdir); +extern void free_tmpdir(MY_TMPDIR *tmpdir); + +extern void my_remember_signal(int signal_number,sig_handler (*func)(int)); +extern uint dirname_part(my_string to,const char *name); +extern uint dirname_length(const char *name); +#define base_name(A) (A+dirname_length(A)) +extern int test_if_hard_path(const char *dir_name); +extern my_bool has_path(const char *name); +extern char *convert_dirname(char *to, const char *from, const char *from_end); +extern void to_unix_path(my_string name); +extern my_string fn_ext(const char *name); +extern my_string fn_same(my_string toname,const char *name,int flag); +extern my_string fn_format(my_string to,const char *name,const char *dir, + const char *form, uint flag); +extern size_s strlength(const char *str); +extern void pack_dirname(my_string to,const char *from); +extern uint unpack_dirname(my_string to,const char *from); +extern uint cleanup_dirname(my_string to,const char *from); +extern uint system_filename(my_string to,const char *from); +extern uint unpack_filename(my_string to,const char *from); +extern my_string intern_filename(my_string to,const char *from); +extern my_string directory_file_name(my_string dst, const char *src); +extern int pack_filename(my_string to, const char *name, size_s max_length); +extern my_string my_path(my_string to,const char *progname, + const char *own_pathname_part); +extern my_string my_load_path(my_string to, const char *path, + const char *own_path_prefix); +extern int wild_compare(const char *str,const char *wildstr,pbool str_is_pattern); +extern WF_PACK *wf_comp(my_string str); +extern int wf_test(struct wild_file_pack *wf_pack,const char *name); +extern void wf_end(struct wild_file_pack *buffer); +extern size_s strip_sp(my_string str); +extern void get_date(my_string to,int timeflag,time_t use_time); +extern void soundex(CHARSET_INFO *, my_string out_pntr, my_string in_pntr,pbool remove_garbage); +extern int init_record_cache(RECORD_CACHE *info,uint cachesize,File file, + uint reclength,enum cache_type type, + pbool use_async_io); +extern int read_cache_record(RECORD_CACHE *info,byte *to); +extern int end_record_cache(RECORD_CACHE *info); +extern int write_cache_record(RECORD_CACHE *info,my_off_t filepos, + const byte *record,uint length); +extern int flush_write_cache(RECORD_CACHE *info); +extern long my_clock(void); +extern sig_handler sigtstp_handler(int signal_number); +extern void handle_recived_signals(void); + +extern sig_handler my_set_alarm_variable(int signo); +extern void my_string_ptr_sort(void *base,uint items,size_s size); +extern void radixsort_for_str_ptr(uchar* base[], uint number_of_elements, + size_s size_of_element,uchar *buffer[]); +extern qsort_t qsort2(void *base_ptr, size_t total_elems, size_t size, + qsort2_cmp cmp, void *cmp_argument); +extern qsort2_cmp get_ptr_compare(uint); +void my_store_ptr(byte *buff, uint pack_length, my_off_t pos); +my_off_t my_get_ptr(byte *ptr, uint pack_length); +extern int init_io_cache(IO_CACHE *info,File file,uint cachesize, + enum cache_type type,my_off_t seek_offset, + pbool use_async_io, myf cache_myflags); +extern my_bool reinit_io_cache(IO_CACHE *info,enum cache_type type, + my_off_t seek_offset,pbool use_async_io, + pbool clear_cache); +extern void setup_io_cache(IO_CACHE* info); +extern int _my_b_read(IO_CACHE *info,byte *Buffer,uint Count); +#ifdef THREAD +extern int _my_b_read_r(IO_CACHE *info,byte *Buffer,uint Count); +extern void init_io_cache_share(IO_CACHE *info, + IO_CACHE_SHARE *s, uint num_threads); +extern void remove_io_thread(IO_CACHE *info); +#endif +extern int _my_b_seq_read(IO_CACHE *info,byte *Buffer,uint Count); +extern int _my_b_net_read(IO_CACHE *info,byte *Buffer,uint Count); +extern int _my_b_get(IO_CACHE *info); +extern int _my_b_async_read(IO_CACHE *info,byte *Buffer,uint Count); +extern int _my_b_write(IO_CACHE *info,const byte *Buffer,uint Count); +extern int my_b_append(IO_CACHE *info,const byte *Buffer,uint Count); +extern int my_b_safe_write(IO_CACHE *info,const byte *Buffer,uint Count); + +extern int my_block_write(IO_CACHE *info, const byte *Buffer, + uint Count, my_off_t pos); +extern int my_b_flush_io_cache(IO_CACHE *info, int need_append_buffer_lock); + +#define flush_io_cache(info) my_b_flush_io_cache((info),1) + +extern int end_io_cache(IO_CACHE *info); +extern uint my_b_fill(IO_CACHE *info); +extern void my_b_seek(IO_CACHE *info,my_off_t pos); +extern uint my_b_gets(IO_CACHE *info, char *to, uint max_length); +extern my_off_t my_b_filelength(IO_CACHE *info); +extern uint my_b_printf(IO_CACHE *info, const char* fmt, ...); +extern uint my_b_vprintf(IO_CACHE *info, const char* fmt, va_list ap); +extern my_bool open_cached_file(IO_CACHE *cache,const char *dir, + const char *prefix, uint cache_size, + myf cache_myflags); +extern my_bool real_open_cached_file(IO_CACHE *cache); +extern void close_cached_file(IO_CACHE *cache); +File create_temp_file(char *to, const char *dir, const char *pfx, + int mode, myf MyFlags); +#define my_init_dynamic_array(A,B,C,D) init_dynamic_array(A,B,C,D CALLER_INFO) +#define my_init_dynamic_array_ci(A,B,C,D) init_dynamic_array(A,B,C,D ORIG_CALLER_INFO) +extern my_bool init_dynamic_array(DYNAMIC_ARRAY *array,uint element_size, + uint init_alloc,uint alloc_increment + CALLER_INFO_PROTO); +extern my_bool insert_dynamic(DYNAMIC_ARRAY *array,gptr element); +extern byte *alloc_dynamic(DYNAMIC_ARRAY *array); +extern byte *pop_dynamic(DYNAMIC_ARRAY*); +extern my_bool set_dynamic(DYNAMIC_ARRAY *array,gptr element,uint array_index); +extern void get_dynamic(DYNAMIC_ARRAY *array,gptr element,uint array_index); +extern void delete_dynamic(DYNAMIC_ARRAY *array); +extern void delete_dynamic_element(DYNAMIC_ARRAY *array, uint array_index); +extern void freeze_size(DYNAMIC_ARRAY *array); +#define dynamic_array_ptr(array,array_index) ((array)->buffer+(array_index)*(array)->size_of_element) +#define dynamic_element(array,array_index,type) ((type)((array)->buffer) +(array_index)) +#define push_dynamic(A,B) insert_dynamic(A,B) +#define reset_dynamic(array) ((array)->elements= 0) + +extern my_bool init_dynamic_string(DYNAMIC_STRING *str, const char *init_str, + uint init_alloc,uint alloc_increment); +extern my_bool dynstr_append(DYNAMIC_STRING *str, const char *append); +my_bool dynstr_append_mem(DYNAMIC_STRING *str, const char *append, + uint length); +extern my_bool dynstr_set(DYNAMIC_STRING *str, const char *init_str); +extern my_bool dynstr_realloc(DYNAMIC_STRING *str, ulong additional_size); +extern void dynstr_free(DYNAMIC_STRING *str); +#ifdef HAVE_MLOCK +extern byte *my_malloc_lock(uint length,myf flags); +extern void my_free_lock(byte *ptr,myf flags); +#else +#define my_malloc_lock(A,B) my_malloc((A),(B)) +#define my_free_lock(A,B) my_free((A),(B)) +#endif +#define alloc_root_inited(A) ((A)->min_malloc != 0) +#define ALLOC_ROOT_MIN_BLOCK_SIZE (MALLOC_OVERHEAD + sizeof(USED_MEM) + 8) +#define clear_alloc_root(A) do { (A)->free= (A)->used= (A)->pre_alloc= 0; (A)->min_malloc=0;} while(0) +extern void init_alloc_root(MEM_ROOT *mem_root, uint block_size, + uint pre_alloc_size); +extern gptr alloc_root(MEM_ROOT *mem_root,unsigned int Size); +extern gptr multi_alloc_root(MEM_ROOT *mem_root, ...); +extern void free_root(MEM_ROOT *root, myf MyFLAGS); +extern void set_prealloc_root(MEM_ROOT *root, char *ptr); +extern void reset_root_defaults(MEM_ROOT *mem_root, uint block_size, + uint prealloc_size); +extern char *strdup_root(MEM_ROOT *root,const char *str); +extern char *strmake_root(MEM_ROOT *root,const char *str,uint len); +extern char *memdup_root(MEM_ROOT *root,const char *str,uint len); +extern int get_defaults_options(int argc, char **argv, + char **defaults, char **extra_defaults, + char **group_suffix); +extern int load_defaults(const char *conf_file, const char **groups, + int *argc, char ***argv); +extern int modify_defaults_file(const char *file_location, const char *option, + const char *option_value, + const char *section_name, int remove_option); +extern int my_search_option_files(const char *conf_file, int *argc, + char ***argv, uint *args_used, + Process_option_func func, void *func_ctx); +extern void free_defaults(char **argv); +extern void my_print_default_files(const char *conf_file); +extern void print_defaults(const char *conf_file, const char **groups); +extern my_bool my_compress(byte *, ulong *, ulong *); +extern my_bool my_uncompress(byte *, ulong *, ulong *); +extern byte *my_compress_alloc(const byte *packet, ulong *len, ulong *complen); +extern ha_checksum my_checksum(ha_checksum crc, const byte *mem, uint count); +extern uint my_bit_log2(ulong value); +extern uint my_count_bits(ulonglong v); +extern uint my_count_bits_ushort(ushort v); +extern void my_sleep(ulong m_seconds); +extern ulong crc32(ulong crc, const uchar *buf, uint len); +extern uint my_set_max_open_files(uint files); +void my_free_open_file_info(void); + +ulonglong my_getsystime(void); +my_bool my_gethwaddr(uchar *to); + +#ifdef HAVE_SYS_MMAN_H +#include + +#ifndef MAP_NOSYNC +#define MAP_NOSYNC 0 +#endif + +#define my_mmap(a,b,c,d,e,f) mmap(a,b,c,d,e,f) +#ifdef HAVE_GETPAGESIZE +#define my_getpagesize() getpagesize() +#else +/* qnx ? */ +#define my_getpagesize() 8192 +#endif +#define my_munmap(a,b) munmap((a),(b)) + +#else +/* not a complete set of mmap() flags, but only those that nesessary */ +#define PROT_READ 1 +#define PROT_WRITE 2 +#define MAP_SHARED 0x0001 +#define MAP_NOSYNC 0x0800 +#define MAP_FAILED ((void *)-1) +#define MS_SYNC 0x0000 + +#ifndef __NETWARE__ +#define HAVE_MMAP +#endif + +int my_getpagesize(void); +void *my_mmap(void *, size_t, int, int, int, my_off_t); +int my_munmap(void *, size_t); +#endif + +int my_msync(int, void *, size_t, int); + +/* character sets */ +extern uint get_charset_number(const char *cs_name, uint cs_flags); +extern uint get_collation_number(const char *name); +extern const char *get_charset_name(uint cs_number); + +extern CHARSET_INFO *get_charset(uint cs_number, myf flags); +extern CHARSET_INFO *get_charset_by_name(const char *cs_name, myf flags); +extern CHARSET_INFO *get_charset_by_csname(const char *cs_name, + uint cs_flags, myf my_flags); +extern void free_charsets(void); +extern char *get_charsets_dir(char *buf); +extern my_bool my_charset_same(CHARSET_INFO *cs1, CHARSET_INFO *cs2); +extern my_bool init_compiled_charsets(myf flags); +extern void add_compiled_collation(CHARSET_INFO *cs); +extern ulong escape_string_for_mysql(CHARSET_INFO *charset_info, + char *to, ulong to_length, + const char *from, ulong length); +#ifdef __WIN__ +#define BACKSLASH_MBTAIL +/* File system character set */ +extern CHARSET_INFO *fs_character_set(void); +#endif +extern ulong escape_quotes_for_mysql(CHARSET_INFO *charset_info, + char *to, ulong to_length, + const char *from, ulong length); + +extern void thd_increment_bytes_sent(ulong length); +extern void thd_increment_bytes_received(ulong length); +extern void thd_increment_net_big_packet_count(ulong length); + +#ifdef __WIN__ +extern my_bool have_tcpip; /* Is set if tcpip is used */ + +/* implemented in my_windac.c */ + +int my_security_attr_create(SECURITY_ATTRIBUTES **psa, const char **perror, + DWORD owner_rights, DWORD everybody_rights); + +void my_security_attr_free(SECURITY_ATTRIBUTES *sa); + +/* implemented in my_conio.c */ +char* my_cgets(char *string, unsigned long clen, unsigned long* plen); + +#endif +#ifdef __NETWARE__ +void netware_reg_user(const char *ip, const char *user, + const char *application); +#endif + +C_MODE_END +#include "raid.h" +#endif /* _my_sys_h */ diff --git a/Src/3rd_party/mysql/include/mysql.h b/Src/3rd_party/mysql/include/mysql.h new file mode 100644 index 0000000..c4b4e02 --- /dev/null +++ b/Src/3rd_party/mysql/include/mysql.h @@ -0,0 +1,839 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _mysql_h +#define _mysql_h + +#ifdef __CYGWIN__ /* CYGWIN implements a UNIX API */ +#undef WIN +#undef _WIN +#undef _WIN32 +#undef _WIN64 +#undef __WIN__ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _global_h /* If not standard header */ +#include +#ifdef __LCC__ +#include /* For windows */ +#endif +typedef char my_bool; +#if (defined(_WIN32) || defined(_WIN64)) && !defined(__WIN__) +#define __WIN__ +#endif +#if !defined(__WIN__) +#define STDCALL +#else +#define STDCALL __stdcall +#endif +typedef char * gptr; + +#ifndef my_socket_defined +#ifdef __WIN__ +#define my_socket SOCKET +#else +typedef int my_socket; +#endif /* __WIN__ */ +#endif /* my_socket_defined */ +#endif /* _global_h */ + +#include "mysql_com.h" +#include "mysql_time.h" +#include "mysql_version.h" +#include "typelib.h" + +#include "my_list.h" /* for LISTs used in 'MYSQL' and 'MYSQL_STMT' */ + +extern unsigned int mysql_port; +extern char *mysql_unix_port; + +#define CLIENT_NET_READ_TIMEOUT 365*24*3600 /* Timeout on read */ +#define CLIENT_NET_WRITE_TIMEOUT 365*24*3600 /* Timeout on write */ + +#ifdef __NETWARE__ +#pragma pack(push, 8) /* 8 byte alignment */ +#endif + +#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) +#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) +#define IS_BLOB(n) ((n) & BLOB_FLAG) +#define IS_NUM(t) ((t) <= FIELD_TYPE_INT24 || (t) == FIELD_TYPE_YEAR || (t) == FIELD_TYPE_NEWDECIMAL) +#define IS_NUM_FIELD(f) ((f)->flags & NUM_FLAG) +#define INTERNAL_NUM_FIELD(f) (((f)->type <= FIELD_TYPE_INT24 && ((f)->type != FIELD_TYPE_TIMESTAMP || (f)->length == 14 || (f)->length == 8)) || (f)->type == FIELD_TYPE_YEAR) + + +typedef struct st_mysql_field { + char *name; /* Name of column */ + char *org_name; /* Original column name, if an alias */ + char *table; /* Table of column if column was a field */ + char *org_table; /* Org table name, if table was an alias */ + char *db; /* Database for table */ + char *catalog; /* Catalog for table */ + char *def; /* Default value (set by mysql_list_fields) */ + unsigned long length; /* Width of column (create length) */ + unsigned long max_length; /* Max width for selected set */ + unsigned int name_length; + unsigned int org_name_length; + unsigned int table_length; + unsigned int org_table_length; + unsigned int db_length; + unsigned int catalog_length; + unsigned int def_length; + unsigned int flags; /* Div flags */ + unsigned int decimals; /* Number of decimals in field */ + unsigned int charsetnr; /* Character set */ + enum enum_field_types type; /* Type of field. See mysql_com.h for types */ +} MYSQL_FIELD; + +typedef char **MYSQL_ROW; /* return data as array of strings */ +typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ + +#ifndef _global_h +#if defined(NO_CLIENT_LONG_LONG) +typedef unsigned long my_ulonglong; +#elif defined (__WIN__) +typedef unsigned __int64 my_ulonglong; +#else +typedef unsigned long long my_ulonglong; +#endif +#endif + +#define MYSQL_COUNT_ERROR (~(my_ulonglong) 0) + +typedef struct st_mysql_rows { + struct st_mysql_rows *next; /* list of rows */ + MYSQL_ROW data; + unsigned long length; +} MYSQL_ROWS; + +typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ + +#include "my_alloc.h" + +typedef struct st_mysql_data { + my_ulonglong rows; + unsigned int fields; + MYSQL_ROWS *data; + MEM_ROOT alloc; +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || defined(EMBEDDED_LIBRARY) + MYSQL_ROWS **prev_ptr; +#endif +} MYSQL_DATA; + +enum mysql_option +{ + MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT +}; + +struct st_mysql_options { + unsigned int connect_timeout, read_timeout, write_timeout; + unsigned int port, protocol; + unsigned long client_flag; + char *host,*user,*password,*unix_socket,*db; + struct st_dynamic_array *init_commands; + char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; + char *ssl_key; /* PEM key file */ + char *ssl_cert; /* PEM cert file */ + char *ssl_ca; /* PEM CA file */ + char *ssl_capath; /* PEM directory of CA-s? */ + char *ssl_cipher; /* cipher to use */ + char *shared_memory_base_name; + unsigned long max_allowed_packet; + my_bool use_ssl; /* if to use SSL or not */ + my_bool compress,named_pipe; + /* + On connect, find out the replication role of the server, and + establish connections to all the peers + */ + my_bool rpl_probe; + /* + Each call to mysql_real_query() will parse it to tell if it is a read + or a write, and direct it to the slave or the master + */ + my_bool rpl_parse; + /* + If set, never read from a master, only from slave, when doing + a read that is replication-aware + */ + my_bool no_master_reads; +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || defined(EMBEDDED_LIBRARY) + my_bool separate_thread; +#endif + enum mysql_option methods_to_use; + char *client_ip; + /* Refuse client connecting to server if it uses old (pre-4.1.1) protocol */ + my_bool secure_auth; + /* 0 - never report, 1 - always report (default) */ + my_bool report_data_truncation; + + /* function pointers for local infile support */ + int (*local_infile_init)(void **, const char *, void *); + int (*local_infile_read)(void *, char *, unsigned int); + void (*local_infile_end)(void *); + int (*local_infile_error)(void *, char *, unsigned int); + void *local_infile_userdata; +}; + +enum mysql_status +{ + MYSQL_STATUS_READY,MYSQL_STATUS_GET_RESULT,MYSQL_STATUS_USE_RESULT +}; + +enum mysql_protocol_type +{ + MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, + MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY +}; +/* + There are three types of queries - the ones that have to go to + the master, the ones that go to a slave, and the adminstrative + type which must happen on the pivot connectioin +*/ +enum mysql_rpl_type +{ + MYSQL_RPL_MASTER, MYSQL_RPL_SLAVE, MYSQL_RPL_ADMIN +}; + +typedef struct character_set +{ + unsigned int number; /* character set number */ + unsigned int state; /* character set state */ + const char *csname; /* collation name */ + const char *name; /* character set name */ + const char *comment; /* comment */ + const char *dir; /* character set directory */ + unsigned int mbminlen; /* min. length for multibyte strings */ + unsigned int mbmaxlen; /* max. length for multibyte strings */ +} MY_CHARSET_INFO; + +struct st_mysql_methods; + +typedef struct st_mysql +{ + NET net; /* Communication parameters */ + gptr connector_fd; /* ConnectorFd for SSL */ + char *host,*user,*passwd,*unix_socket,*server_version,*host_info,*info; + char *db; + struct charset_info_st *charset; + MYSQL_FIELD *fields; + MEM_ROOT field_alloc; + my_ulonglong affected_rows; + my_ulonglong insert_id; /* id if insert on table with NEXTNR */ + my_ulonglong extra_info; /* Not used */ + unsigned long thread_id; /* Id for connection in server */ + unsigned long packet_length; + unsigned int port; + unsigned long client_flag,server_capabilities; + unsigned int protocol_version; + unsigned int field_count; + unsigned int server_status; + unsigned int server_language; + unsigned int warning_count; + struct st_mysql_options options; + enum mysql_status status; + my_bool free_me; /* If free in mysql_close */ + my_bool reconnect; /* set to 1 if automatic reconnect */ + + /* session-wide random string */ + char scramble[SCRAMBLE_LENGTH+1]; + + /* + Set if this is the original connection, not a master or a slave we have + added though mysql_rpl_probe() or mysql_set_master()/ mysql_add_slave() + */ + my_bool rpl_pivot; + /* + Pointers to the master, and the next slave connections, points to + itself if lone connection. + */ + struct st_mysql* master, *next_slave; + + struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ + /* needed for send/read/store/use result to work correctly with replication */ + struct st_mysql* last_used_con; + + LIST *stmts; /* list of all statements */ + const struct st_mysql_methods *methods; + void *thd; + /* + Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag + from mysql_stmt_close if close had to cancel result set of this object. + */ + my_bool *unbuffered_fetch_owner; +} MYSQL; + +typedef struct st_mysql_res { + my_ulonglong row_count; + MYSQL_FIELD *fields; + MYSQL_DATA *data; + MYSQL_ROWS *data_cursor; + unsigned long *lengths; /* column lengths of current row */ + MYSQL *handle; /* for unbuffered reads */ + MEM_ROOT field_alloc; + unsigned int field_count, current_field; + MYSQL_ROW row; /* If unbuffered read */ + MYSQL_ROW current_row; /* buffer to current row */ + my_bool eof; /* Used by mysql_fetch_row */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + const struct st_mysql_methods *methods; +} MYSQL_RES; + +#define MAX_MYSQL_MANAGER_ERR 256 +#define MAX_MYSQL_MANAGER_MSG 256 + +#define MANAGER_OK 200 +#define MANAGER_INFO 250 +#define MANAGER_ACCESS 401 +#define MANAGER_CLIENT_ERR 450 +#define MANAGER_INTERNAL_ERR 500 + +#if !defined(MYSQL_SERVER) && !defined(MYSQL_CLIENT) +#define MYSQL_CLIENT +#endif + + +typedef struct st_mysql_manager +{ + NET net; + char *host,*user,*passwd; + unsigned int port; + my_bool free_me; + my_bool eof; + int cmd_status; + int last_errno; + char* net_buf,*net_buf_pos,*net_data_end; + int net_buf_size; + char last_error[MAX_MYSQL_MANAGER_ERR]; +} MYSQL_MANAGER; + +typedef struct st_mysql_parameters +{ + unsigned long *p_max_allowed_packet; + unsigned long *p_net_buffer_length; +} MYSQL_PARAMETERS; + +#if !defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) +#define max_allowed_packet (*mysql_get_parameters()->p_max_allowed_packet) +#define net_buffer_length (*mysql_get_parameters()->p_net_buffer_length) +#endif + +/* + Set up and bring down the server; to ensure that applications will + work when linked against either the standard client library or the + embedded server library, these functions should be called. +*/ +int STDCALL mysql_server_init(int argc, char **argv, char **groups); +void STDCALL mysql_server_end(void); +/* + mysql_server_init/end need to be called when using libmysqld or + libmysqlclient (exactly, mysql_server_init() is called by mysql_init() so + you don't need to call it explicitely; but you need to call + mysql_server_end() to free memory). The names are a bit misleading + (mysql_SERVER* to be used when using libmysqlCLIENT). So we add more general + names which suit well whether you're using libmysqld or libmysqlclient. We + intend to promote these aliases over the mysql_server* ones. +*/ +#define mysql_library_init mysql_server_init +#define mysql_library_end mysql_server_end + +MYSQL_PARAMETERS *STDCALL mysql_get_parameters(void); + +/* + Set up and bring down a thread; these function should be called + for each thread in an application which opens at least one MySQL + connection. All uses of the connection(s) should be between these + function calls. +*/ +my_bool STDCALL mysql_thread_init(void); +void STDCALL mysql_thread_end(void); + +/* + Functions to get information from the MYSQL and MYSQL_RES structures + Should definitely be used if one uses shared libraries. +*/ + +my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res); +unsigned int STDCALL mysql_num_fields(MYSQL_RES *res); +my_bool STDCALL mysql_eof(MYSQL_RES *res); +MYSQL_FIELD *STDCALL mysql_fetch_field_direct(MYSQL_RES *res, + unsigned int fieldnr); +MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res); +MYSQL_ROW_OFFSET STDCALL mysql_row_tell(MYSQL_RES *res); +MYSQL_FIELD_OFFSET STDCALL mysql_field_tell(MYSQL_RES *res); + +unsigned int STDCALL mysql_field_count(MYSQL *mysql); +my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql); +my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql); +unsigned int STDCALL mysql_errno(MYSQL *mysql); +const char * STDCALL mysql_error(MYSQL *mysql); +const char *STDCALL mysql_sqlstate(MYSQL *mysql); +unsigned int STDCALL mysql_warning_count(MYSQL *mysql); +const char * STDCALL mysql_info(MYSQL *mysql); +unsigned long STDCALL mysql_thread_id(MYSQL *mysql); +const char * STDCALL mysql_character_set_name(MYSQL *mysql); +int STDCALL mysql_set_character_set(MYSQL *mysql, const char *csname); + +MYSQL * STDCALL mysql_init(MYSQL *mysql); +my_bool STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, + const char *cert, const char *ca, + const char *capath, const char *cipher); +my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, + const char *passwd, const char *db); +MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, + const char *user, + const char *passwd, + const char *db, + unsigned int port, + const char *unix_socket, + unsigned long clientflag); +int STDCALL mysql_select_db(MYSQL *mysql, const char *db); +int STDCALL mysql_query(MYSQL *mysql, const char *q); +int STDCALL mysql_send_query(MYSQL *mysql, const char *q, + unsigned long length); +int STDCALL mysql_real_query(MYSQL *mysql, const char *q, + unsigned long length); +MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql); + +/* perform query on master */ +my_bool STDCALL mysql_master_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_master_send_query(MYSQL *mysql, const char *q, + unsigned long length); +/* perform query on slave */ +my_bool STDCALL mysql_slave_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_slave_send_query(MYSQL *mysql, const char *q, + unsigned long length); +void STDCALL mysql_get_character_set_info(MYSQL *mysql, + MY_CHARSET_INFO *charset); + +/* local infile support */ + +#define LOCAL_INFILE_ERROR_LEN 512 + +void +mysql_set_local_infile_handler(MYSQL *mysql, + int (*local_infile_init)(void **, const char *, + void *), + int (*local_infile_read)(void *, char *, + unsigned int), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char*, + unsigned int), + void *); + +void +mysql_set_local_infile_default(MYSQL *mysql); + + +/* + enable/disable parsing of all queries to decide if they go on master or + slave +*/ +void STDCALL mysql_enable_rpl_parse(MYSQL* mysql); +void STDCALL mysql_disable_rpl_parse(MYSQL* mysql); +/* get the value of the parse flag */ +int STDCALL mysql_rpl_parse_enabled(MYSQL* mysql); + +/* enable/disable reads from master */ +void STDCALL mysql_enable_reads_from_master(MYSQL* mysql); +void STDCALL mysql_disable_reads_from_master(MYSQL* mysql); +/* get the value of the master read flag */ +my_bool STDCALL mysql_reads_from_master_enabled(MYSQL* mysql); + +enum mysql_rpl_type STDCALL mysql_rpl_query_type(const char* q, int len); + +/* discover the master and its slaves */ +my_bool STDCALL mysql_rpl_probe(MYSQL* mysql); + +/* set the master, close/free the old one, if it is not a pivot */ +int STDCALL mysql_set_master(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); +int STDCALL mysql_add_slave(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); + +int STDCALL mysql_shutdown(MYSQL *mysql, + enum mysql_enum_shutdown_level + shutdown_level); +int STDCALL mysql_dump_debug_info(MYSQL *mysql); +int STDCALL mysql_refresh(MYSQL *mysql, + unsigned int refresh_options); +int STDCALL mysql_kill(MYSQL *mysql,unsigned long pid); +int STDCALL mysql_set_server_option(MYSQL *mysql, + enum enum_mysql_set_option + option); +int STDCALL mysql_ping(MYSQL *mysql); +const char * STDCALL mysql_stat(MYSQL *mysql); +const char * STDCALL mysql_get_server_info(MYSQL *mysql); +const char * STDCALL mysql_get_client_info(void); +unsigned long STDCALL mysql_get_client_version(void); +const char * STDCALL mysql_get_host_info(MYSQL *mysql); +unsigned long STDCALL mysql_get_server_version(MYSQL *mysql); +unsigned int STDCALL mysql_get_proto_info(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_list_dbs(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_tables(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_processes(MYSQL *mysql); +int STDCALL mysql_options(MYSQL *mysql,enum mysql_option option, + const char *arg); +void STDCALL mysql_free_result(MYSQL_RES *result); +void STDCALL mysql_data_seek(MYSQL_RES *result, + my_ulonglong offset); +MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *result, + MYSQL_ROW_OFFSET offset); +MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *result, + MYSQL_FIELD_OFFSET offset); +MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result); +unsigned long * STDCALL mysql_fetch_lengths(MYSQL_RES *result); +MYSQL_FIELD * STDCALL mysql_fetch_field(MYSQL_RES *result); +MYSQL_RES * STDCALL mysql_list_fields(MYSQL *mysql, const char *table, + const char *wild); +unsigned long STDCALL mysql_escape_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_hex_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, + char *to,const char *from, + unsigned long length); +void STDCALL mysql_debug(const char *debug); +char * STDCALL mysql_odbc_escape_string(MYSQL *mysql, + char *to, + unsigned long to_length, + const char *from, + unsigned long from_length, + void *param, + char * + (*extend_buffer) + (void *, char *to, + unsigned long *length)); +void STDCALL myodbc_remove_escape(MYSQL *mysql,char *name); +unsigned int STDCALL mysql_thread_safe(void); +my_bool STDCALL mysql_embedded(void); +MYSQL_MANAGER* STDCALL mysql_manager_init(MYSQL_MANAGER* con); +MYSQL_MANAGER* STDCALL mysql_manager_connect(MYSQL_MANAGER* con, + const char* host, + const char* user, + const char* passwd, + unsigned int port); +void STDCALL mysql_manager_close(MYSQL_MANAGER* con); +int STDCALL mysql_manager_command(MYSQL_MANAGER* con, + const char* cmd, int cmd_len); +int STDCALL mysql_manager_fetch_line(MYSQL_MANAGER* con, + char* res_buf, + int res_buf_size); +my_bool STDCALL mysql_read_query_result(MYSQL *mysql); + + +/* + The following definitions are added for the enhanced + client-server protocol +*/ + +/* statement state */ +enum enum_mysql_stmt_state +{ + MYSQL_STMT_INIT_DONE= 1, MYSQL_STMT_PREPARE_DONE, MYSQL_STMT_EXECUTE_DONE, + MYSQL_STMT_FETCH_DONE +}; + + +/* + This structure is used to define bind information, and + internally by the client library. + Public members with their descriptions are listed below + (conventionally `On input' refers to the binds given to + mysql_stmt_bind_param, `On output' refers to the binds given + to mysql_stmt_bind_result): + + buffer_type - One of the MYSQL_* types, used to describe + the host language type of buffer. + On output: if column type is different from + buffer_type, column value is automatically converted + to buffer_type before it is stored in the buffer. + buffer - On input: points to the buffer with input data. + On output: points to the buffer capable to store + output data. + The type of memory pointed by buffer must correspond + to buffer_type. See the correspondence table in + the comment to mysql_stmt_bind_param. + + The two above members are mandatory for any kind of bind. + + buffer_length - the length of the buffer. You don't have to set + it for any fixed length buffer: float, double, + int, etc. It must be set however for variable-length + types, such as BLOBs or STRINGs. + + length - On input: in case when lengths of input values + are different for each execute, you can set this to + point at a variable containining value length. This + way the value length can be different in each execute. + If length is not NULL, buffer_length is not used. + Note, length can even point at buffer_length if + you keep bind structures around while fetching: + this way you can change buffer_length before + each execution, everything will work ok. + On output: if length is set, mysql_stmt_fetch will + write column length into it. + + is_null - On input: points to a boolean variable that should + be set to TRUE for NULL values. + This member is useful only if your data may be + NULL in some but not all cases. + If your data is never NULL, is_null should be set to 0. + If your data is always NULL, set buffer_type + to MYSQL_TYPE_NULL, and is_null will not be used. + + is_unsigned - On input: used to signify that values provided for one + of numeric types are unsigned. + On output describes signedness of the output buffer. + If, taking into account is_unsigned flag, column data + is out of range of the output buffer, data for this column + is regarded truncated. Note that this has no correspondence + to the sign of result set column, if you need to find it out + use mysql_stmt_result_metadata. + error - where to write a truncation error if it is present. + possible error value is: + 0 no truncation + 1 value is out of range or buffer is too small + + Please note that MYSQL_BIND also has internals members. +*/ + +typedef struct st_mysql_bind +{ + unsigned long *length; /* output length pointer */ + my_bool *is_null; /* Pointer to null indicator */ + void *buffer; /* buffer to get/put data */ + /* set this if you want to track data truncations happened during fetch */ + my_bool *error; + enum enum_field_types buffer_type; /* buffer type */ + /* output buffer length, must be set when fetching str/binary */ + unsigned long buffer_length; + unsigned char *row_ptr; /* for the current data position */ + unsigned long offset; /* offset position for char/binary fetch */ + unsigned long length_value; /* Used if length is 0 */ + unsigned int param_number; /* For null count and error messages */ + unsigned int pack_length; /* Internal length for packed data */ + my_bool error_value; /* used if error is 0 */ + my_bool is_unsigned; /* set if integer type is unsigned */ + my_bool long_data_used; /* If used with mysql_send_long_data */ + my_bool is_null_value; /* Used if is_null is 0 */ + void (*store_param_func)(NET *net, struct st_mysql_bind *param); + void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *, + unsigned char **row); + void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *, + unsigned char **row); +} MYSQL_BIND; + + +/* statement handler */ +typedef struct st_mysql_stmt +{ + MEM_ROOT mem_root; /* root allocations */ + LIST list; /* list to keep track of all stmts */ + MYSQL *mysql; /* connection handle */ + MYSQL_BIND *params; /* input parameters */ + MYSQL_BIND *bind; /* output parameters */ + MYSQL_FIELD *fields; /* result set metadata */ + MYSQL_DATA result; /* cached result set */ + MYSQL_ROWS *data_cursor; /* current row in cached result */ + /* copy of mysql->affected_rows after statement execution */ + my_ulonglong affected_rows; + my_ulonglong insert_id; /* copy of mysql->insert_id */ + /* + mysql_stmt_fetch() calls this function to fetch one row (it's different + for buffered, unbuffered and cursor fetch). + */ + int (*read_row_func)(struct st_mysql_stmt *stmt, + unsigned char **row); + unsigned long stmt_id; /* Id for prepared statement */ + unsigned long flags; /* i.e. type of cursor to open */ + unsigned long prefetch_rows; /* number of rows per one COM_FETCH */ + /* + Copied from mysql->server_status after execute/fetch to know + server-side cursor status for this statement. + */ + unsigned int server_status; + unsigned int last_errno; /* error code */ + unsigned int param_count; /* input parameter count */ + unsigned int field_count; /* number of columns in result set */ + enum enum_mysql_stmt_state state; /* statement state */ + char last_error[MYSQL_ERRMSG_SIZE]; /* error message */ + char sqlstate[SQLSTATE_LENGTH+1]; + /* Types of input parameters should be sent to server */ + my_bool send_types_to_server; + my_bool bind_param_done; /* input buffers were supplied */ + unsigned char bind_result_done; /* output buffers were supplied */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + /* + Is set to true if we need to calculate field->max_length for + metadata fields when doing mysql_stmt_store_result. + */ + my_bool update_max_length; +} MYSQL_STMT; + +enum enum_stmt_attr_type +{ + /* + When doing mysql_stmt_store_result calculate max_length attribute + of statement metadata. This is to be consistent with the old API, + where this was done automatically. + In the new API we do that only by request because it slows down + mysql_stmt_store_result sufficiently. + */ + STMT_ATTR_UPDATE_MAX_LENGTH, + /* + unsigned long with combination of cursor flags (read only, for update, + etc) + */ + STMT_ATTR_CURSOR_TYPE, + /* + Amount of rows to retrieve from server per one fetch if using cursors. + Accepts unsigned long attribute in the range 1 - ulong_max + */ + STMT_ATTR_PREFETCH_ROWS +}; + + +typedef struct st_mysql_methods +{ + my_bool (*read_query_result)(MYSQL *mysql); + my_bool (*advanced_command)(MYSQL *mysql, + enum enum_server_command command, + const char *header, + unsigned long header_length, + const char *arg, + unsigned long arg_length, + my_bool skip_check); + MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + unsigned int fields); + MYSQL_RES * (*use_result)(MYSQL *mysql); + void (*fetch_lengths)(unsigned long *to, + MYSQL_ROW column, unsigned int field_count); + void (*flush_use_result)(MYSQL *mysql); +#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) + MYSQL_FIELD * (*list_fields)(MYSQL *mysql); + my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); + int (*stmt_execute)(MYSQL_STMT *stmt); + int (*read_binary_rows)(MYSQL_STMT *stmt); + int (*unbuffered_fetch)(MYSQL *mysql, char **row); + void (*free_embedded_thd)(MYSQL *mysql); + const char *(*read_statistics)(MYSQL *mysql); + my_bool (*next_result)(MYSQL *mysql); + int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); +#endif +} MYSQL_METHODS; + + +MYSQL_STMT * STDCALL mysql_stmt_init(MYSQL *mysql); +int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, + unsigned long length); +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, + unsigned int column, + unsigned long offset); +int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt); +unsigned long STDCALL mysql_stmt_param_count(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *attr); +my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + void *attr); +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_close(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_reset(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt); +my_bool STDCALL mysql_stmt_send_long_data(MYSQL_STMT *stmt, + unsigned int param_number, + const char *data, + unsigned long length); +MYSQL_RES *STDCALL mysql_stmt_result_metadata(MYSQL_STMT *stmt); +MYSQL_RES *STDCALL mysql_stmt_param_metadata(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_errno(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_error(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_sqlstate(MYSQL_STMT * stmt); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_seek(MYSQL_STMT *stmt, + MYSQL_ROW_OFFSET offset); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_tell(MYSQL_STMT *stmt); +void STDCALL mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset); +my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_field_count(MYSQL_STMT *stmt); + +my_bool STDCALL mysql_commit(MYSQL * mysql); +my_bool STDCALL mysql_rollback(MYSQL * mysql); +my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode); +my_bool STDCALL mysql_more_results(MYSQL *mysql); +int STDCALL mysql_next_result(MYSQL *mysql); +void STDCALL mysql_close(MYSQL *sock); + + +/* status return codes */ +#define MYSQL_NO_DATA 100 +#define MYSQL_DATA_TRUNCATED 101 + +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) + +#ifdef USE_OLD_FUNCTIONS +MYSQL * STDCALL mysql_connect(MYSQL *mysql, const char *host, + const char *user, const char *passwd); +int STDCALL mysql_create_db(MYSQL *mysql, const char *DB); +int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) +#endif +#define HAVE_MYSQL_REAL_CONNECT + +/* + The following functions are mainly exported because of mysqlbinlog; + They are not for general usage +*/ + +#define simple_command(mysql, command, arg, length, skip_check) \ + (*(mysql)->methods->advanced_command)(mysql, command, \ + NullS, 0, arg, length, skip_check) +unsigned long net_safe_read(MYSQL* mysql); + +#ifdef __NETWARE__ +#pragma pack(pop) /* restore alignment */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _mysql_h */ diff --git a/Src/3rd_party/mysql/include/mysql_com.h b/Src/3rd_party/mysql/include/mysql_com.h new file mode 100644 index 0000000..1e595cd --- /dev/null +++ b/Src/3rd_party/mysql/include/mysql_com.h @@ -0,0 +1,444 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* +** Common definition between mysql server & client +*/ + +#ifndef _mysql_com_h +#define _mysql_com_h + +#define NAME_LEN 64 /* Field/table name length */ +#define HOSTNAME_LENGTH 60 +#define USERNAME_LENGTH 16 +#define SERVER_VERSION_LENGTH 60 +#define SQLSTATE_LENGTH 5 + +#define LOCAL_HOST "localhost" +#define LOCAL_HOST_NAMEDPIPE "." + + +#if defined(__WIN__) && !defined( _CUSTOMCONFIG_) +#define MYSQL_NAMEDPIPE "MySQL" +#define MYSQL_SERVICENAME "MySQL" +#endif /* __WIN__ */ + +/* + You should add new commands to the end of this list, otherwise old + servers won't be able to handle them as 'unsupported'. +*/ + +enum enum_server_command +{ + COM_SLEEP, COM_QUIT, COM_INIT_DB, COM_QUERY, COM_FIELD_LIST, + COM_CREATE_DB, COM_DROP_DB, COM_REFRESH, COM_SHUTDOWN, COM_STATISTICS, + COM_PROCESS_INFO, COM_CONNECT, COM_PROCESS_KILL, COM_DEBUG, COM_PING, + COM_TIME, COM_DELAYED_INSERT, COM_CHANGE_USER, COM_BINLOG_DUMP, + COM_TABLE_DUMP, COM_CONNECT_OUT, COM_REGISTER_SLAVE, + COM_STMT_PREPARE, COM_STMT_EXECUTE, COM_STMT_SEND_LONG_DATA, COM_STMT_CLOSE, + COM_STMT_RESET, COM_SET_OPTION, COM_STMT_FETCH, + /* don't forget to update const char *command_name[] in sql_parse.cc */ + + /* Must be last */ + COM_END +}; + + +/* + Length of random string sent by server on handshake; this is also length of + obfuscated password, recieved from client +*/ +#define SCRAMBLE_LENGTH 20 +#define SCRAMBLE_LENGTH_323 8 +/* length of password stored in the db: new passwords are preceeded with '*' */ +#define SCRAMBLED_PASSWORD_CHAR_LENGTH (SCRAMBLE_LENGTH*2+1) +#define SCRAMBLED_PASSWORD_CHAR_LENGTH_323 (SCRAMBLE_LENGTH_323*2) + + +#define NOT_NULL_FLAG 1 /* Field can't be NULL */ +#define PRI_KEY_FLAG 2 /* Field is part of a primary key */ +#define UNIQUE_KEY_FLAG 4 /* Field is part of a unique key */ +#define MULTIPLE_KEY_FLAG 8 /* Field is part of a key */ +#define BLOB_FLAG 16 /* Field is a blob */ +#define UNSIGNED_FLAG 32 /* Field is unsigned */ +#define ZEROFILL_FLAG 64 /* Field is zerofill */ +#define BINARY_FLAG 128 /* Field is binary */ + +/* The following are only sent to new clients */ +#define ENUM_FLAG 256 /* field is an enum */ +#define AUTO_INCREMENT_FLAG 512 /* field is a autoincrement field */ +#define TIMESTAMP_FLAG 1024 /* Field is a timestamp */ +#define SET_FLAG 2048 /* field is a set */ +#define NO_DEFAULT_VALUE_FLAG 4096 /* Field doesn't have default value */ +#define NUM_FLAG 32768 /* Field is num (for clients) */ +#define PART_KEY_FLAG 16384 /* Intern; Part of some key */ +#define GROUP_FLAG 32768 /* Intern: Group field */ +#define UNIQUE_FLAG 65536 /* Intern: Used by sql_yacc */ +#define BINCMP_FLAG 131072 /* Intern: Used by sql_yacc */ + +#define REFRESH_GRANT 1 /* Refresh grant tables */ +#define REFRESH_LOG 2 /* Start on new log file */ +#define REFRESH_TABLES 4 /* close all tables */ +#define REFRESH_HOSTS 8 /* Flush host cache */ +#define REFRESH_STATUS 16 /* Flush status variables */ +#define REFRESH_THREADS 32 /* Flush thread cache */ +#define REFRESH_SLAVE 64 /* Reset master info and restart slave + thread */ +#define REFRESH_MASTER 128 /* Remove all bin logs in the index + and truncate the index */ + +/* The following can't be set with mysql_refresh() */ +#define REFRESH_READ_LOCK 16384 /* Lock tables for read */ +#define REFRESH_FAST 32768 /* Intern flag */ + +/* RESET (remove all queries) from query cache */ +#define REFRESH_QUERY_CACHE 65536 +#define REFRESH_QUERY_CACHE_FREE 0x20000L /* pack query cache */ +#define REFRESH_DES_KEY_FILE 0x40000L +#define REFRESH_USER_RESOURCES 0x80000L + +#define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ +#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */ +#define CLIENT_LONG_FLAG 4 /* Get all column flags */ +#define CLIENT_CONNECT_WITH_DB 8 /* One can specify db on connect */ +#define CLIENT_NO_SCHEMA 16 /* Don't allow database.table.column */ +#define CLIENT_COMPRESS 32 /* Can use compression protocol */ +#define CLIENT_ODBC 64 /* Odbc client */ +#define CLIENT_LOCAL_FILES 128 /* Can use LOAD DATA LOCAL */ +#define CLIENT_IGNORE_SPACE 256 /* Ignore spaces before '(' */ +#define CLIENT_PROTOCOL_41 512 /* New 4.1 protocol */ +#define CLIENT_INTERACTIVE 1024 /* This is an interactive client */ +#define CLIENT_SSL 2048 /* Switch to SSL after handshake */ +#define CLIENT_IGNORE_SIGPIPE 4096 /* IGNORE sigpipes */ +#define CLIENT_TRANSACTIONS 8192 /* Client knows about transactions */ +#define CLIENT_RESERVED 16384 /* Old flag for 4.1 protocol */ +#define CLIENT_SECURE_CONNECTION 32768 /* New 4.1 authentication */ +#define CLIENT_MULTI_STATEMENTS 65536 /* Enable/disable multi-stmt support */ +#define CLIENT_MULTI_RESULTS 131072 /* Enable/disable multi-results */ +#define CLIENT_REMEMBER_OPTIONS (((ulong) 1) << 31) + +#define SERVER_STATUS_IN_TRANS 1 /* Transaction has started */ +#define SERVER_STATUS_AUTOCOMMIT 2 /* Server in auto_commit mode */ +#define SERVER_STATUS_MORE_RESULTS 4 /* More results on server */ +#define SERVER_MORE_RESULTS_EXISTS 8 /* Multi query - next query exists */ +#define SERVER_QUERY_NO_GOOD_INDEX_USED 16 +#define SERVER_QUERY_NO_INDEX_USED 32 +/* + The server was able to fulfill the clients request and opened a + read-only non-scrollable cursor for a query. This flag comes + in reply to COM_STMT_EXECUTE and COM_STMT_FETCH commands. +*/ +#define SERVER_STATUS_CURSOR_EXISTS 64 +/* + This flag is sent when a read-only cursor is exhausted, in reply to + COM_STMT_FETCH command. +*/ +#define SERVER_STATUS_LAST_ROW_SENT 128 +#define SERVER_STATUS_DB_DROPPED 256 /* A database was dropped */ +#define SERVER_STATUS_NO_BACKSLASH_ESCAPES 512 + +#define MYSQL_ERRMSG_SIZE 512 +#define NET_READ_TIMEOUT 30 /* Timeout on read */ +#define NET_WRITE_TIMEOUT 60 /* Timeout on write */ +#define NET_WAIT_TIMEOUT 8*60*60 /* Wait for new query */ + +#define ONLY_KILL_QUERY 1 + +struct st_vio; /* Only C */ +typedef struct st_vio Vio; + +#define MAX_TINYINT_WIDTH 3 /* Max width for a TINY w.o. sign */ +#define MAX_SMALLINT_WIDTH 5 /* Max width for a SHORT w.o. sign */ +#define MAX_MEDIUMINT_WIDTH 8 /* Max width for a INT24 w.o. sign */ +#define MAX_INT_WIDTH 10 /* Max width for a LONG w.o. sign */ +#define MAX_BIGINT_WIDTH 20 /* Max width for a LONGLONG */ +#define MAX_CHAR_WIDTH 255 /* Max length for a CHAR colum */ +#define MAX_BLOB_WIDTH 8192 /* Default width for blob */ + +typedef struct st_net { +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || !defined(EMBEDDED_LIBRARY) + Vio* vio; + unsigned char *buff,*buff_end,*write_pos,*read_pos; + my_socket fd; /* For Perl DBI/dbd */ + unsigned long max_packet,max_packet_size; + unsigned int pkt_nr,compress_pkt_nr; + unsigned int write_timeout, read_timeout, retry_count; + int fcntl; + my_bool compress; + /* + The following variable is set if we are doing several queries in one + command ( as in LOAD TABLE ... FROM MASTER ), + and do not want to confuse the client with OK at the wrong time + */ + unsigned long remain_in_buf,length, buf_length, where_b; + unsigned int *return_status; + unsigned char reading_or_writing; + char save_char; + my_bool no_send_ok; /* For SPs and other things that do multiple stmts */ + my_bool no_send_eof; /* For SPs' first version read-only cursors */ + /* + Set if OK packet is already sent, and we do not need to send error + messages + */ + my_bool no_send_error; + /* + Pointer to query object in query cache, do not equal NULL (0) for + queries in cache that have not stored its results yet + */ +#endif + char last_error[MYSQL_ERRMSG_SIZE], sqlstate[SQLSTATE_LENGTH+1]; + unsigned int last_errno; + unsigned char error; + gptr query_cache_query; + my_bool report_error; /* We should report error (we have unreported error) */ + my_bool return_errno; +} NET; + +#define packet_error (~(unsigned long) 0) + +enum enum_field_types { MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, + MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG, + MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE, + MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP, + MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24, + MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, + MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, + MYSQL_TYPE_BIT, + MYSQL_TYPE_NEWDECIMAL=246, + MYSQL_TYPE_ENUM=247, + MYSQL_TYPE_SET=248, + MYSQL_TYPE_TINY_BLOB=249, + MYSQL_TYPE_MEDIUM_BLOB=250, + MYSQL_TYPE_LONG_BLOB=251, + MYSQL_TYPE_BLOB=252, + MYSQL_TYPE_VAR_STRING=253, + MYSQL_TYPE_STRING=254, + MYSQL_TYPE_GEOMETRY=255 + +}; + +/* For backward compatibility */ +#define CLIENT_MULTI_QUERIES CLIENT_MULTI_STATEMENTS +#define FIELD_TYPE_DECIMAL MYSQL_TYPE_DECIMAL +#define FIELD_TYPE_NEWDECIMAL MYSQL_TYPE_NEWDECIMAL +#define FIELD_TYPE_TINY MYSQL_TYPE_TINY +#define FIELD_TYPE_SHORT MYSQL_TYPE_SHORT +#define FIELD_TYPE_LONG MYSQL_TYPE_LONG +#define FIELD_TYPE_FLOAT MYSQL_TYPE_FLOAT +#define FIELD_TYPE_DOUBLE MYSQL_TYPE_DOUBLE +#define FIELD_TYPE_NULL MYSQL_TYPE_NULL +#define FIELD_TYPE_TIMESTAMP MYSQL_TYPE_TIMESTAMP +#define FIELD_TYPE_LONGLONG MYSQL_TYPE_LONGLONG +#define FIELD_TYPE_INT24 MYSQL_TYPE_INT24 +#define FIELD_TYPE_DATE MYSQL_TYPE_DATE +#define FIELD_TYPE_TIME MYSQL_TYPE_TIME +#define FIELD_TYPE_DATETIME MYSQL_TYPE_DATETIME +#define FIELD_TYPE_YEAR MYSQL_TYPE_YEAR +#define FIELD_TYPE_NEWDATE MYSQL_TYPE_NEWDATE +#define FIELD_TYPE_ENUM MYSQL_TYPE_ENUM +#define FIELD_TYPE_SET MYSQL_TYPE_SET +#define FIELD_TYPE_TINY_BLOB MYSQL_TYPE_TINY_BLOB +#define FIELD_TYPE_MEDIUM_BLOB MYSQL_TYPE_MEDIUM_BLOB +#define FIELD_TYPE_LONG_BLOB MYSQL_TYPE_LONG_BLOB +#define FIELD_TYPE_BLOB MYSQL_TYPE_BLOB +#define FIELD_TYPE_VAR_STRING MYSQL_TYPE_VAR_STRING +#define FIELD_TYPE_STRING MYSQL_TYPE_STRING +#define FIELD_TYPE_CHAR MYSQL_TYPE_TINY +#define FIELD_TYPE_INTERVAL MYSQL_TYPE_ENUM +#define FIELD_TYPE_GEOMETRY MYSQL_TYPE_GEOMETRY +#define FIELD_TYPE_BIT MYSQL_TYPE_BIT + + +/* Shutdown/kill enums and constants */ + +/* Bits for THD::killable. */ +#define MYSQL_SHUTDOWN_KILLABLE_CONNECT (unsigned char)(1 << 0) +#define MYSQL_SHUTDOWN_KILLABLE_TRANS (unsigned char)(1 << 1) +#define MYSQL_SHUTDOWN_KILLABLE_LOCK_TABLE (unsigned char)(1 << 2) +#define MYSQL_SHUTDOWN_KILLABLE_UPDATE (unsigned char)(1 << 3) + +enum mysql_enum_shutdown_level { + /* + We want levels to be in growing order of hardness (because we use number + comparisons). Note that DEFAULT does not respect the growing property, but + it's ok. + */ + SHUTDOWN_DEFAULT = 0, + /* wait for existing connections to finish */ + SHUTDOWN_WAIT_CONNECTIONS= MYSQL_SHUTDOWN_KILLABLE_CONNECT, + /* wait for existing trans to finish */ + SHUTDOWN_WAIT_TRANSACTIONS= MYSQL_SHUTDOWN_KILLABLE_TRANS, + /* wait for existing updates to finish (=> no partial MyISAM update) */ + SHUTDOWN_WAIT_UPDATES= MYSQL_SHUTDOWN_KILLABLE_UPDATE, + /* flush InnoDB buffers and other storage engines' buffers*/ + SHUTDOWN_WAIT_ALL_BUFFERS= (MYSQL_SHUTDOWN_KILLABLE_UPDATE << 1), + /* don't flush InnoDB buffers, flush other storage engines' buffers*/ + SHUTDOWN_WAIT_CRITICAL_BUFFERS= (MYSQL_SHUTDOWN_KILLABLE_UPDATE << 1) + 1, + /* Now the 2 levels of the KILL command */ +#if MYSQL_VERSION_ID >= 50000 + KILL_QUERY= 254, +#endif + KILL_CONNECTION= 255 +}; + + +enum enum_cursor_type +{ + CURSOR_TYPE_NO_CURSOR= 0, + CURSOR_TYPE_READ_ONLY= 1, + CURSOR_TYPE_FOR_UPDATE= 2, + CURSOR_TYPE_SCROLLABLE= 4 +}; + + +/* options for mysql_set_option */ +enum enum_mysql_set_option +{ + MYSQL_OPTION_MULTI_STATEMENTS_ON, + MYSQL_OPTION_MULTI_STATEMENTS_OFF +}; + +#define net_new_transaction(net) ((net)->pkt_nr=0) + +#ifdef __cplusplus +extern "C" { +#endif + +my_bool my_net_init(NET *net, Vio* vio); +void my_net_local_init(NET *net); +void net_end(NET *net); +void net_clear(NET *net); +my_bool net_realloc(NET *net, unsigned long length); +my_bool net_flush(NET *net); +my_bool my_net_write(NET *net,const char *packet,unsigned long len); +my_bool net_write_command(NET *net,unsigned char command, + const char *header, unsigned long head_len, + const char *packet, unsigned long len); +int net_real_write(NET *net,const char *packet,unsigned long len); +unsigned long my_net_read(NET *net); + +/* + The following function is not meant for normal usage + Currently it's used internally by manager.c +*/ +struct sockaddr; +int my_connect(my_socket s, const struct sockaddr *name, unsigned int namelen, + unsigned int timeout); + +struct rand_struct { + unsigned long seed1,seed2,max_value; + double max_value_dbl; +}; + +#ifdef __cplusplus +} +#endif + + /* The following is for user defined functions */ + +enum Item_result {STRING_RESULT=0, REAL_RESULT, INT_RESULT, ROW_RESULT, + DECIMAL_RESULT}; + +typedef struct st_udf_args +{ + unsigned int arg_count; /* Number of arguments */ + enum Item_result *arg_type; /* Pointer to item_results */ + char **args; /* Pointer to argument */ + unsigned long *lengths; /* Length of string arguments */ + char *maybe_null; /* Set to 1 for all maybe_null args */ + char **attributes; /* Pointer to attribute name */ + unsigned long *attribute_lengths; /* Length of attribute arguments */ +} UDF_ARGS; + + /* This holds information about the result */ + +typedef struct st_udf_init +{ + my_bool maybe_null; /* 1 if function can return NULL */ + unsigned int decimals; /* for real functions */ + unsigned long max_length; /* For string functions */ + char *ptr; /* free pointer for function data */ + my_bool const_item; /* 0 if result is independent of arguments */ +} UDF_INIT; + + /* Constants when using compression */ +#define NET_HEADER_SIZE 4 /* standard header size */ +#define COMP_HEADER_SIZE 3 /* compression header extra size */ + + /* Prototypes to password functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + These functions are used for authentication by client and server and + implemented in sql/password.c +*/ + +void randominit(struct rand_struct *, unsigned long seed1, + unsigned long seed2); +double my_rnd(struct rand_struct *); +void create_random_string(char *to, unsigned int length, struct rand_struct *rand_st); + +void hash_password(unsigned long *to, const char *password, unsigned int password_len); +void make_scrambled_password_323(char *to, const char *password); +void scramble_323(char *to, const char *message, const char *password); +my_bool check_scramble_323(const char *, const char *message, + unsigned long *salt); +void get_salt_from_password_323(unsigned long *res, const char *password); +void make_password_from_salt_323(char *to, const unsigned long *salt); + +void make_scrambled_password(char *to, const char *password); +void scramble(char *to, const char *message, const char *password); +my_bool check_scramble(const char *reply, const char *message, + const unsigned char *hash_stage2); +void get_salt_from_password(unsigned char *res, const char *password); +void make_password_from_salt(char *to, const unsigned char *hash_stage2); +char *octet2hex(char *to, const char *str, unsigned int len); + +/* end of password.c */ + +char *get_tty_password(char *opt_message); +const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); + +/* Some other useful functions */ + +my_bool my_init(void); +extern int modify_defaults_file(const char *file_location, const char *option, + const char *option_value, + const char *section_name, int remove_option); +int load_defaults(const char *conf_file, const char **groups, + int *argc, char ***argv); +my_bool my_thread_init(void); +void my_thread_end(void); + +#ifdef _global_h +ulong STDCALL net_field_length(uchar **packet); +my_ulonglong net_field_length_ll(uchar **packet); +char *net_store_length(char *pkg, ulonglong length); +#endif + +#ifdef __cplusplus +} +#endif + +#define NULL_LENGTH ((unsigned long) ~0) /* For net_store_length */ +#define MYSQL_STMT_HEADER 4 +#define MYSQL_LONG_DATA_HEADER 6 + +#endif diff --git a/Src/3rd_party/mysql/include/mysql_embed.h b/Src/3rd_party/mysql/include/mysql_embed.h new file mode 100644 index 0000000..311d95e --- /dev/null +++ b/Src/3rd_party/mysql/include/mysql_embed.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Defines that are unique to the embedded version of MySQL */ + +#ifdef EMBEDDED_LIBRARY + +/* Things we don't need in the embedded version of MySQL */ +/* TODO HF add #undef HAVE_VIO if we don't want client in embedded library */ + +#undef HAVE_PSTACK /* No stacktrace */ +#undef HAVE_DLOPEN /* No udf functions */ +#undef HAVE_OPENSSL +#undef HAVE_SMEM /* No shared memory */ +#undef HAVE_NDBCLUSTER_DB /* No NDB cluster */ + +#define DONT_USE_RAID + +#endif /* EMBEDDED_LIBRARY */ diff --git a/Src/3rd_party/mysql/include/mysql_time.h b/Src/3rd_party/mysql/include/mysql_time.h new file mode 100644 index 0000000..5f4fc12 --- /dev/null +++ b/Src/3rd_party/mysql/include/mysql_time.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2004 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _mysql_time_h_ +#define _mysql_time_h_ + +/* + Time declarations shared between the server and client API: + you should not add anything to this header unless it's used + (and hence should be visible) in mysql.h. + If you're looking for a place to add new time-related declaration, + it's most likely my_time.h. See also "C API Handling of Date + and Time Values" chapter in documentation. +*/ + +enum enum_mysql_timestamp_type +{ + MYSQL_TIMESTAMP_NONE= -2, MYSQL_TIMESTAMP_ERROR= -1, + MYSQL_TIMESTAMP_DATE= 0, MYSQL_TIMESTAMP_DATETIME= 1, MYSQL_TIMESTAMP_TIME= 2 +}; + + +/* + Structure which is used to represent datetime values inside MySQL. + + We assume that values in this structure are normalized, i.e. year <= 9999, + month <= 12, day <= 31, hour <= 23, hour <= 59, hour <= 59. Many functions + in server such as my_system_gmt_sec() or make_time() family of functions + rely on this (actually now usage of make_*() family relies on a bit weaker + restriction). Also functions that produce MYSQL_TIME as result ensure this. + There is one exception to this rule though if this structure holds time + value (time_type == MYSQL_TIMESTAMP_TIME) days and hour member can hold + bigger values. +*/ +typedef struct st_mysql_time +{ + unsigned int year, month, day, hour, minute, second; + unsigned long second_part; + my_bool neg; + enum enum_mysql_timestamp_type time_type; +} MYSQL_TIME; + +#endif /* _mysql_time_h_ */ diff --git a/Src/3rd_party/mysql/include/mysql_version.h b/Src/3rd_party/mysql/include/mysql_version.h new file mode 100644 index 0000000..c0a49de --- /dev/null +++ b/Src/3rd_party/mysql/include/mysql_version.h @@ -0,0 +1,29 @@ +/* Copyright Abandoned 1996, 1999, 2001 MySQL AB + This file is public domain and comes with NO WARRANTY of any kind */ + +/* Version numbers for protocol & mysqld */ + +#ifndef _mysql_version_h +#define _mysql_version_h +#ifdef _CUSTOMCONFIG_ +#include +#else +#define PROTOCOL_VERSION 10 +#define MYSQL_SERVER_VERSION "5.0.15" +#define MYSQL_BASE_VERSION "mysqld-5.0" +#define MYSQL_SERVER_SUFFIX_DEF "" +#define FRM_VER 6 +#define MYSQL_VERSION_ID 50015 +#define MYSQL_PORT 3306 +#define MYSQL_UNIX_ADDR "/tmp/mysql.sock" +#define MYSQL_CONFIG_NAME "my" +#define MYSQL_COMPILATION_COMMENT "Official MySQL binary" + +/* mysqld compile time options */ +#endif /* _CUSTOMCONFIG_ */ + +#ifndef LICENSE +#define LICENSE GPL +#endif /* LICENSE */ + +#endif /* _mysql_version_h */ diff --git a/Src/3rd_party/mysql/include/mysqld_ername.h b/Src/3rd_party/mysql/include/mysqld_ername.h new file mode 100644 index 0000000..9ebeea1 --- /dev/null +++ b/Src/3rd_party/mysql/include/mysqld_ername.h @@ -0,0 +1,456 @@ +/* Autogenerated file, please don't edit */ + +{ "ER_HASHCHK", 1000 }, +{ "ER_NISAMCHK", 1001 }, +{ "ER_NO", 1002 }, +{ "ER_YES", 1003 }, +{ "ER_CANT_CREATE_FILE", 1004 }, +{ "ER_CANT_CREATE_TABLE", 1005 }, +{ "ER_CANT_CREATE_DB", 1006 }, +{ "ER_DB_CREATE_EXISTS", 1007 }, +{ "ER_DB_DROP_EXISTS", 1008 }, +{ "ER_DB_DROP_DELETE", 1009 }, +{ "ER_DB_DROP_RMDIR", 1010 }, +{ "ER_CANT_DELETE_FILE", 1011 }, +{ "ER_CANT_FIND_SYSTEM_REC", 1012 }, +{ "ER_CANT_GET_STAT", 1013 }, +{ "ER_CANT_GET_WD", 1014 }, +{ "ER_CANT_LOCK", 1015 }, +{ "ER_CANT_OPEN_FILE", 1016 }, +{ "ER_FILE_NOT_FOUND", 1017 }, +{ "ER_CANT_READ_DIR", 1018 }, +{ "ER_CANT_SET_WD", 1019 }, +{ "ER_CHECKREAD", 1020 }, +{ "ER_DISK_FULL", 1021 }, +{ "ER_DUP_KEY", 1022 }, +{ "ER_ERROR_ON_CLOSE", 1023 }, +{ "ER_ERROR_ON_READ", 1024 }, +{ "ER_ERROR_ON_RENAME", 1025 }, +{ "ER_ERROR_ON_WRITE", 1026 }, +{ "ER_FILE_USED", 1027 }, +{ "ER_FILSORT_ABORT", 1028 }, +{ "ER_FORM_NOT_FOUND", 1029 }, +{ "ER_GET_ERRNO", 1030 }, +{ "ER_ILLEGAL_HA", 1031 }, +{ "ER_KEY_NOT_FOUND", 1032 }, +{ "ER_NOT_FORM_FILE", 1033 }, +{ "ER_NOT_KEYFILE", 1034 }, +{ "ER_OLD_KEYFILE", 1035 }, +{ "ER_OPEN_AS_READONLY", 1036 }, +{ "ER_OUTOFMEMORY", 1037 }, +{ "ER_OUT_OF_SORTMEMORY", 1038 }, +{ "ER_UNEXPECTED_EOF", 1039 }, +{ "ER_CON_COUNT_ERROR", 1040 }, +{ "ER_OUT_OF_RESOURCES", 1041 }, +{ "ER_BAD_HOST_ERROR", 1042 }, +{ "ER_HANDSHAKE_ERROR", 1043 }, +{ "ER_DBACCESS_DENIED_ERROR", 1044 }, +{ "ER_ACCESS_DENIED_ERROR", 1045 }, +{ "ER_NO_DB_ERROR", 1046 }, +{ "ER_UNKNOWN_COM_ERROR", 1047 }, +{ "ER_BAD_NULL_ERROR", 1048 }, +{ "ER_BAD_DB_ERROR", 1049 }, +{ "ER_TABLE_EXISTS_ERROR", 1050 }, +{ "ER_BAD_TABLE_ERROR", 1051 }, +{ "ER_NON_UNIQ_ERROR", 1052 }, +{ "ER_SERVER_SHUTDOWN", 1053 }, +{ "ER_BAD_FIELD_ERROR", 1054 }, +{ "ER_WRONG_FIELD_WITH_GROUP", 1055 }, +{ "ER_WRONG_GROUP_FIELD", 1056 }, +{ "ER_WRONG_SUM_SELECT", 1057 }, +{ "ER_WRONG_VALUE_COUNT", 1058 }, +{ "ER_TOO_LONG_IDENT", 1059 }, +{ "ER_DUP_FIELDNAME", 1060 }, +{ "ER_DUP_KEYNAME", 1061 }, +{ "ER_DUP_ENTRY", 1062 }, +{ "ER_WRONG_FIELD_SPEC", 1063 }, +{ "ER_PARSE_ERROR", 1064 }, +{ "ER_EMPTY_QUERY", 1065 }, +{ "ER_NONUNIQ_TABLE", 1066 }, +{ "ER_INVALID_DEFAULT", 1067 }, +{ "ER_MULTIPLE_PRI_KEY", 1068 }, +{ "ER_TOO_MANY_KEYS", 1069 }, +{ "ER_TOO_MANY_KEY_PARTS", 1070 }, +{ "ER_TOO_LONG_KEY", 1071 }, +{ "ER_KEY_COLUMN_DOES_NOT_EXITS", 1072 }, +{ "ER_BLOB_USED_AS_KEY", 1073 }, +{ "ER_TOO_BIG_FIELDLENGTH", 1074 }, +{ "ER_WRONG_AUTO_KEY", 1075 }, +{ "ER_READY", 1076 }, +{ "ER_NORMAL_SHUTDOWN", 1077 }, +{ "ER_GOT_SIGNAL", 1078 }, +{ "ER_SHUTDOWN_COMPLETE", 1079 }, +{ "ER_FORCING_CLOSE", 1080 }, +{ "ER_IPSOCK_ERROR", 1081 }, +{ "ER_NO_SUCH_INDEX", 1082 }, +{ "ER_WRONG_FIELD_TERMINATORS", 1083 }, +{ "ER_BLOBS_AND_NO_TERMINATED", 1084 }, +{ "ER_TEXTFILE_NOT_READABLE", 1085 }, +{ "ER_FILE_EXISTS_ERROR", 1086 }, +{ "ER_LOAD_INFO", 1087 }, +{ "ER_ALTER_INFO", 1088 }, +{ "ER_WRONG_SUB_KEY", 1089 }, +{ "ER_CANT_REMOVE_ALL_FIELDS", 1090 }, +{ "ER_CANT_DROP_FIELD_OR_KEY", 1091 }, +{ "ER_INSERT_INFO", 1092 }, +{ "ER_UPDATE_TABLE_USED", 1093 }, +{ "ER_NO_SUCH_THREAD", 1094 }, +{ "ER_KILL_DENIED_ERROR", 1095 }, +{ "ER_NO_TABLES_USED", 1096 }, +{ "ER_TOO_BIG_SET", 1097 }, +{ "ER_NO_UNIQUE_LOGFILE", 1098 }, +{ "ER_TABLE_NOT_LOCKED_FOR_WRITE", 1099 }, +{ "ER_TABLE_NOT_LOCKED", 1100 }, +{ "ER_BLOB_CANT_HAVE_DEFAULT", 1101 }, +{ "ER_WRONG_DB_NAME", 1102 }, +{ "ER_WRONG_TABLE_NAME", 1103 }, +{ "ER_TOO_BIG_SELECT", 1104 }, +{ "ER_UNKNOWN_ERROR", 1105 }, +{ "ER_UNKNOWN_PROCEDURE", 1106 }, +{ "ER_WRONG_PARAMCOUNT_TO_PROCEDURE", 1107 }, +{ "ER_WRONG_PARAMETERS_TO_PROCEDURE", 1108 }, +{ "ER_UNKNOWN_TABLE", 1109 }, +{ "ER_FIELD_SPECIFIED_TWICE", 1110 }, +{ "ER_INVALID_GROUP_FUNC_USE", 1111 }, +{ "ER_UNSUPPORTED_EXTENSION", 1112 }, +{ "ER_TABLE_MUST_HAVE_COLUMNS", 1113 }, +{ "ER_RECORD_FILE_FULL", 1114 }, +{ "ER_UNKNOWN_CHARACTER_SET", 1115 }, +{ "ER_TOO_MANY_TABLES", 1116 }, +{ "ER_TOO_MANY_FIELDS", 1117 }, +{ "ER_TOO_BIG_ROWSIZE", 1118 }, +{ "ER_STACK_OVERRUN", 1119 }, +{ "ER_WRONG_OUTER_JOIN", 1120 }, +{ "ER_NULL_COLUMN_IN_INDEX", 1121 }, +{ "ER_CANT_FIND_UDF", 1122 }, +{ "ER_CANT_INITIALIZE_UDF", 1123 }, +{ "ER_UDF_NO_PATHS", 1124 }, +{ "ER_UDF_EXISTS", 1125 }, +{ "ER_CANT_OPEN_LIBRARY", 1126 }, +{ "ER_CANT_FIND_DL_ENTRY", 1127 }, +{ "ER_FUNCTION_NOT_DEFINED", 1128 }, +{ "ER_HOST_IS_BLOCKED", 1129 }, +{ "ER_HOST_NOT_PRIVILEGED", 1130 }, +{ "ER_PASSWORD_ANONYMOUS_USER", 1131 }, +{ "ER_PASSWORD_NOT_ALLOWED", 1132 }, +{ "ER_PASSWORD_NO_MATCH", 1133 }, +{ "ER_UPDATE_INFO", 1134 }, +{ "ER_CANT_CREATE_THREAD", 1135 }, +{ "ER_WRONG_VALUE_COUNT_ON_ROW", 1136 }, +{ "ER_CANT_REOPEN_TABLE", 1137 }, +{ "ER_INVALID_USE_OF_NULL", 1138 }, +{ "ER_REGEXP_ERROR", 1139 }, +{ "ER_MIX_OF_GROUP_FUNC_AND_FIELDS", 1140 }, +{ "ER_NONEXISTING_GRANT", 1141 }, +{ "ER_TABLEACCESS_DENIED_ERROR", 1142 }, +{ "ER_COLUMNACCESS_DENIED_ERROR", 1143 }, +{ "ER_ILLEGAL_GRANT_FOR_TABLE", 1144 }, +{ "ER_GRANT_WRONG_HOST_OR_USER", 1145 }, +{ "ER_NO_SUCH_TABLE", 1146 }, +{ "ER_NONEXISTING_TABLE_GRANT", 1147 }, +{ "ER_NOT_ALLOWED_COMMAND", 1148 }, +{ "ER_SYNTAX_ERROR", 1149 }, +{ "ER_DELAYED_CANT_CHANGE_LOCK", 1150 }, +{ "ER_TOO_MANY_DELAYED_THREADS", 1151 }, +{ "ER_ABORTING_CONNECTION", 1152 }, +{ "ER_NET_PACKET_TOO_LARGE", 1153 }, +{ "ER_NET_READ_ERROR_FROM_PIPE", 1154 }, +{ "ER_NET_FCNTL_ERROR", 1155 }, +{ "ER_NET_PACKETS_OUT_OF_ORDER", 1156 }, +{ "ER_NET_UNCOMPRESS_ERROR", 1157 }, +{ "ER_NET_READ_ERROR", 1158 }, +{ "ER_NET_READ_INTERRUPTED", 1159 }, +{ "ER_NET_ERROR_ON_WRITE", 1160 }, +{ "ER_NET_WRITE_INTERRUPTED", 1161 }, +{ "ER_TOO_LONG_STRING", 1162 }, +{ "ER_TABLE_CANT_HANDLE_BLOB", 1163 }, +{ "ER_TABLE_CANT_HANDLE_AUTO_INCREMENT", 1164 }, +{ "ER_DELAYED_INSERT_TABLE_LOCKED", 1165 }, +{ "ER_WRONG_COLUMN_NAME", 1166 }, +{ "ER_WRONG_KEY_COLUMN", 1167 }, +{ "ER_WRONG_MRG_TABLE", 1168 }, +{ "ER_DUP_UNIQUE", 1169 }, +{ "ER_BLOB_KEY_WITHOUT_LENGTH", 1170 }, +{ "ER_PRIMARY_CANT_HAVE_NULL", 1171 }, +{ "ER_TOO_MANY_ROWS", 1172 }, +{ "ER_REQUIRES_PRIMARY_KEY", 1173 }, +{ "ER_NO_RAID_COMPILED", 1174 }, +{ "ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE", 1175 }, +{ "ER_KEY_DOES_NOT_EXITS", 1176 }, +{ "ER_CHECK_NO_SUCH_TABLE", 1177 }, +{ "ER_CHECK_NOT_IMPLEMENTED", 1178 }, +{ "ER_CANT_DO_THIS_DURING_AN_TRANSACTION", 1179 }, +{ "ER_ERROR_DURING_COMMIT", 1180 }, +{ "ER_ERROR_DURING_ROLLBACK", 1181 }, +{ "ER_ERROR_DURING_FLUSH_LOGS", 1182 }, +{ "ER_ERROR_DURING_CHECKPOINT", 1183 }, +{ "ER_NEW_ABORTING_CONNECTION", 1184 }, +{ "ER_DUMP_NOT_IMPLEMENTED", 1185 }, +{ "ER_FLUSH_MASTER_BINLOG_CLOSED", 1186 }, +{ "ER_INDEX_REBUILD", 1187 }, +{ "ER_MASTER", 1188 }, +{ "ER_MASTER_NET_READ", 1189 }, +{ "ER_MASTER_NET_WRITE", 1190 }, +{ "ER_FT_MATCHING_KEY_NOT_FOUND", 1191 }, +{ "ER_LOCK_OR_ACTIVE_TRANSACTION", 1192 }, +{ "ER_UNKNOWN_SYSTEM_VARIABLE", 1193 }, +{ "ER_CRASHED_ON_USAGE", 1194 }, +{ "ER_CRASHED_ON_REPAIR", 1195 }, +{ "ER_WARNING_NOT_COMPLETE_ROLLBACK", 1196 }, +{ "ER_TRANS_CACHE_FULL", 1197 }, +{ "ER_SLAVE_MUST_STOP", 1198 }, +{ "ER_SLAVE_NOT_RUNNING", 1199 }, +{ "ER_BAD_SLAVE", 1200 }, +{ "ER_MASTER_INFO", 1201 }, +{ "ER_SLAVE_THREAD", 1202 }, +{ "ER_TOO_MANY_USER_CONNECTIONS", 1203 }, +{ "ER_SET_CONSTANTS_ONLY", 1204 }, +{ "ER_LOCK_WAIT_TIMEOUT", 1205 }, +{ "ER_LOCK_TABLE_FULL", 1206 }, +{ "ER_READ_ONLY_TRANSACTION", 1207 }, +{ "ER_DROP_DB_WITH_READ_LOCK", 1208 }, +{ "ER_CREATE_DB_WITH_READ_LOCK", 1209 }, +{ "ER_WRONG_ARGUMENTS", 1210 }, +{ "ER_NO_PERMISSION_TO_CREATE_USER", 1211 }, +{ "ER_UNION_TABLES_IN_DIFFERENT_DIR", 1212 }, +{ "ER_LOCK_DEADLOCK", 1213 }, +{ "ER_TABLE_CANT_HANDLE_FT", 1214 }, +{ "ER_CANNOT_ADD_FOREIGN", 1215 }, +{ "ER_NO_REFERENCED_ROW", 1216 }, +{ "ER_ROW_IS_REFERENCED", 1217 }, +{ "ER_CONNECT_TO_MASTER", 1218 }, +{ "ER_QUERY_ON_MASTER", 1219 }, +{ "ER_ERROR_WHEN_EXECUTING_COMMAND", 1220 }, +{ "ER_WRONG_USAGE", 1221 }, +{ "ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT", 1222 }, +{ "ER_CANT_UPDATE_WITH_READLOCK", 1223 }, +{ "ER_MIXING_NOT_ALLOWED", 1224 }, +{ "ER_DUP_ARGUMENT", 1225 }, +{ "ER_USER_LIMIT_REACHED", 1226 }, +{ "ER_SPECIFIC_ACCESS_DENIED_ERROR", 1227 }, +{ "ER_LOCAL_VARIABLE", 1228 }, +{ "ER_GLOBAL_VARIABLE", 1229 }, +{ "ER_NO_DEFAULT", 1230 }, +{ "ER_WRONG_VALUE_FOR_VAR", 1231 }, +{ "ER_WRONG_TYPE_FOR_VAR", 1232 }, +{ "ER_VAR_CANT_BE_READ", 1233 }, +{ "ER_CANT_USE_OPTION_HERE", 1234 }, +{ "ER_NOT_SUPPORTED_YET", 1235 }, +{ "ER_MASTER_FATAL_ERROR_READING_BINLOG", 1236 }, +{ "ER_SLAVE_IGNORED_TABLE", 1237 }, +{ "ER_INCORRECT_GLOBAL_LOCAL_VAR", 1238 }, +{ "ER_WRONG_FK_DEF", 1239 }, +{ "ER_KEY_REF_DO_NOT_MATCH_TABLE_REF", 1240 }, +{ "ER_OPERAND_COLUMNS", 1241 }, +{ "ER_SUBQUERY_NO_1_ROW", 1242 }, +{ "ER_UNKNOWN_STMT_HANDLER", 1243 }, +{ "ER_CORRUPT_HELP_DB", 1244 }, +{ "ER_CYCLIC_REFERENCE", 1245 }, +{ "ER_AUTO_CONVERT", 1246 }, +{ "ER_ILLEGAL_REFERENCE", 1247 }, +{ "ER_DERIVED_MUST_HAVE_ALIAS", 1248 }, +{ "ER_SELECT_REDUCED", 1249 }, +{ "ER_TABLENAME_NOT_ALLOWED_HERE", 1250 }, +{ "ER_NOT_SUPPORTED_AUTH_MODE", 1251 }, +{ "ER_SPATIAL_CANT_HAVE_NULL", 1252 }, +{ "ER_COLLATION_CHARSET_MISMATCH", 1253 }, +{ "ER_SLAVE_WAS_RUNNING", 1254 }, +{ "ER_SLAVE_WAS_NOT_RUNNING", 1255 }, +{ "ER_TOO_BIG_FOR_UNCOMPRESS", 1256 }, +{ "ER_ZLIB_Z_MEM_ERROR", 1257 }, +{ "ER_ZLIB_Z_BUF_ERROR", 1258 }, +{ "ER_ZLIB_Z_DATA_ERROR", 1259 }, +{ "ER_CUT_VALUE_GROUP_CONCAT", 1260 }, +{ "ER_WARN_TOO_FEW_RECORDS", 1261 }, +{ "ER_WARN_TOO_MANY_RECORDS", 1262 }, +{ "ER_WARN_NULL_TO_NOTNULL", 1263 }, +{ "ER_WARN_DATA_OUT_OF_RANGE", 1264 }, +{ "WARN_DATA_TRUNCATED", 1265 }, +{ "ER_WARN_USING_OTHER_HANDLER", 1266 }, +{ "ER_CANT_AGGREGATE_2COLLATIONS", 1267 }, +{ "ER_DROP_USER", 1268 }, +{ "ER_REVOKE_GRANTS", 1269 }, +{ "ER_CANT_AGGREGATE_3COLLATIONS", 1270 }, +{ "ER_CANT_AGGREGATE_NCOLLATIONS", 1271 }, +{ "ER_VARIABLE_IS_NOT_STRUCT", 1272 }, +{ "ER_UNKNOWN_COLLATION", 1273 }, +{ "ER_SLAVE_IGNORED_SSL_PARAMS", 1274 }, +{ "ER_SERVER_IS_IN_SECURE_AUTH_MODE", 1275 }, +{ "ER_WARN_FIELD_RESOLVED", 1276 }, +{ "ER_BAD_SLAVE_UNTIL_COND", 1277 }, +{ "ER_MISSING_SKIP_SLAVE", 1278 }, +{ "ER_UNTIL_COND_IGNORED", 1279 }, +{ "ER_WRONG_NAME_FOR_INDEX", 1280 }, +{ "ER_WRONG_NAME_FOR_CATALOG", 1281 }, +{ "ER_WARN_QC_RESIZE", 1282 }, +{ "ER_BAD_FT_COLUMN", 1283 }, +{ "ER_UNKNOWN_KEY_CACHE", 1284 }, +{ "ER_WARN_HOSTNAME_WONT_WORK", 1285 }, +{ "ER_UNKNOWN_STORAGE_ENGINE", 1286 }, +{ "ER_WARN_DEPRECATED_SYNTAX", 1287 }, +{ "ER_NON_UPDATABLE_TABLE", 1288 }, +{ "ER_FEATURE_DISABLED", 1289 }, +{ "ER_OPTION_PREVENTS_STATEMENT", 1290 }, +{ "ER_DUPLICATED_VALUE_IN_TYPE", 1291 }, +{ "ER_TRUNCATED_WRONG_VALUE", 1292 }, +{ "ER_TOO_MUCH_AUTO_TIMESTAMP_COLS", 1293 }, +{ "ER_INVALID_ON_UPDATE", 1294 }, +{ "ER_UNSUPPORTED_PS", 1295 }, +{ "ER_GET_ERRMSG", 1296 }, +{ "ER_GET_TEMPORARY_ERRMSG", 1297 }, +{ "ER_UNKNOWN_TIME_ZONE", 1298 }, +{ "ER_WARN_INVALID_TIMESTAMP", 1299 }, +{ "ER_INVALID_CHARACTER_STRING", 1300 }, +{ "ER_WARN_ALLOWED_PACKET_OVERFLOWED", 1301 }, +{ "ER_CONFLICTING_DECLARATIONS", 1302 }, +{ "ER_SP_NO_RECURSIVE_CREATE", 1303 }, +{ "ER_SP_ALREADY_EXISTS", 1304 }, +{ "ER_SP_DOES_NOT_EXIST", 1305 }, +{ "ER_SP_DROP_FAILED", 1306 }, +{ "ER_SP_STORE_FAILED", 1307 }, +{ "ER_SP_LILABEL_MISMATCH", 1308 }, +{ "ER_SP_LABEL_REDEFINE", 1309 }, +{ "ER_SP_LABEL_MISMATCH", 1310 }, +{ "ER_SP_UNINIT_VAR", 1311 }, +{ "ER_SP_BADSELECT", 1312 }, +{ "ER_SP_BADRETURN", 1313 }, +{ "ER_SP_BADSTATEMENT", 1314 }, +{ "ER_UPDATE_LOG_DEPRECATED_IGNORED", 1315 }, +{ "ER_UPDATE_LOG_DEPRECATED_TRANSLATED", 1316 }, +{ "ER_QUERY_INTERRUPTED", 1317 }, +{ "ER_SP_WRONG_NO_OF_ARGS", 1318 }, +{ "ER_SP_COND_MISMATCH", 1319 }, +{ "ER_SP_NORETURN", 1320 }, +{ "ER_SP_NORETURNEND", 1321 }, +{ "ER_SP_BAD_CURSOR_QUERY", 1322 }, +{ "ER_SP_BAD_CURSOR_SELECT", 1323 }, +{ "ER_SP_CURSOR_MISMATCH", 1324 }, +{ "ER_SP_CURSOR_ALREADY_OPEN", 1325 }, +{ "ER_SP_CURSOR_NOT_OPEN", 1326 }, +{ "ER_SP_UNDECLARED_VAR", 1327 }, +{ "ER_SP_WRONG_NO_OF_FETCH_ARGS", 1328 }, +{ "ER_SP_FETCH_NO_DATA", 1329 }, +{ "ER_SP_DUP_PARAM", 1330 }, +{ "ER_SP_DUP_VAR", 1331 }, +{ "ER_SP_DUP_COND", 1332 }, +{ "ER_SP_DUP_CURS", 1333 }, +{ "ER_SP_CANT_ALTER", 1334 }, +{ "ER_SP_SUBSELECT_NYI", 1335 }, +{ "ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG", 1336 }, +{ "ER_SP_VARCOND_AFTER_CURSHNDLR", 1337 }, +{ "ER_SP_CURSOR_AFTER_HANDLER", 1338 }, +{ "ER_SP_CASE_NOT_FOUND", 1339 }, +{ "ER_FPARSER_TOO_BIG_FILE", 1340 }, +{ "ER_FPARSER_BAD_HEADER", 1341 }, +{ "ER_FPARSER_EOF_IN_COMMENT", 1342 }, +{ "ER_FPARSER_ERROR_IN_PARAMETER", 1343 }, +{ "ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER", 1344 }, +{ "ER_VIEW_NO_EXPLAIN", 1345 }, +{ "ER_FRM_UNKNOWN_TYPE", 1346 }, +{ "ER_WRONG_OBJECT", 1347 }, +{ "ER_NONUPDATEABLE_COLUMN", 1348 }, +{ "ER_VIEW_SELECT_DERIVED", 1349 }, +{ "ER_VIEW_SELECT_CLAUSE", 1350 }, +{ "ER_VIEW_SELECT_VARIABLE", 1351 }, +{ "ER_VIEW_SELECT_TMPTABLE", 1352 }, +{ "ER_VIEW_WRONG_LIST", 1353 }, +{ "ER_WARN_VIEW_MERGE", 1354 }, +{ "ER_WARN_VIEW_WITHOUT_KEY", 1355 }, +{ "ER_VIEW_INVALID", 1356 }, +{ "ER_SP_NO_DROP_SP", 1357 }, +{ "ER_SP_GOTO_IN_HNDLR", 1358 }, +{ "ER_TRG_ALREADY_EXISTS", 1359 }, +{ "ER_TRG_DOES_NOT_EXIST", 1360 }, +{ "ER_TRG_ON_VIEW_OR_TEMP_TABLE", 1361 }, +{ "ER_TRG_CANT_CHANGE_ROW", 1362 }, +{ "ER_TRG_NO_SUCH_ROW_IN_TRG", 1363 }, +{ "ER_NO_DEFAULT_FOR_FIELD", 1364 }, +{ "ER_DIVISION_BY_ZERO", 1365 }, +{ "ER_TRUNCATED_WRONG_VALUE_FOR_FIELD", 1366 }, +{ "ER_ILLEGAL_VALUE_FOR_TYPE", 1367 }, +{ "ER_VIEW_NONUPD_CHECK", 1368 }, +{ "ER_VIEW_CHECK_FAILED", 1369 }, +{ "ER_PROCACCESS_DENIED_ERROR", 1370 }, +{ "ER_RELAY_LOG_FAIL", 1371 }, +{ "ER_PASSWD_LENGTH", 1372 }, +{ "ER_UNKNOWN_TARGET_BINLOG", 1373 }, +{ "ER_IO_ERR_LOG_INDEX_READ", 1374 }, +{ "ER_BINLOG_PURGE_PROHIBITED", 1375 }, +{ "ER_FSEEK_FAIL", 1376 }, +{ "ER_BINLOG_PURGE_FATAL_ERR", 1377 }, +{ "ER_LOG_IN_USE", 1378 }, +{ "ER_LOG_PURGE_UNKNOWN_ERR", 1379 }, +{ "ER_RELAY_LOG_INIT", 1380 }, +{ "ER_NO_BINARY_LOGGING", 1381 }, +{ "ER_RESERVED_SYNTAX", 1382 }, +{ "ER_WSAS_FAILED", 1383 }, +{ "ER_DIFF_GROUPS_PROC", 1384 }, +{ "ER_NO_GROUP_FOR_PROC", 1385 }, +{ "ER_ORDER_WITH_PROC", 1386 }, +{ "ER_LOGGING_PROHIBIT_CHANGING_OF", 1387 }, +{ "ER_NO_FILE_MAPPING", 1388 }, +{ "ER_WRONG_MAGIC", 1389 }, +{ "ER_PS_MANY_PARAM", 1390 }, +{ "ER_KEY_PART_0", 1391 }, +{ "ER_VIEW_CHECKSUM", 1392 }, +{ "ER_VIEW_MULTIUPDATE", 1393 }, +{ "ER_VIEW_NO_INSERT_FIELD_LIST", 1394 }, +{ "ER_VIEW_DELETE_MERGE_VIEW", 1395 }, +{ "ER_CANNOT_USER", 1396 }, +{ "ER_XAER_NOTA", 1397 }, +{ "ER_XAER_INVAL", 1398 }, +{ "ER_XAER_RMFAIL", 1399 }, +{ "ER_XAER_OUTSIDE", 1400 }, +{ "ER_XAER_RMERR", 1401 }, +{ "ER_XA_RBROLLBACK", 1402 }, +{ "ER_NONEXISTING_PROC_GRANT", 1403 }, +{ "ER_PROC_AUTO_GRANT_FAIL", 1404 }, +{ "ER_PROC_AUTO_REVOKE_FAIL", 1405 }, +{ "ER_DATA_TOO_LONG", 1406 }, +{ "ER_SP_BAD_SQLSTATE", 1407 }, +{ "ER_STARTUP", 1408 }, +{ "ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR", 1409 }, +{ "ER_CANT_CREATE_USER_WITH_GRANT", 1410 }, +{ "ER_WRONG_VALUE_FOR_TYPE", 1411 }, +{ "ER_TABLE_DEF_CHANGED", 1412 }, +{ "ER_SP_DUP_HANDLER", 1413 }, +{ "ER_SP_NOT_VAR_ARG", 1414 }, +{ "ER_SP_NO_RETSET", 1415 }, +{ "ER_CANT_CREATE_GEOMETRY_OBJECT", 1416 }, +{ "ER_FAILED_ROUTINE_BREAK_BINLOG", 1417 }, +{ "ER_BINLOG_UNSAFE_ROUTINE", 1418 }, +{ "ER_BINLOG_CREATE_ROUTINE_NEED_SUPER", 1419 }, +{ "ER_EXEC_STMT_WITH_OPEN_CURSOR", 1420 }, +{ "ER_STMT_HAS_NO_OPEN_CURSOR", 1421 }, +{ "ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG", 1422 }, +{ "ER_NO_DEFAULT_FOR_VIEW_FIELD", 1423 }, +{ "ER_SP_NO_RECURSION", 1424 }, +{ "ER_TOO_BIG_SCALE", 1425 }, +{ "ER_TOO_BIG_PRECISION", 1426 }, +{ "ER_M_BIGGER_THAN_D", 1427 }, +{ "ER_WRONG_LOCK_OF_SYSTEM_TABLE", 1428 }, +{ "ER_CONNECT_TO_FOREIGN_DATA_SOURCE", 1429 }, +{ "ER_QUERY_ON_FOREIGN_DATA_SOURCE", 1430 }, +{ "ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST", 1431 }, +{ "ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE", 1432 }, +{ "ER_FOREIGN_DATA_STRING_INVALID", 1433 }, +{ "ER_CANT_CREATE_FEDERATED_TABLE", 1434 }, +{ "ER_TRG_IN_WRONG_SCHEMA", 1435 }, +{ "ER_STACK_OVERRUN_NEED_MORE", 1436 }, +{ "ER_TOO_LONG_BODY", 1437 }, +{ "ER_WARN_CANT_DROP_DEFAULT_KEYCACHE", 1438 }, +{ "ER_TOO_BIG_DISPLAYWIDTH", 1439 }, +{ "ER_XAER_DUPID", 1440 }, +{ "ER_DATETIME_FUNCTION_OVERFLOW", 1441 }, +{ "ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG", 1442 }, +{ "ER_VIEW_PREVENT_UPDATE", 1443 }, +{ "ER_PS_NO_RECURSION", 1444 }, +{ "ER_SP_CANT_SET_AUTOCOMMIT", 1445 }, +{ "ER_NO_VIEW_USER", 1446 }, +{ "ER_VIEW_FRM_NO_USER", 1447 }, +{ "ER_VIEW_OTHER_USER", 1448 }, +{ "ER_NO_SUCH_USER", 1449 }, +{ "ER_FORBID_SCHEMA_CHANGE", 1450 }, +{ "ER_ROW_IS_REFERENCED_2", 1451 }, +{ "ER_NO_REFERENCED_ROW_2", 1452 }, +{ "ER_SP_BAD_VAR_SHADOW", 1453 }, diff --git a/Src/3rd_party/mysql/include/mysqld_error.h b/Src/3rd_party/mysql/include/mysqld_error.h new file mode 100644 index 0000000..e8bc392 --- /dev/null +++ b/Src/3rd_party/mysql/include/mysqld_error.h @@ -0,0 +1,458 @@ +/* Autogenerated file, please don't edit */ + +#define ER_ERROR_FIRST 1000 +#define ER_HASHCHK 1000 +#define ER_NISAMCHK 1001 +#define ER_NO 1002 +#define ER_YES 1003 +#define ER_CANT_CREATE_FILE 1004 +#define ER_CANT_CREATE_TABLE 1005 +#define ER_CANT_CREATE_DB 1006 +#define ER_DB_CREATE_EXISTS 1007 +#define ER_DB_DROP_EXISTS 1008 +#define ER_DB_DROP_DELETE 1009 +#define ER_DB_DROP_RMDIR 1010 +#define ER_CANT_DELETE_FILE 1011 +#define ER_CANT_FIND_SYSTEM_REC 1012 +#define ER_CANT_GET_STAT 1013 +#define ER_CANT_GET_WD 1014 +#define ER_CANT_LOCK 1015 +#define ER_CANT_OPEN_FILE 1016 +#define ER_FILE_NOT_FOUND 1017 +#define ER_CANT_READ_DIR 1018 +#define ER_CANT_SET_WD 1019 +#define ER_CHECKREAD 1020 +#define ER_DISK_FULL 1021 +#define ER_DUP_KEY 1022 +#define ER_ERROR_ON_CLOSE 1023 +#define ER_ERROR_ON_READ 1024 +#define ER_ERROR_ON_RENAME 1025 +#define ER_ERROR_ON_WRITE 1026 +#define ER_FILE_USED 1027 +#define ER_FILSORT_ABORT 1028 +#define ER_FORM_NOT_FOUND 1029 +#define ER_GET_ERRNO 1030 +#define ER_ILLEGAL_HA 1031 +#define ER_KEY_NOT_FOUND 1032 +#define ER_NOT_FORM_FILE 1033 +#define ER_NOT_KEYFILE 1034 +#define ER_OLD_KEYFILE 1035 +#define ER_OPEN_AS_READONLY 1036 +#define ER_OUTOFMEMORY 1037 +#define ER_OUT_OF_SORTMEMORY 1038 +#define ER_UNEXPECTED_EOF 1039 +#define ER_CON_COUNT_ERROR 1040 +#define ER_OUT_OF_RESOURCES 1041 +#define ER_BAD_HOST_ERROR 1042 +#define ER_HANDSHAKE_ERROR 1043 +#define ER_DBACCESS_DENIED_ERROR 1044 +#define ER_ACCESS_DENIED_ERROR 1045 +#define ER_NO_DB_ERROR 1046 +#define ER_UNKNOWN_COM_ERROR 1047 +#define ER_BAD_NULL_ERROR 1048 +#define ER_BAD_DB_ERROR 1049 +#define ER_TABLE_EXISTS_ERROR 1050 +#define ER_BAD_TABLE_ERROR 1051 +#define ER_NON_UNIQ_ERROR 1052 +#define ER_SERVER_SHUTDOWN 1053 +#define ER_BAD_FIELD_ERROR 1054 +#define ER_WRONG_FIELD_WITH_GROUP 1055 +#define ER_WRONG_GROUP_FIELD 1056 +#define ER_WRONG_SUM_SELECT 1057 +#define ER_WRONG_VALUE_COUNT 1058 +#define ER_TOO_LONG_IDENT 1059 +#define ER_DUP_FIELDNAME 1060 +#define ER_DUP_KEYNAME 1061 +#define ER_DUP_ENTRY 1062 +#define ER_WRONG_FIELD_SPEC 1063 +#define ER_PARSE_ERROR 1064 +#define ER_EMPTY_QUERY 1065 +#define ER_NONUNIQ_TABLE 1066 +#define ER_INVALID_DEFAULT 1067 +#define ER_MULTIPLE_PRI_KEY 1068 +#define ER_TOO_MANY_KEYS 1069 +#define ER_TOO_MANY_KEY_PARTS 1070 +#define ER_TOO_LONG_KEY 1071 +#define ER_KEY_COLUMN_DOES_NOT_EXITS 1072 +#define ER_BLOB_USED_AS_KEY 1073 +#define ER_TOO_BIG_FIELDLENGTH 1074 +#define ER_WRONG_AUTO_KEY 1075 +#define ER_READY 1076 +#define ER_NORMAL_SHUTDOWN 1077 +#define ER_GOT_SIGNAL 1078 +#define ER_SHUTDOWN_COMPLETE 1079 +#define ER_FORCING_CLOSE 1080 +#define ER_IPSOCK_ERROR 1081 +#define ER_NO_SUCH_INDEX 1082 +#define ER_WRONG_FIELD_TERMINATORS 1083 +#define ER_BLOBS_AND_NO_TERMINATED 1084 +#define ER_TEXTFILE_NOT_READABLE 1085 +#define ER_FILE_EXISTS_ERROR 1086 +#define ER_LOAD_INFO 1087 +#define ER_ALTER_INFO 1088 +#define ER_WRONG_SUB_KEY 1089 +#define ER_CANT_REMOVE_ALL_FIELDS 1090 +#define ER_CANT_DROP_FIELD_OR_KEY 1091 +#define ER_INSERT_INFO 1092 +#define ER_UPDATE_TABLE_USED 1093 +#define ER_NO_SUCH_THREAD 1094 +#define ER_KILL_DENIED_ERROR 1095 +#define ER_NO_TABLES_USED 1096 +#define ER_TOO_BIG_SET 1097 +#define ER_NO_UNIQUE_LOGFILE 1098 +#define ER_TABLE_NOT_LOCKED_FOR_WRITE 1099 +#define ER_TABLE_NOT_LOCKED 1100 +#define ER_BLOB_CANT_HAVE_DEFAULT 1101 +#define ER_WRONG_DB_NAME 1102 +#define ER_WRONG_TABLE_NAME 1103 +#define ER_TOO_BIG_SELECT 1104 +#define ER_UNKNOWN_ERROR 1105 +#define ER_UNKNOWN_PROCEDURE 1106 +#define ER_WRONG_PARAMCOUNT_TO_PROCEDURE 1107 +#define ER_WRONG_PARAMETERS_TO_PROCEDURE 1108 +#define ER_UNKNOWN_TABLE 1109 +#define ER_FIELD_SPECIFIED_TWICE 1110 +#define ER_INVALID_GROUP_FUNC_USE 1111 +#define ER_UNSUPPORTED_EXTENSION 1112 +#define ER_TABLE_MUST_HAVE_COLUMNS 1113 +#define ER_RECORD_FILE_FULL 1114 +#define ER_UNKNOWN_CHARACTER_SET 1115 +#define ER_TOO_MANY_TABLES 1116 +#define ER_TOO_MANY_FIELDS 1117 +#define ER_TOO_BIG_ROWSIZE 1118 +#define ER_STACK_OVERRUN 1119 +#define ER_WRONG_OUTER_JOIN 1120 +#define ER_NULL_COLUMN_IN_INDEX 1121 +#define ER_CANT_FIND_UDF 1122 +#define ER_CANT_INITIALIZE_UDF 1123 +#define ER_UDF_NO_PATHS 1124 +#define ER_UDF_EXISTS 1125 +#define ER_CANT_OPEN_LIBRARY 1126 +#define ER_CANT_FIND_DL_ENTRY 1127 +#define ER_FUNCTION_NOT_DEFINED 1128 +#define ER_HOST_IS_BLOCKED 1129 +#define ER_HOST_NOT_PRIVILEGED 1130 +#define ER_PASSWORD_ANONYMOUS_USER 1131 +#define ER_PASSWORD_NOT_ALLOWED 1132 +#define ER_PASSWORD_NO_MATCH 1133 +#define ER_UPDATE_INFO 1134 +#define ER_CANT_CREATE_THREAD 1135 +#define ER_WRONG_VALUE_COUNT_ON_ROW 1136 +#define ER_CANT_REOPEN_TABLE 1137 +#define ER_INVALID_USE_OF_NULL 1138 +#define ER_REGEXP_ERROR 1139 +#define ER_MIX_OF_GROUP_FUNC_AND_FIELDS 1140 +#define ER_NONEXISTING_GRANT 1141 +#define ER_TABLEACCESS_DENIED_ERROR 1142 +#define ER_COLUMNACCESS_DENIED_ERROR 1143 +#define ER_ILLEGAL_GRANT_FOR_TABLE 1144 +#define ER_GRANT_WRONG_HOST_OR_USER 1145 +#define ER_NO_SUCH_TABLE 1146 +#define ER_NONEXISTING_TABLE_GRANT 1147 +#define ER_NOT_ALLOWED_COMMAND 1148 +#define ER_SYNTAX_ERROR 1149 +#define ER_DELAYED_CANT_CHANGE_LOCK 1150 +#define ER_TOO_MANY_DELAYED_THREADS 1151 +#define ER_ABORTING_CONNECTION 1152 +#define ER_NET_PACKET_TOO_LARGE 1153 +#define ER_NET_READ_ERROR_FROM_PIPE 1154 +#define ER_NET_FCNTL_ERROR 1155 +#define ER_NET_PACKETS_OUT_OF_ORDER 1156 +#define ER_NET_UNCOMPRESS_ERROR 1157 +#define ER_NET_READ_ERROR 1158 +#define ER_NET_READ_INTERRUPTED 1159 +#define ER_NET_ERROR_ON_WRITE 1160 +#define ER_NET_WRITE_INTERRUPTED 1161 +#define ER_TOO_LONG_STRING 1162 +#define ER_TABLE_CANT_HANDLE_BLOB 1163 +#define ER_TABLE_CANT_HANDLE_AUTO_INCREMENT 1164 +#define ER_DELAYED_INSERT_TABLE_LOCKED 1165 +#define ER_WRONG_COLUMN_NAME 1166 +#define ER_WRONG_KEY_COLUMN 1167 +#define ER_WRONG_MRG_TABLE 1168 +#define ER_DUP_UNIQUE 1169 +#define ER_BLOB_KEY_WITHOUT_LENGTH 1170 +#define ER_PRIMARY_CANT_HAVE_NULL 1171 +#define ER_TOO_MANY_ROWS 1172 +#define ER_REQUIRES_PRIMARY_KEY 1173 +#define ER_NO_RAID_COMPILED 1174 +#define ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE 1175 +#define ER_KEY_DOES_NOT_EXITS 1176 +#define ER_CHECK_NO_SUCH_TABLE 1177 +#define ER_CHECK_NOT_IMPLEMENTED 1178 +#define ER_CANT_DO_THIS_DURING_AN_TRANSACTION 1179 +#define ER_ERROR_DURING_COMMIT 1180 +#define ER_ERROR_DURING_ROLLBACK 1181 +#define ER_ERROR_DURING_FLUSH_LOGS 1182 +#define ER_ERROR_DURING_CHECKPOINT 1183 +#define ER_NEW_ABORTING_CONNECTION 1184 +#define ER_DUMP_NOT_IMPLEMENTED 1185 +#define ER_FLUSH_MASTER_BINLOG_CLOSED 1186 +#define ER_INDEX_REBUILD 1187 +#define ER_MASTER 1188 +#define ER_MASTER_NET_READ 1189 +#define ER_MASTER_NET_WRITE 1190 +#define ER_FT_MATCHING_KEY_NOT_FOUND 1191 +#define ER_LOCK_OR_ACTIVE_TRANSACTION 1192 +#define ER_UNKNOWN_SYSTEM_VARIABLE 1193 +#define ER_CRASHED_ON_USAGE 1194 +#define ER_CRASHED_ON_REPAIR 1195 +#define ER_WARNING_NOT_COMPLETE_ROLLBACK 1196 +#define ER_TRANS_CACHE_FULL 1197 +#define ER_SLAVE_MUST_STOP 1198 +#define ER_SLAVE_NOT_RUNNING 1199 +#define ER_BAD_SLAVE 1200 +#define ER_MASTER_INFO 1201 +#define ER_SLAVE_THREAD 1202 +#define ER_TOO_MANY_USER_CONNECTIONS 1203 +#define ER_SET_CONSTANTS_ONLY 1204 +#define ER_LOCK_WAIT_TIMEOUT 1205 +#define ER_LOCK_TABLE_FULL 1206 +#define ER_READ_ONLY_TRANSACTION 1207 +#define ER_DROP_DB_WITH_READ_LOCK 1208 +#define ER_CREATE_DB_WITH_READ_LOCK 1209 +#define ER_WRONG_ARGUMENTS 1210 +#define ER_NO_PERMISSION_TO_CREATE_USER 1211 +#define ER_UNION_TABLES_IN_DIFFERENT_DIR 1212 +#define ER_LOCK_DEADLOCK 1213 +#define ER_TABLE_CANT_HANDLE_FT 1214 +#define ER_CANNOT_ADD_FOREIGN 1215 +#define ER_NO_REFERENCED_ROW 1216 +#define ER_ROW_IS_REFERENCED 1217 +#define ER_CONNECT_TO_MASTER 1218 +#define ER_QUERY_ON_MASTER 1219 +#define ER_ERROR_WHEN_EXECUTING_COMMAND 1220 +#define ER_WRONG_USAGE 1221 +#define ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT 1222 +#define ER_CANT_UPDATE_WITH_READLOCK 1223 +#define ER_MIXING_NOT_ALLOWED 1224 +#define ER_DUP_ARGUMENT 1225 +#define ER_USER_LIMIT_REACHED 1226 +#define ER_SPECIFIC_ACCESS_DENIED_ERROR 1227 +#define ER_LOCAL_VARIABLE 1228 +#define ER_GLOBAL_VARIABLE 1229 +#define ER_NO_DEFAULT 1230 +#define ER_WRONG_VALUE_FOR_VAR 1231 +#define ER_WRONG_TYPE_FOR_VAR 1232 +#define ER_VAR_CANT_BE_READ 1233 +#define ER_CANT_USE_OPTION_HERE 1234 +#define ER_NOT_SUPPORTED_YET 1235 +#define ER_MASTER_FATAL_ERROR_READING_BINLOG 1236 +#define ER_SLAVE_IGNORED_TABLE 1237 +#define ER_INCORRECT_GLOBAL_LOCAL_VAR 1238 +#define ER_WRONG_FK_DEF 1239 +#define ER_KEY_REF_DO_NOT_MATCH_TABLE_REF 1240 +#define ER_OPERAND_COLUMNS 1241 +#define ER_SUBQUERY_NO_1_ROW 1242 +#define ER_UNKNOWN_STMT_HANDLER 1243 +#define ER_CORRUPT_HELP_DB 1244 +#define ER_CYCLIC_REFERENCE 1245 +#define ER_AUTO_CONVERT 1246 +#define ER_ILLEGAL_REFERENCE 1247 +#define ER_DERIVED_MUST_HAVE_ALIAS 1248 +#define ER_SELECT_REDUCED 1249 +#define ER_TABLENAME_NOT_ALLOWED_HERE 1250 +#define ER_NOT_SUPPORTED_AUTH_MODE 1251 +#define ER_SPATIAL_CANT_HAVE_NULL 1252 +#define ER_COLLATION_CHARSET_MISMATCH 1253 +#define ER_SLAVE_WAS_RUNNING 1254 +#define ER_SLAVE_WAS_NOT_RUNNING 1255 +#define ER_TOO_BIG_FOR_UNCOMPRESS 1256 +#define ER_ZLIB_Z_MEM_ERROR 1257 +#define ER_ZLIB_Z_BUF_ERROR 1258 +#define ER_ZLIB_Z_DATA_ERROR 1259 +#define ER_CUT_VALUE_GROUP_CONCAT 1260 +#define ER_WARN_TOO_FEW_RECORDS 1261 +#define ER_WARN_TOO_MANY_RECORDS 1262 +#define ER_WARN_NULL_TO_NOTNULL 1263 +#define ER_WARN_DATA_OUT_OF_RANGE 1264 +#define WARN_DATA_TRUNCATED 1265 +#define ER_WARN_USING_OTHER_HANDLER 1266 +#define ER_CANT_AGGREGATE_2COLLATIONS 1267 +#define ER_DROP_USER 1268 +#define ER_REVOKE_GRANTS 1269 +#define ER_CANT_AGGREGATE_3COLLATIONS 1270 +#define ER_CANT_AGGREGATE_NCOLLATIONS 1271 +#define ER_VARIABLE_IS_NOT_STRUCT 1272 +#define ER_UNKNOWN_COLLATION 1273 +#define ER_SLAVE_IGNORED_SSL_PARAMS 1274 +#define ER_SERVER_IS_IN_SECURE_AUTH_MODE 1275 +#define ER_WARN_FIELD_RESOLVED 1276 +#define ER_BAD_SLAVE_UNTIL_COND 1277 +#define ER_MISSING_SKIP_SLAVE 1278 +#define ER_UNTIL_COND_IGNORED 1279 +#define ER_WRONG_NAME_FOR_INDEX 1280 +#define ER_WRONG_NAME_FOR_CATALOG 1281 +#define ER_WARN_QC_RESIZE 1282 +#define ER_BAD_FT_COLUMN 1283 +#define ER_UNKNOWN_KEY_CACHE 1284 +#define ER_WARN_HOSTNAME_WONT_WORK 1285 +#define ER_UNKNOWN_STORAGE_ENGINE 1286 +#define ER_WARN_DEPRECATED_SYNTAX 1287 +#define ER_NON_UPDATABLE_TABLE 1288 +#define ER_FEATURE_DISABLED 1289 +#define ER_OPTION_PREVENTS_STATEMENT 1290 +#define ER_DUPLICATED_VALUE_IN_TYPE 1291 +#define ER_TRUNCATED_WRONG_VALUE 1292 +#define ER_TOO_MUCH_AUTO_TIMESTAMP_COLS 1293 +#define ER_INVALID_ON_UPDATE 1294 +#define ER_UNSUPPORTED_PS 1295 +#define ER_GET_ERRMSG 1296 +#define ER_GET_TEMPORARY_ERRMSG 1297 +#define ER_UNKNOWN_TIME_ZONE 1298 +#define ER_WARN_INVALID_TIMESTAMP 1299 +#define ER_INVALID_CHARACTER_STRING 1300 +#define ER_WARN_ALLOWED_PACKET_OVERFLOWED 1301 +#define ER_CONFLICTING_DECLARATIONS 1302 +#define ER_SP_NO_RECURSIVE_CREATE 1303 +#define ER_SP_ALREADY_EXISTS 1304 +#define ER_SP_DOES_NOT_EXIST 1305 +#define ER_SP_DROP_FAILED 1306 +#define ER_SP_STORE_FAILED 1307 +#define ER_SP_LILABEL_MISMATCH 1308 +#define ER_SP_LABEL_REDEFINE 1309 +#define ER_SP_LABEL_MISMATCH 1310 +#define ER_SP_UNINIT_VAR 1311 +#define ER_SP_BADSELECT 1312 +#define ER_SP_BADRETURN 1313 +#define ER_SP_BADSTATEMENT 1314 +#define ER_UPDATE_LOG_DEPRECATED_IGNORED 1315 +#define ER_UPDATE_LOG_DEPRECATED_TRANSLATED 1316 +#define ER_QUERY_INTERRUPTED 1317 +#define ER_SP_WRONG_NO_OF_ARGS 1318 +#define ER_SP_COND_MISMATCH 1319 +#define ER_SP_NORETURN 1320 +#define ER_SP_NORETURNEND 1321 +#define ER_SP_BAD_CURSOR_QUERY 1322 +#define ER_SP_BAD_CURSOR_SELECT 1323 +#define ER_SP_CURSOR_MISMATCH 1324 +#define ER_SP_CURSOR_ALREADY_OPEN 1325 +#define ER_SP_CURSOR_NOT_OPEN 1326 +#define ER_SP_UNDECLARED_VAR 1327 +#define ER_SP_WRONG_NO_OF_FETCH_ARGS 1328 +#define ER_SP_FETCH_NO_DATA 1329 +#define ER_SP_DUP_PARAM 1330 +#define ER_SP_DUP_VAR 1331 +#define ER_SP_DUP_COND 1332 +#define ER_SP_DUP_CURS 1333 +#define ER_SP_CANT_ALTER 1334 +#define ER_SP_SUBSELECT_NYI 1335 +#define ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG 1336 +#define ER_SP_VARCOND_AFTER_CURSHNDLR 1337 +#define ER_SP_CURSOR_AFTER_HANDLER 1338 +#define ER_SP_CASE_NOT_FOUND 1339 +#define ER_FPARSER_TOO_BIG_FILE 1340 +#define ER_FPARSER_BAD_HEADER 1341 +#define ER_FPARSER_EOF_IN_COMMENT 1342 +#define ER_FPARSER_ERROR_IN_PARAMETER 1343 +#define ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER 1344 +#define ER_VIEW_NO_EXPLAIN 1345 +#define ER_FRM_UNKNOWN_TYPE 1346 +#define ER_WRONG_OBJECT 1347 +#define ER_NONUPDATEABLE_COLUMN 1348 +#define ER_VIEW_SELECT_DERIVED 1349 +#define ER_VIEW_SELECT_CLAUSE 1350 +#define ER_VIEW_SELECT_VARIABLE 1351 +#define ER_VIEW_SELECT_TMPTABLE 1352 +#define ER_VIEW_WRONG_LIST 1353 +#define ER_WARN_VIEW_MERGE 1354 +#define ER_WARN_VIEW_WITHOUT_KEY 1355 +#define ER_VIEW_INVALID 1356 +#define ER_SP_NO_DROP_SP 1357 +#define ER_SP_GOTO_IN_HNDLR 1358 +#define ER_TRG_ALREADY_EXISTS 1359 +#define ER_TRG_DOES_NOT_EXIST 1360 +#define ER_TRG_ON_VIEW_OR_TEMP_TABLE 1361 +#define ER_TRG_CANT_CHANGE_ROW 1362 +#define ER_TRG_NO_SUCH_ROW_IN_TRG 1363 +#define ER_NO_DEFAULT_FOR_FIELD 1364 +#define ER_DIVISION_BY_ZERO 1365 +#define ER_TRUNCATED_WRONG_VALUE_FOR_FIELD 1366 +#define ER_ILLEGAL_VALUE_FOR_TYPE 1367 +#define ER_VIEW_NONUPD_CHECK 1368 +#define ER_VIEW_CHECK_FAILED 1369 +#define ER_PROCACCESS_DENIED_ERROR 1370 +#define ER_RELAY_LOG_FAIL 1371 +#define ER_PASSWD_LENGTH 1372 +#define ER_UNKNOWN_TARGET_BINLOG 1373 +#define ER_IO_ERR_LOG_INDEX_READ 1374 +#define ER_BINLOG_PURGE_PROHIBITED 1375 +#define ER_FSEEK_FAIL 1376 +#define ER_BINLOG_PURGE_FATAL_ERR 1377 +#define ER_LOG_IN_USE 1378 +#define ER_LOG_PURGE_UNKNOWN_ERR 1379 +#define ER_RELAY_LOG_INIT 1380 +#define ER_NO_BINARY_LOGGING 1381 +#define ER_RESERVED_SYNTAX 1382 +#define ER_WSAS_FAILED 1383 +#define ER_DIFF_GROUPS_PROC 1384 +#define ER_NO_GROUP_FOR_PROC 1385 +#define ER_ORDER_WITH_PROC 1386 +#define ER_LOGGING_PROHIBIT_CHANGING_OF 1387 +#define ER_NO_FILE_MAPPING 1388 +#define ER_WRONG_MAGIC 1389 +#define ER_PS_MANY_PARAM 1390 +#define ER_KEY_PART_0 1391 +#define ER_VIEW_CHECKSUM 1392 +#define ER_VIEW_MULTIUPDATE 1393 +#define ER_VIEW_NO_INSERT_FIELD_LIST 1394 +#define ER_VIEW_DELETE_MERGE_VIEW 1395 +#define ER_CANNOT_USER 1396 +#define ER_XAER_NOTA 1397 +#define ER_XAER_INVAL 1398 +#define ER_XAER_RMFAIL 1399 +#define ER_XAER_OUTSIDE 1400 +#define ER_XAER_RMERR 1401 +#define ER_XA_RBROLLBACK 1402 +#define ER_NONEXISTING_PROC_GRANT 1403 +#define ER_PROC_AUTO_GRANT_FAIL 1404 +#define ER_PROC_AUTO_REVOKE_FAIL 1405 +#define ER_DATA_TOO_LONG 1406 +#define ER_SP_BAD_SQLSTATE 1407 +#define ER_STARTUP 1408 +#define ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR 1409 +#define ER_CANT_CREATE_USER_WITH_GRANT 1410 +#define ER_WRONG_VALUE_FOR_TYPE 1411 +#define ER_TABLE_DEF_CHANGED 1412 +#define ER_SP_DUP_HANDLER 1413 +#define ER_SP_NOT_VAR_ARG 1414 +#define ER_SP_NO_RETSET 1415 +#define ER_CANT_CREATE_GEOMETRY_OBJECT 1416 +#define ER_FAILED_ROUTINE_BREAK_BINLOG 1417 +#define ER_BINLOG_UNSAFE_ROUTINE 1418 +#define ER_BINLOG_CREATE_ROUTINE_NEED_SUPER 1419 +#define ER_EXEC_STMT_WITH_OPEN_CURSOR 1420 +#define ER_STMT_HAS_NO_OPEN_CURSOR 1421 +#define ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG 1422 +#define ER_NO_DEFAULT_FOR_VIEW_FIELD 1423 +#define ER_SP_NO_RECURSION 1424 +#define ER_TOO_BIG_SCALE 1425 +#define ER_TOO_BIG_PRECISION 1426 +#define ER_M_BIGGER_THAN_D 1427 +#define ER_WRONG_LOCK_OF_SYSTEM_TABLE 1428 +#define ER_CONNECT_TO_FOREIGN_DATA_SOURCE 1429 +#define ER_QUERY_ON_FOREIGN_DATA_SOURCE 1430 +#define ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST 1431 +#define ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE 1432 +#define ER_FOREIGN_DATA_STRING_INVALID 1433 +#define ER_CANT_CREATE_FEDERATED_TABLE 1434 +#define ER_TRG_IN_WRONG_SCHEMA 1435 +#define ER_STACK_OVERRUN_NEED_MORE 1436 +#define ER_TOO_LONG_BODY 1437 +#define ER_WARN_CANT_DROP_DEFAULT_KEYCACHE 1438 +#define ER_TOO_BIG_DISPLAYWIDTH 1439 +#define ER_XAER_DUPID 1440 +#define ER_DATETIME_FUNCTION_OVERFLOW 1441 +#define ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG 1442 +#define ER_VIEW_PREVENT_UPDATE 1443 +#define ER_PS_NO_RECURSION 1444 +#define ER_SP_CANT_SET_AUTOCOMMIT 1445 +#define ER_NO_VIEW_USER 1446 +#define ER_VIEW_FRM_NO_USER 1447 +#define ER_VIEW_OTHER_USER 1448 +#define ER_NO_SUCH_USER 1449 +#define ER_FORBID_SCHEMA_CHANGE 1450 +#define ER_ROW_IS_REFERENCED_2 1451 +#define ER_NO_REFERENCED_ROW_2 1452 +#define ER_SP_BAD_VAR_SHADOW 1453 +#define ER_ERROR_LAST 1453 diff --git a/Src/3rd_party/mysql/include/sched.h b/Src/3rd_party/mysql/include/sched.h new file mode 100644 index 0000000..f36a97a --- /dev/null +++ b/Src/3rd_party/mysql/include/sched.h @@ -0,0 +1,183 @@ +/* + * Module: sched.h + * + * Purpose: + * Provides an implementation of POSIX realtime extensions + * as defined in + * + * POSIX 1003.1b-1993 (POSIX.1b) + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#if !defined(_SCHED_H) +#define _SCHED_H + +#undef PTW32_SCHED_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_SCHED_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_SCHED_LEVEL +#define PTW32_SCHED_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_SCHED_LEVEL +#define PTW32_SCHED_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_SCHED_LEVEL_MAX 3 + +#if ( defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112 ) || !defined(PTW32_SCHED_LEVEL) +#define PTW32_SCHED_LEVEL PTW32_SCHED_LEVEL_MAX +/* Include everything */ +#endif + + +#if defined(__GNUC__) && !defined(__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the library, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the library, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#if !defined(PTW32_STATIC_LIB) +# if defined(PTW32_BUILD) +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#if !defined(PTW32_CONFIG_H) +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(__MINGW64__) +# define HAVE_STRUCT_TIMESPEC +# define HAVE_MODE_T +# elif defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX +#if defined(NEED_ERRNO) +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX */ + +#if (defined(__MINGW64__) || defined(__MINGW32__)) || defined(_UWIN) +# if PTW32_SCHED_LEVEL >= PTW32_SCHED_LEVEL_MAX +/* For pid_t */ +# include +/* Required by Unix 98 */ +# include +# else + typedef int pid_t; +# endif +#else + typedef int pid_t; +#endif + +/* Thread scheduling policies */ + +enum { + SCHED_OTHER = 0, + SCHED_FIFO, + SCHED_RR, + SCHED_MIN = SCHED_OTHER, + SCHED_MAX = SCHED_RR +}; + +struct sched_param { + int sched_priority; +}; + +#if defined(__cplusplus) +extern "C" +{ +#endif /* __cplusplus */ + +PTW32_DLLPORT int __cdecl sched_yield (void); + +PTW32_DLLPORT int __cdecl sched_get_priority_min (int policy); + +PTW32_DLLPORT int __cdecl sched_get_priority_max (int policy); + +PTW32_DLLPORT int __cdecl sched_setscheduler (pid_t pid, int policy); + +PTW32_DLLPORT int __cdecl sched_getscheduler (pid_t pid); + +/* + * Note that this macro returns ENOTSUP rather than + * ENOSYS as might be expected. However, returning ENOSYS + * should mean that sched_get_priority_{min,max} are + * not implemented as well as sched_rr_get_interval. + * This is not the case, since we just don't support + * round-robin scheduling. Therefore I have chosen to + * return the same value as sched_setscheduler when + * SCHED_RR is passed to it. + */ +#define sched_rr_get_interval(_pid, _interval) \ + ( errno = ENOTSUP, (int) -1 ) + + +#if defined(__cplusplus) +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_SCHED_LEVEL +#undef PTW32_SCHED_LEVEL_MAX + +#endif /* !_SCHED_H */ + diff --git a/Src/3rd_party/mysql/include/typelib.h b/Src/3rd_party/mysql/include/typelib.h new file mode 100644 index 0000000..4d6a90a --- /dev/null +++ b/Src/3rd_party/mysql/include/typelib.h @@ -0,0 +1,34 @@ +/* Copyright (C) 2000 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifndef _typelib_h +#define _typelib_h + +typedef struct st_typelib { /* Different types saved here */ + unsigned int count; /* How many types */ + const char *name; /* Name of typelib */ + const char **type_names; + unsigned int *type_lengths; +} TYPELIB; + +extern int find_type(char *x,TYPELIB *typelib,unsigned int full_name); +extern void make_type(char *to,unsigned int nr,TYPELIB *typelib); +extern const char *get_type(TYPELIB *typelib,unsigned int nr); + +extern TYPELIB sql_protocol_typelib; + +#endif /* _typelib_h */ diff --git a/Src/3rd_party/mysql/lib/libmysql.dll b/Src/3rd_party/mysql/lib/libmysql.dll new file mode 100644 index 0000000000000000000000000000000000000000..d18a3fc49934db06e39629fbc274c9051f513850 GIT binary patch literal 1155072 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⩽AeRU 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�%mNFQfrX=WbCGVtki9uV9wnv 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

Gg{k~7QUI&*mP3eo4E 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

    c3tMEg-a35)kP|Eimx7=g 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¥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

    PI35m#66 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 literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x86/pthreadGCE2.dll b/Src/3rd_party/pthread/bin/x86/pthreadGCE2.dll new file mode 100644 index 0000000000000000000000000000000000000000..9e18ea24b8b99b7de0998b4f2f2cebce93583edb GIT binary patch literal 121953 zcmeFa3wTu3)i*v#28bkPf{qw9>ZoH45;4(OlNvRUfKj6kmjFssP(V-wgc(5vgw8}f zJ>>1wVymqe@Jd^(XsN|}BuJ#yiqt9&biMEDNi%283i_^^;+qqi>6<>&*Ko;b-}SR5PZ@IX!TtRj zbYQs4)s%CX>!-hY=@Qq(9M|uG$aYuX;|g+e2fEgyP;rjSmFseiFXlV`-;V!XD`v_& z(up@p$dfs)IwbaWxxU?zEB$i0e5xKRus;6eQIX>s>%ot@9M{%-JZI;)`~;Tyw>ZZY zA%VL4o0#J&$&z>bP{1Q%-TVy+PMIG>UTr)6Awujc@J{`k=&G1FWb&loBqUZaV>|vw z_gt^w|CGNvS4I1fIjW$W`MdBx`|{hALU={Fx>KuE+S+6J1cE+1e$V&hAIlB7YVKU7 zwDTWd_Kqxe)wK_qqS_T&f~L}!PgyDXC-A1Lazg7mlKmmN14Au!z;;BuIdaa*Fhdzqsx_MDfjJ4DL0(s){cB1 zp1v&Q&OBYpZ6PmhM?QJGE=#%UGo@UgK6vaxKFc>|DOaQV5fB+bXGoWeGs{(gE|sSd zU_1XQMmhF-RAxEQrSddE@Ylr4heG6oG1R{Xn7ihkd54*Kt?j|T$2Y5NNxU&;c>UI4&$(QkeOQNWce?$2 zSpAS1-r$|EsUr%w>79kG+d@Y; z&3hsdz%hpJuqMajadY^NwkdliTyf>5^@Xms8`;caHnZwkm+L{nw>@}uCsOc}NUvPC zYsE|;>}4yR4SvyuXequK%~-piLe(>#26&xIW+EPEXgu13wR4C(>WZ1MN24Md z=-AOqcJ6@4n)mqk;DNf{99NF3E$H_eL5zK=-)dGcIwSs9LDl`M&`X_DuW~Bemuw)6 z30HJPO*9T9yrv8P4qQ_GX}o=d=gxM|Q|%qmbsWWxsflMZxNgbLdyt6M*Wl5*%@a8a zh1weYzNa~>7@fv@e}5P)FUF555;EdnhFAXK^>+2fSMnnN+Sd9P<6-Px$Kp$iDJI4o zcX)l_i{OR0@yagG6W;-92XUlKa9E&iwZN&2@AAB~(A66EL>>YFb9AA3UGdWs@mkq# zy#M!~8>0({8`l+g)|d1{T4z1T1ai7CG~gajWIuixhvlATTv*>_XOnnvKB-C`Zx)*MT6~ znU(E$%d;;*DZ(YXsB4Vzms&FfZoY~4*yG?O<7hWJZNnyw_lA?yFV%Q;5`wWA2%H1d z4mN82)j)kWZj%pK$U;ajy-2o$16dB!lWF`}@uu2_;sR~UhOm0WDpS5&rB8D7z`jucg|4lhqO8;ebp z-2@OLRh0{h&HS376~R+#hE@eD%to(?CL~|Abb;4wbk__m43-)|(f%RS8R;~#0_8)S zjgeKc-^uSn$qpi$GD&#|@0?*Oc1KouYr7{h43cH6he*^Rr3_t`X}0aMQpjjSTa&-h zX!84z-GuDYXdL-a+I1+`#D3!0sGkjdwvF-EEj^S{fvR=J1(b{|+;{^}8dVtw`}x2I z)s%`K!TK=p8n{60LRyU4Z!I* zeh+O3jyF{iW!lQ-EO+?jd}D^6IJ6K4Jjd&&n6H&vjLoRz2OnPKGF~Rl-(p4dkiww) z7sM!J@81ZbAqpOI)}{Pm9}7vqc%q>m<*@0{@n_3&yLg+sM~BV@N?u^t*_$o@P-+pG|clTx8=sLDs!CMyuc0S zsWo$<3?Z2%)&r8xB-$xdvB;Zp8PVX09;ID8m3Q=bN_$BblV29U0 z!0ebEF1;G#540`tkE#3wojKL8T0ikb=m2F_@kHJP?!eC4{wor8BYsVdY9b6#^}fRi zBdbbXjXL9inQDq!_;59tZ)a&r%?LN4$|!Js721rC@z+9}s_g(;MOv9?N<2N0S`I~% zzZ4(`P_2IqfTA$_+I`j7qdXbvHk7Fw!)5l{5%b)eyhgGd`6KOx z04VcYkGf$$>>=ugPQy|ILaW6SIYs04-^gyZCmj%O)@ltaL(hDlu)uD2B0FFo_qoGA zDSSR0iK+bNK^4t!p0x7cQT+40FB1Io9hozdaxn&3ihmOGI&D7kc?_&eg#%`dV=$qT zO00a9hsI0nqtMMnKA30cmsyMK>=; zhe)$Y$Og@TFc9krkO;(J4Xg{Z1;0gM@CvYpQn5u7eWg;qeQ9bsN`LxG={+lM!)w64 zF0}wEzNQP{^%*H}GYUYTS+gI-3^f=3U>qu8kJH-lTQ8stbW=`lrGup*6+QQ=@32 zgyvf7wqO$`$x_s`mOmzDT4QIOGF$7E)mo?MsO&RXZ{RgoXPv}hI$iW5j6A|EnIQhl zK;+5~_!F$C-&5Kv5|21v`5?jm>k>7T8_GU@z8XzPv_wM|n-2m<@2yCD7^i+;sow$1 zDc`IsG;%>em4f*QyxUWq@aH)<_EUwUutiZ*)k(B*q{dOVVFug)nQs(lw9##O;A;fj z0yh<>Zp#Pq{{{GPT7Y*deA8tHN)nKyPoP^Ue_aK}17#b72dUy}UUHkxZ~{_zXDLOR zBkx%6cm4vhwH`j$rFjRy}#OQkjE6?-CxCAglYERs$z z4*lX@h{YCMnBBnp-Ou16BPP`!RmQ_A&HdFtV{3RrtysFf&#}umE` zvI(kpBF1wvBP+*2p>k3_!kNSwFf5>?G^uz(+l6!o=v7mIiF(?Yqw{`a_fi)8%KUu~Ny1YBd3*a+OMLdI|?Zf_$k1drwH<#6$`>%m3pMU>Hk_i2M~v zXd$-U=z!->XT^FO0E|+PCif=ZcVt30{KABM4Q>^J^;;QAnf zD#{<<1z9|m>fm0$r##+(tg#7PTPb;!pH#-}X?TemR38IsI)YKK9rXj_D@EE)z5*pc z>jU8dw{BmF0ePSR5_$$j;4{d==-6uTS|0`&$>#R^V8|p_0LeE|i1ze_isw@7=~8Q* z$^#8cW4{DGj;?Q!xJb|Ad%!yWEHt8Yyc0|f&ikf;R9L+!fm9B8iym?bp$2DWAMW-C zp)j4H?w#J&T_~?~zY`nvL~du((ESnQ1?+lIF-7-3n4OEb=&E(ehOv%AT}Lak)l&{%ys_*GPcwX_W0ECMWuIDUW(rT#xh&ct;}8n;G##ndfh(-*Wt8+wydNip=0Ckb*Q@`kR;Fkm-J4dLQBFivI9UVHq z30ROlM);`(1|!SZl~g4Mz+crGz{@g$h%RAFjJo7RWCarRYi)-ZLznEe-=sV{x&$i< z_}N9D^s_fI`M~ta+7+r3>H1_L^~p+P1w@}<4wwkU93#9K8OBDaou*{0yz&!!E^$lI zJ#@*_bkBIyqwYBhylIXoG{+WWQlcwq-2unHvb}PP{X$wDJy-fvJ?bUw= zgJq1)56{of3C`-QcjVQ1=1Wz8m#1ckX#h`#sP_VkYq2DBYW#C+lDwcU4aIP=WXc~A?I4Hl^o&_x3Q&w zrTq++b$Wj(A<=g_Dy`?KE|m6@L21XQ>cNNlZ%lmuS9~biB(z=7744&iKhMqPb7_CK zYkBdB>fGYgCH+;$w&_~F8=sC5^M%Hj2H37J%G z!pz>kG4v4{h}s1hWQ?hOx_SzJb@?6YIbJ;x51>D-;x$DWnr!i!ukRs;s(20b6&TWq z*F5dR&&u{^nA$czr*(HQ*Vy{T2j_=&t1^O>)7Qfqp<$0(pK2#&@oVj$YJU4Sef66F z=l>d?VU$#i=a}^P3)VK^%(d^^9Nu1$h#@%i=^-`*PMznBcPG}1*frKq;kh1SO2z+1 zOv#(ct39;iO>0N%TeYBC2EAJj#VTqGng@vc5e0BtAN(yIhx^q({B`cUgHu&p6fXSV zI{UJ(=IBP_aIFQWM!|_7V{z`hgVuSF8QxIXVLfy=6*I$d>%i>s*k47L>2m$oeTFoa zi(0p&7i6MiuLcpDSl}OTw)k6UFFlVsH605FJWHslzCSbPOXjT~w$&Gf_ZN7UZa^9I zt`zf7yln)RW_|csj#MU7iSy~_6M+h!@mJYCP+?r#jXTo>Gy@RDGTA#I0q-TK3#!So zuQ@_Cn9M{e%#2{RD~&4}E4NVrXSy*zVUrnaSe{5Z@HF7s?YIlhN3wI#w)(HwpWBw9 z7S>Tq?NiyI8(l(#*!>4!gO*r#0Vd}&wVpy7Sk_ZGchWvsg6x1T%SZ5m>5&hpdD#>B z2}lQ9kUq}g(1R)l($^lU+6VDU$@cdBEOsC^N{Lmh@kqu%U@NH@$X7pxx0n_K(J}s= z$N)t_$ddH^2{5iUt30uV(|FsDfKz%zxGmtVq;!E_l!RJw$%r z`g=3a9B2d(myNxG{$=nax~~MMih{%NncMo=P7EQe5{OPb_r^mq*c31zKgG{7+>$cB zsNG^#J8i#%o4@$^LIv)LpgpP`-gi%~Ga z#gC&5ex$h3Uh4*Fimd02-G3msRcPGE-(HFFk-rP*2OLe@Vy%fLkaP%>0GdCk}>KKj3M3jqbQEH|CZS{kg$G8P&7e; zwEnGH{FKldkhUKEp|m!?yVl_I($3HVIKZh|+97j^H4W8M(3g3FI|T#Xq*rH2PnXv* z5cx?~JIXO&3C8B?R~Z?cxUWIFvZgM2mUa`#Oz8%K{ApQtpgS+4r&F<4a?x~Syt|Wb zvC9}zxh*NmQ==IYA2ksHSLzyj($VO1or%0DHjT6@F=8OC+|TD_~>|Gr)Fh)mg0TCQO&Nk4VT{iXm_$^Xk+Ja zR~}mf02?6fYE)6QQt{(4{>iAz4V3?(IMJa%FC$KL9n|x|=txP;XsiW{04dQNJs#JK z#7)%83>^q+cByR~tC5kZUsUk#Nz7w`);PAa0tmEkv$5Zaa`!$jSqo4*GdA=p+9H9b zA+K3xon(y0te1(tS*qpJZbCmuZR^OlLFYu%$yhQyyOem6iWLrb*+Y;naC^~t#n#rS z!jbcLvFMc|`o3QgP3XOskR6Dvg6!PVkP|vBK}(uRcA8=nUMjZk5_nu!D7L-CuHJ_U ztO6BlQH6t1t)EqVf(r2A>`+6ep5p;cn z7BCrZjC2J`SLxEm=ZpdT7s`d#r*E6|MAjmevby2>=q!S? zK@DRr)|z(0`#?Zp&kUV^=p zp2(%(3hYtit|gvfXge>zM}u9NHl=FEFU7uFhP)H#1R#BCCGr?9jV*$XK}rS2k!rHU zyb@d8^)|q~a68TUZbbo0VqAc(=yv7vM7CG?3AzpgMD45Mo;6bDCx4XvG8KsPRf(ZB zSnX-MgB{NB2b5;px(vWbMG1>@`<$+FdX_4l2%`!Vk(H=6QuV6!WTct>nEHhPQ#6K3 z?v=(29U)L>{8sn4yWOLw6K*<%9|C%!#M7er*x#20ZPn9H5#_mWB?1=i6T)~}HrurI}^tGQ3B;nm}qT5u*frYY^d4| zDcA3~p8qZ4SShf$*OSbOhnQ(6s0Jl6o|;~u(qk{DChonR6v&qiqlvZIl`V+Yp^-x< z&TFtGud@$RC=Dyer)1qJSxjLTzNplg+*QuXMyPMhi}kau zCQd~Fx$iFp`d{RPKJBUnf^)L#x}B&py0gzjjkUA7*IRtUdX7ycc!mDfe2&3mNFHZj zuH?Y6#e(-I`I4I<(2G3#dNoeH40x$ES~rxwkaq%=Rvog&#vV@{z%!(DzCBw(S6qOi zN2~PMEAW3-va>+AP@IFX+QXzs04d|ZIhu~cY@M^8ak=1;PPJ349S|q^<_ZSl$k?@H1F_}`OjI`?{A>7NBAlC=**)!Y_mOs<2mrgxgQfGEf7WddIgNjOEQbGyzi5;5M0cQ9c>i`Q?#{LUBU>%$4ssnz8 zz_!I-pWYchvz$sPxz?vc_Y!_$xrg!PW&xHa41hlz_f? zA7xbe!BnNzB>Wg@;w`NA*+Q20r1M6oSnCkA9S;2!J|Ux+lX02v#3 z_7iqii}&Lt7&5cI7U+(B0f@(AN24#{cDM1_(tHzVy3nEZC^Jr#In69Khn1Mu78;io znvFb_)z_S#Z`_oRf8n0x8J85EHM}r=_m(y69vpr&Yt zt)ghG6^(Hs=E;Cw=b{QJOn;JlisTI5Ez}@BqzCL6Kwyq!_e9F_U3QH6Qgo z5XTh;NU7DaSTj8g5OuJQliy%Yeru2wNZ~~>|4OZuQiJASsXYaaQw5R6tkSCTV(pxl zCPQnm^R+iI&WB0HIvH;gvojA9<+%m>#B*Fg0F&0#XoA-Uqb!i<8=l{g6FlFUsQOrE zLH#<4yOYf>*K@{uw+eYtCpW%1ssD+83VbtA%n{Uk44!;B>qu==pkju^5KXx1*l8B5 zo_mP(2DH3IPac6p99TyF{@mc;@cz7Dk#2;^;4|w{lvIVH*6Wx&o%hZU67P-=i@la- z9nE>!6M2lG*cNDW?xpZVco?1z<&#LJFifSJd!d_d?gYLf!p7<461=w|m;}w-t^B3> zxo7kk!hdWLNG3aOo!waK{Eu4t>einH|5a!Sujp37M+$6s%g*wECj#r=WzMXCJ9KQ# zKG@snFK%{nZDg?4eYOHkSgHQcihQ~{N4NB4~ihEny>&C#hxH|w3F zo5r+!G?Hfw$M&2G%$>ggsOSr*3x)1@iM`1soqTIK+T|Rf*1!_mP6rc>Lcg^T*#UbH zm;`(PoO?0{tws$idDhkwS&uGL)~f_itH^rgUT8lR;NtdFCzQ#(LD|*cUlsrh(U@sl zsDd|UFTM`NM}oy#ccD4pP1ZtZ-D(c`Epnl{xFdaU_~U{UdeJOd{7+>OUBIE5%?L>A zC6L}|<=$jL2oe2d@#}&=26xd{=Gn5~LNT8E*^@!ARGmgC15eRc=9(dnKj00q&V!NO z3sq&5KD{M%l;~A?$SlusyfI2|BRdfL`GG)ss0$=@J_~VM0ksVI&9eA3HTnWa+YW$; zDf5@{y}>`(tgt`BmZ$_+UG4Cc zYBVM@^Y`5>LkCV!i=G%;S>LQkXxf*=phXm2RAYEU1&w1X_O_bqJrOJnxfVB$$?2?< zfGdbwZXJhKjr#G{(|~StT1WEdM(Y>&h4xD*Q+Rsu;FIA&SKEd8hyfMKDnsS{K6SOmBj&|@=@H_k3Yvs0titzc*4C5%_;zPDv(ku1Do3_Puf z32v#)Ou{zU#4$e&KdQ%MdA6KmH=|0?T}- z4pY8oX$P^aZCwQmBpe^?iCl+lQuui^Z2t=X^h!6-p~PyKm#L|+f7=sT2k6vPryzT@ z6MwMIg4i;k@Y1#}0C)M^*X)C%sUo&sXV_k4nK$SrsfEZ0k{EBlhvEr?yK1kbNkAI_DL@ zX90Lm-!i!k-xvNo9=t+qx~Cy?9Q6LQ^$w@4H>5Fm>a0R;H>P5;xR&`DR2ykZXRmy1B>)#7g>u@Ny&f0UzyKN&n7VYIzUR!?ZF39 ze3it!&IM;sq$g{MTEiNsTcY))oE6OL!C{n?JT?~lWrF#%v$AKd!|$aM4q-k9PeLlb z?Hm;Q&*)>Kn(%C?^}MdeqYo?fo}eK17t2&~0`zA%?piR|)A|NLEj^3#3ikTSii7Op zof@qV?oQn*6tyra-7tLXtK#h}-1oLp#_5a)*pX)e`w~y&Lp($=nsW*}hY7J9Z#9sQ zYesnC2=6Sppzy2_wd!(&%J!vs&?g2@oJGxgaeOz;K~R9a(H`DDP>!oX_N7soQlY2c zhEhfBK#>4}I}nfzFpm&0Jhu_G_F$yIB4Ps-a)|R}3Tx#$%IVWu zLqk||7Eodr9Y%>Qb@uT(Pwt_W{&qmuLq1_Y*d=Jf*}DQd?c0jU0~huHD!eP%|F9RT z4K|67ds=Lp`p@y zHskYk347#}Zr!sd(KKDZs^nqIYs2S!Iq%YEsotQE%emT_dXS2fRCYBo6|hEfCrV=d zExffGf9v`?btBd-~#dTtvD?ue}-^&2@= zawl{xeQ54M#MzGoLP>q(i8P}Qs8p?e4RuDZuYCfO;R^-a*zE0zTmT3c;J~yHLk{ux zinC~}$+HyRT;EBCSI%avWF)f2#i9ZyWsWYj>ZFbeu#^G~12L2c;;)t4>{UQR=3^vF zz{RV|rk&4%&5Kp{V@xbl!?^t-9vl@uD=&Dousp}_i9;KMC%hA)xc~|pB=@my2I*7Q zeQ;j^I7YAg2%%teasNgyEels%tXofV1y}tf=yn#e#>O51lr)|wml}#E=m912!AQj_ zFV+rx84KoJS7l&*oI?to*Sc-~1QsSWoZ!ioGtg5W*g4-QX+!8FWlmh9CZlTFdc`RA zY$@u+`bK3#n>COXOV*)%#I$(zmD;No`&jQadoRsaK&e|D*k~GnT$Wq+d;~W)7XeA1 zQNe|J0wbrbf}a9@@9yJU+0TWeZR|(g zFb|&`Uxu1Fn^_~S#oezcKLF(s?;FDo-i$QRdyGKhmR`T-Mcit2H_G`?jvLVYH7M`- z{1_I7X5T zEG9c}g6{~7&~q;|odm>TJu4XSUR3Nz{Y2dcJ--FmL;P;i&qhAs#Wmm{abkY!{!rqU z>6lBQ=$o8_#LDM4hr>>W&Uvv9I!J%}$$or+zx|TDNYslabOY)(%JipigxUlGWuN}8 zK~rKy|2LQl{Gr*RjaCC_#GPzdiop0#rvfy#o4k|*dqv%KWR3q6Ou_AO*dZ3)3!m~) zcN!zT3#j6arf|~+AcL-?_^VK~+EuM4BUOp3O6D4bpW&}zyw(%DW*3b!#24vp<6 zfK3_x1fAMwyqy+%@kFM9k8m|)K4L=|F73>hD3G4k@1={)IACvlW$Y6rqy&eXR?N8~ zIM<=Ebt!_gZ_qap8_zXe#ikx<0&(9A*`MEo=zmhZw<*xbK5vU1( zs|2d`ao8&2)V@l6mZpUvE}d;y6|vY9YiB<-XTd2Zaqig zlEMNDNoZ;ta8zob6dGsulmwwiAPX}I#SQj)Zs-oQr+lJcqtIxfy?qJ_Nqlh>*gKli z2JgTyFZ6N&#*H$H&DM&3-Zuop%6;JF=7?UkGvXg!>!HcQYGu&8<&K8linQKYtsF|) z4;J$-D7^K2^rox^j_KTU!DeMz^b%|a!tMb~&1Q|5T2U{dY+7GMFXT!$j4S4~SDzOp zZ7HF89W>Sy9mtJ9DLQ;b66PL+|6{MR{$BeDfQwo`wMBVROlnn(Z$B+gjttjofTj<$ zq9Id@A{jD&Gf?2%9sWAU)B00jAhE2y_T6YE(@w^IIE4p5dF=uecKD8Sx79&)V>bZx zsM3{cq^JOwfj7JIS)GLS-eg`w(ha`N%5g$vFAA6I~n%WfR3 z`M3g8NGczfCwsIQj_$-8__vQml!3|-&E-plCI;- zX@eF7PtGYeZ>nu|hAQ~&j36S~D@6MVVoSWpYqfz45HohiE{7Z>!;Gk~ZLf1Sq89zc zdIT)li^OL935Vjk{P0(>8SlezdRiAlMiMq-@P-#eB$YWe*=bbktRqyLkeBzK7iZ22 z>wqNq#9_*eIC@IgaMh_Bt*6IGqg)Aaz@WWvwAL}R+Y2GaPNbv9i=3Qg<xz4yU$(gr^ru?xs0I!)+@v#c*3ADLF@c68=cVKGRk~5nKnQvm0)| zyb^F>_p~xnfpro!G;LGx3p6!vDG7IC3p=Kdeqyb~F?`m~cYxbuQ6u~h*pXoX7rn=F zO7G1^btru~SOVRbuJa@s{@r!ddsNPz*1iCMc}XulJda;W=mG6yr58L)uRvGLa%IuM zO5+}Y@K>d+GOq~dKAcS~7BxfHRG#3IUif=C~HJ4f}?diz5PP=>7*@}=7kpV(WV z37;c*$HQVK*G+cLU#>J$Co2JU^7fb8Fl7ViB*?ajZ#;w5)VXYW%B#Pz7B}6sIvrf zFequfY#)yjxT}CvRWmjNSq@00B80bgw`j5|_Id-_$zkBu56~^GO@4)9W7TMWjv@)T z!85YJye}Q*!c>^UfLHA2pjikntP6t5J^anj3EtPTJBmY&bJsczUWy9fC?GQ1@A{fqJ#f1 zxda*<$guIz3FlU=B=IwxXQH#Wxp1oV3jhaJdrPQ?VouS82GmUBkw<(zWx zPfyD^GqUTm_b#J8!*b3UpdprX@)e8psCS=49i?{@t2q_axep+Hls$ukL4}OHXTgUV zT?R98kH#R8KkU(%quNM|Ud#rfNU=bJpERFCaJ=eCXIVx1t+ z1S5fgP#7wup+n81Q1Ec`7}DX>kC1b@ERdH=)PsqCLSod~Hy%n&E-QGESjM&`+gpJ4 zaQC{&ov!m*_b!^tfGhUJ81PWG!=PZyDs;7ZE|+_j0dwZ-`1Sn z28`%MyvkL~~H$?g@us9JoM6 z%|(aA^DwXZR6+YQ%#2a=TWvs7hyT#2lmNMc&zMWte;M$r6x@R{548Km& zqSg;O^T+@!zZTmcojCAh6q1u#zadlYFAjdp=3GzRL9ZdfPwn`8pPneMrG>*ZrOWj_O7do(w#| z`!`E06V;N2PGTdZW9S446??bYft3MZVbo)@7Z#_-ml#L$_sTK&hg7}~B`}}+F!8Bt z8rA;B;G`~7+AJ!|0stO|rIuenor4cDma(EQa%#as*)n9mSf<_=-0m z#?{^(uQ?cVcv1MPcyP{=g#t1-pooE@{kkZjy@P}XZMb-m~8*NBq?Ue%|Vw?%sxieNP8-< zLBHL2=$*Nx)|*mEEK}R3^MRl24_ZYQ-DtO=eQW=j^QzZ-;}R;I~{w*B<$;78Nu3?QzX-Wdzz2ztyGk8)P~`P_W)5Fbb3+TMTe# zbFnd$7a#5=F;930gF5E`+3P14Wn-`DWUr;j3fS{B`{0pauaHzy>{W?$bGUaTc zV<#i@T809RnuE>Z?waAQx#wVN%G{fAMHeOXO9PX`_$H7i6Ld58JBi%hO*0be*o3PU zJQs+HRm4y2K~|}6%HGFZi+wWAeC>(wF6ETH7#GO+=gHZ3#QwBB(SloV*2ARXDpoM_ zCJ?1?U$_wJk(_WMUThkwXuc;t6@;TGOkPbtzE?#PbRn|TAy3dw@U4WOd@pq)4|Bc5 z{Rlo;L_SnLG?*Av)IDFo=Er{13l3JXZeN`b!P}z z$s;Lq=7{d~6A82$8;sSQnO!e!eT(_ASAhG!ua`~&%H8OtmVZqz{qcOvtO>nzECw2S zsekX|Z9RmFnR;m*g%bpiBCkiibbf|jqQL+CFPf0YcF_gP|ERLCM-cK(s(%+L_6-N9 zp#IsIO+gKp`43qEd#37d`ur!Al!D^sY%(Mzh^Vd-!L;wpqI~Yv)i~?j*X^9w3r|@e zYf^liou?viGXFosQ-3%QqMm)O`xkj?2_Potx?@4S1LUayDt5^cPb&Y+jMYurR zhLg-9Y>OXdo_?`$Iz?sN78s3t=bw}eAL!V}Rl~u}Dz4->KgwPgInXhS*H8%Y9;_(7 zL}2{^%o>EW)Nsh74xcZ;{z`=DmOvGm2z`1YVk~`*r2dF0Qv>$sna{uq_+OKrdG~jP z`hdVlOmiuh4qb`{%ro0Y6k~2I;oQm#z-cQpC}z!TKyC@o;2nvdZ6mx4GhjzTO<&LV z&qYdSKZ-W2BdK~Ewi(d?Zw1WCZEYiD8(g7Iyhrs!{C8B4aN-`$0f_B+BG)3f<_vnp zIG2hU*Bo17;&RkLNzEDb?LCo6l8ClTj6f-!xefIwMT;Sr{xODkiD=d{+{dFcIFk`N z-fZ!+PmzaFl<&zK?l6vt?a(SLAJv^EY5uth=t>~xpCFRc>-repvmi1TUFw3!BZPA| zP!Gg@iZwY+I2S;WaN~gx?q=20c?;)OI4ZJn)h<@L)K}hyUBcR;KVs5B%mWb*=^RlD4u~kw`vjhO~?v3@g&ZF z7&>jhd#$xnkGjljAF9T*r*>c;dac>It>j)Ob!K!WiB$-DSQ^X$G}yF>dvec2FOojA zC-TE2d3dS{bcHlMD3Pci!7-KJr4Lfv?4PRN4y@oYsc@Dj;e4XtqzwB#s2Npi0YNh! zJxvfm?`-&0ooGggjMqN*00}Tl>ZD$3#5)D%QM0xnDX$!}!^G=hK*MbBbC&Ul`6JYLyB4-oh!YOuW%&lA^kO#9(&l)@#4R$?;bI{?uqa-hT(SV z-R^xUtP|*)-734i{pdopl)e?h6X|q;mya^@5W=TkVPqK$=QBooJL`SO!)^}7=I{_< zZM)DbH$(KV*%-=?4Zyn7&qco>x)S_5z72o_Gzb;lIJ*(Zx*2SSya&ZZkX3QGA$fIY z)w@Ft*ag&fowx0llGJUjze7V^6rlu@qUT_f9ClzAz5qUm$NFUYhLw9`_w_QW))>H0 za+uuJ5kZR-#5&{!l5~)-S%|7m+rJTZhNSgFGb|LOZ7G0Fj$|;5ykYsGPX_=ew^qeioUHAUrDTy zfx7|cDjE^N791V%M1D+mxW!B7wpwCOFf-4C)r4XfN`zryol&2EfpI8HY|bb*Nn!a)?8_T$PCAC`HqHO|5JVQO5cijEato=x2Q02 zQG$~N8?zn(;Ls_0m1`J+MAE1Cu~Pzl<(Br-2>&Bo^%Cv}@2|t96(O9BccWSN z*%;AtgQMBQ8$+cGuUhTD9ElUiLMa8Z0TMNw*-kK4Gp8t9k zNi1|Tm+(7Te>UGbu*k>L)}fWO3x9@p2qnWURDHIgUNN~8d;1>kUzCnnd6KsC_dZ?+8#R!4#e(E=o76p!%|Br&5O|z2kqGna5;C=xELW57^^>uG#OIx!UXGX`Nhfa zcPu8T>V=})c1uB2StesofZyfY%Y2)0CIWW?+A-WvFpfkhJG&X}Tj#KG?nTnE`BD={ zD=^yAMG;&rO71>TMho9)31k7Oi_Bh zqW}d5Sg~)5qRYn^CA5oPYfIO4hu|9)076}drl{>+)`DhSr(26fvG+)HU4?dG?P+M- zS}JW2mrL}xF&=kOzsb2vv#vyy!Ktln!4EN!cR4`16qsd-j%xKpwAzfT zAJrFC3Y>$ay{@-fd(O~Io4jkZ4Xu#l{u^0=RNPw|&{s(2Bs`qha`1bQ{c6|<+|%TI zk8eB*pntu{tO!leXg>*8G8dBgf2GQ-u+F-JL^uA(q(7jyW}p$s@M{p%lh*hu5J3F)r$~xvZi8Ro zoo}4S9jOb9^9s2;qJy9=&c}DSjQR$CiVFash6!>Rvb5>hSziGV*pe_FW9tRNyik!J zU?{|Ue%()TNd!I|>1^`wuwif>#D;Lb1RP8ghd9|)KfzZja7p;WbAU&c0d2V#6?jwe zY@8M~@Ws6Fh8Edk1-*%TULa-GmzaF?r=?XE$HlqB_ihF6)-`}v9d1|Oy4Vc_fgCrC z$J^?8Epr`yHnAIcB91A~20n3(ZyowwAuF_1IF?b@?S;^h)73Q5m^@9!c?h`kSc9`C z>k*WKA_52WLr%3Zx)EYf2M%jc)r{RF{W?_4T%YhnEJ_k+awK2`?04`Bi{JzBh=RoB z*6G;ZWY*Educm0(6OaR1RBp0IvW!r`!d(#f=F3A0G2T*LYcm$oJ1<+SPg8P~SYz-+ zeg!aw7x55ejj|6>GJ$p~z~q$}#|lrRionPCn;<`@VWiJeeuaIkD(j44^?dXUN^4Ce zwl)_4s_jKRk?q=C{^ZtPj7={yRJqkpBS}YF)>lMr4KWH~X8?A(1HtV}=sd_q$?E@JnWf0q$=kTK&MK@dR*kcy(Ec|n-Pn`f`;cJ&GiuRp;gFPw zQpHSySSecIuv|`b65j~IiWv5h;%@8-7>5-5Md{ss2Y8PfhNQ&$^3>FJsdtMYb+X6C zMpnAs)q8BVN*%Sw=E^RdP-3l1=Et6bD26|G2ft-qfdO?i&Q*8^5h11H@dl31jlX3z zKNqU(x2+5KvL8-)u%7#i2a&!5k=9wyH5c5Ai1+aC%Wb&P@Wkmqk4Ma6l}>^Z@vGm^cstGjn|Qmn!{kCm7%0=#R}rxdKP)WQQJ`H z_e82!>lMhg3M9*U&blU0<4g&qP$D>h4r&luYFW82yvE$wY?phJL`T zg-d9+8qI7&`KtMjn>;uuu}`9P$Wowa}?Zz+0TDVZ{~iuqEy39*u=C>?q) z`ZmUy-P&~>D9SigMPlzq??-Zej5fttkF{8k1L=PK*Nlhv4| zZ&r}|#y2BttbCg!fib|ZmkRl@<*A8}>BLu3{C*7b7?>j5$J+DF@I3qQdGcG|-Csd- zDq6`n05JjlelS`pt+^22_}Pozy+G#>DVZBav0oP%nH;9viMKGPajCXBF`@Ari$J_5 z@>#1;E0f(Vv_W=Xj=WL!l^AfWTRi$pDmise6Lc$Qlu}TJRsyww_273^{_luB?SLd2 zNxr+B%#%D%p}v!c^A!B_2JR`s7h%AQdu20K@3_mHXmM6a%*C7&x$ks}fqD3k$O`Z~ zdE%SekAdg$PJv0HCVN8BxVD^1tW^S!u6>EkFAY0ur#J%xxTna#*z&nQp{{5yvl@c zd@E+3ajAw{sIeEGl5q)e(n^dA{%wp9>Mp`D0M001)8PtN*!uYUy@gN z+qJnUpL*{zL0YRgCX}dhYUXVH0*XGJCJAN%r=YXesueTihw<)xaIkca6}s?cb|xkvAywVZ=}tOtl^I)*6p|)nIgBR%Usac{sR!_e32-}ZKNsZ71bWuzT&qX z2-6$|EXXvB#+4*K3MlIa`N$d>kHFJX|1S(}ge6p#7!chm;r;Jx^6 zGLMN$sO;9PnsEY9O50}SiQEHr58u+16TFPp9~kji;@!o%$=FTliM)b(newJsvM*b4 zuKdClf{CvUx0R)l9xQW0I+vnDz#$A zB{^NKvz-Gr9Yapvimbqa$V;j9a8@W{J>5Yl>GA^D5vh>6&W6NUIuYw^iVxMg&oT1N zST13Fmm{qX(EZ^>3=bDbE;rfjIf)HUH*!+jJHJ1j33Z?kWbn=i!4bly7`#)1Ij75N zxXr4Q^Bg+;8bpb!Rs|yTblzaIT2 zHa5Ss|B#pddF-H9+PqJ-dGFwV?#<@tCd_=N0(V5wL|iI>hl-+2(q7VTK-JEIp7Y7k z#V7|haRgf3+2#BKZ&~}kfO68o!ViJ@Nb4h{Bx|5d+lBmGnGfVFM;kN|VYtW>GK)b{ zI$!`s=2WEvx&+Lx1>j5#un|?YZ#1W?q~cqEnX#KX_QwZEqt|iPl+P)4~72B>ur0v=4OyItZ$#IZ&qxP^CsgkQX; zZO9rcv-5!fdN?Z}CI853wnwC*OZk@y8Q#+@otTjSS`+(Atl1A>EU|zT?1Pn#8=ZUY z6hpVweM9q9e@I9flWLaOUJA?Wo0_Mk))T14Ayj9t6Xq$m6dEv3e}e1)G|CttevmnW z;rcN~u5m>`*{U~U%ibIgAlMN*mPCMO2}Fd!L*vseG@b7Oebn~~ushIrM|s|A4K*_^ zuIE1hY<4*FXP-FpU)tWnpuY=OS5T^cdlaM!l5~g2hGU-vy(zYt^7;G5UO=(yP)rIP zQS)+e84W%90J4!kD3k3#o6AE9mKA@e)LMX6lnq-d-)lM$W~-JusXjlAvr9N-ortV) z2jcTve?L-)ayUTp%C-UQwSM`rSB2dcyIN+G=e5sQv6LdMm>X?G81CQCBniY^(9i_?%KfTh0n2y=Dm<#-~ zVRr<<_)Ri7pJ{yzqmKRu<4Nki**v)(maG3>Y)I+#qS-$mD%Zai<4!j(e1mFEumjL8 zMf03XjC1IhoWBAci_bp=crbwW z3)=V!49Y)Gu(1f&$V;$pUF^ASE)yQWbsi1qL=!M!9IxCB-wMAQ@Vf%Pd9{Iyn-ENO zg57}cK&0`16%fA%;L3@}SpWw7D79WQ-{aj@$?>2wQ- z>I1!;ZBKV22nHG)Z0#8UY#DTIk(hwg17YYG9t5I{5u$(#TX?55WM1NcGt}Cc^&3`} z^BpQh`5RUG3`HGEv(KUv@T}%<;9P~)%8m8v90l(hKeeq1iTXp+Fo+ZPjlT}~qoMC> z<%;_wNv$Ygn7GXiBs>Rw9#aze=p5Dc*6lZBJUu$HQ-pvAB%_QV5yavgrJM?-*mfK)$Mc0)i|-Y^VbB zQ4rc@a_1D= zPH_x&o!00Ljz(|dicVsVO$ew;iCWtV&Pf)#U56=9=lfM4war{L(a=} z`%Q@$U{c2`*?^7@PSY2Kemhexu)~UMA^M%lSoFKvtCU?nz7N>c@o*otjz1C%XdYwp zhEI`^URbci8UqlH-UcrrdiiM5?~yl3K7{BfXfSMv^`cZ!3vgGdW)z=;yB-90LFAt8!sjK{?a6AfmBQx| z#=HUjBA-8fP=e31>Gz+5-MZKBWbAc%?FMHD7Rd!nRC+KA~HEKC~x&?&$Gg z7d`$_h8~{^g2-6^`+EFbP~?9?kN*XZZBmc_8bbv=zQWf)EE4X{cU9?F5yb-Z@V%=)fw~{te*??^B(=YOFx(C=L-E?F3)D;BkbhEoeEsv!~v?%K(m=1 zoov|Sy(+M}?&+ib>P_mK#i#?+dD~U^?AxJ(bA$am`^XDt0qj{kq%nA|IO1V^tA&-Y zj_(buoVnx+{u|r@{Vn$#;sea#4!24TeUetg#MgIWGI4Gp9W*9~027?MG(M_q-_#Ms zlh-H~w}lGLqifNfa7P_9u0(4P{h+-hl|jqr@aaWs18T!G%XhAep`JmK^&I{IEIIns zguU@^wQdR(v`yIqb8(a2`;(6#va4qi(3xlYo&XoFh2GKaq#-d~6N zWA?l8&AjFvAfwvqE**H2bxnZ4MxW+(LTC{P#i#}l#`}MN2(``T_Fx16@I6Cv9&f!L zzJru7ho=Crj#E_~B(Y9{>dr6FKnO>i(f2pmcYcw`>0|lI#TYd(8rHM9nzyL~I;s!T zNa}U&-799E@+P)GhWGbd^yg>QTfw49=GlD3NvZ<#jIDgz8$3I@#``_v0A7o5O_ z+Yt!J*<}{z9cnahf0`44@wr8967I-XMd$q4X*6mc{4T+43LX0FcDzJ+L@@MAKmZ#) ziVZ_>usp5&cnr?VsLG!31)$10wv~q?Fa;*;ofR|9(fLb0h@-9W=f%Mh;m>`+o5G*x z2TMAK??9ppMC6u^S~Ov8f&@oBPgU>hT#THcf641E037uClD`T5Rc9KZ9kFuor{^~) z<3gd(q(QG6|6F1-Kj$;!q8)5x5*s-Ijf4h2O`2h_Eq=QsR%#Xx`t6dxO5xo`^9~^a zbMHYex*^1^+3HzZjb`nIAPm|962KxI3Hg{LD_%{oFOzrBhkW*-5Bq?zgo;{OwjJH4 zU3w(o;B)5u-4<{ghe<{qDQOE2qrAxM(R+T6fyIHw<6)N+PL0OB_cG8;V;r+Q|yP)XhJquE)!M@ z^=q55f5H`;I?zfKDb_TsIgT`sUd7yP!IPuv`_bRU)I16w2$t`dw})`pB$y9=fC9!Q z;tBbP#J}wTI28Z_8Yud%`{y)=+d0h3zl4sU4xrhCYDCV8;$PX*aVE7u9&{M?JUcM| zfaU`$i4!+I3vy0YJF>766#JRPnPLw(^t?RZY4RZ5BxFhVF@icH&BLURTi~DO)@%(G z#D+!9{F*-nk1^Jp!??`#nN$37cHY5Teesam_bG+FQ0+S`65ED~4+@r>V}DkCcXW78 z@SHjL2z_3tkHveyM5W63TU?tGR2xHfXCICV;F2r?*alewF|76L-UJU-Wk@p(B-yVG zNF9Z{f8!_4&~r&pKWFRbH2ncIZ;3B z^mE-%H77o&pRefW9{t>@pUd>~cK!6}`d{kbh5C1~emgRI( z+$>Mn6mI#pW^jOfCbPh-b5r5OKbRWrfGo?z=z|AeVOF+X>P~JO3Z9tV_u&O0f$#<& z<`&MMa%cL$nwNq*+ioaiYym+mbre2NVzRy1j?mwwRwF>teoWt*d@!R&XCP}->~XQP zm1}~;GAW-AE)`t_`LI?FgPDqBZihh0osV~D2iQ;e+X~%AVc@$wT}S5}^1Ym|U>1D8 zpZne7oTd4%!)~K2$X`63b698sERmetzvQ`A%!IcPr^}z=pB0Sab`y7BIR!6*t#@@>9b6=k8^IQae9w%1J zxCr*pk8?`5_i=5@S%PW#vpzbb9@`~;lhfzF`?wy;T?>`E+sUX#jqm38-st0cKF36j z51fp%fYSXreKz%REt4AWFynv!{vV(Kct@DL7)C@X{%4n%=KNvukw5qAD$hy(-{=1u z6qqsns%x&k`pUUiPM&w#DV0Me&zRx5^2#|=u9-eJIAzY2Q|C;&e#({8XHK2vl7iQs zQhAyxbHR;Qj?$&-XH1$qcgkGUn>;03O;^jv=F!7P4Ie+Gp{dC=GBhPLrFrg@IYR^W z!{=xGyz=~+(}S65Bc{)s?i!pz?sB}BZCo+)qUkfQndh4mni-sa{S@ESNz-S9=1loc zf0xfUcKV!PXwnR{HRmSDm@?Tnb=Dl;iIaU--4vWM*EcEXn>2ayoGEkX`c9lp&^}-D z%(nm5xod+wCbyKe{M_>jg}!sG!64&vz=T9n_KJM7>-x%2zKj4}*H`;c6R|6fI!dOQ4Cjp~+SMnwp{Nik9Yc%U8(mlubT#7t{V01f z%i^yN|F_`(Vw^Ltpb? znuL=5T&rKklKO1$X!?1McV?xb_bA8d`oV@Y5S#T!Fg%)ElKFdqvahPuM z$9_yhS_ElVF(tJh6*JPhab2<-s{g3B2K8j(N4;<^yM9T3^t8y`(NNIHw~iurJqRE# z0MD`8ih366ovYiEdRORbLkli-3X@iDjL(-Sdm78)k7*@HD?K_rtsH5!NE;%hRJl5& zjn7CMgS22q+BBp^Gtw3yZ8g%cT|)lYZd9jn4&fii?IEOXKso6Px_@2&BKNR@i;G|Z zOa2Dr??686Zq%Ww)Z2=drUlj3hUsHa!`l?t3cXdCIHSgfIwRM zk=}rGp^q~LXhi5eObCy$Q1l3(Y!k|+(r7Z$rlqu13z+kf7D3u3j8j(Gs4oiSuFt|{ z^#v|YBAbDKu`eF~3iaJ(-SXOmGvJ&{e~0b@jv!d z|BWmi1@5fB1NGna$K#9_bw}&GOS7q=hJx{^;)gTUg8dlB>GqdW58g&S7Isq_AMFo9 z78OE)r;4p1@fDa-yLta zBYz_DQ~B*pq%A<2!`mDe3u$*{l=~8CYctY{F>sqR()>ukm=qTKCBb7iF6gS0(pm$n@Ls7Kczjks`JdeWnlQuL_w zU$@C-_{X#_`2%fY^PT*a;@@Jd5xJ4povx`sKKt0+cm$ANjr`Q{n2fYBNJ|}$`AD0M zv{aqD3~5oMouTm|jw_J1Hly5Hq`jGu_6pMWq@>lM?z>3y;`s4c!UJ<=@NFoAZHE<% zO6S@VRP$kw$^hd;!l*AQ#>^wO1!+}_yei~TM>=`K3Py|G9fPD6^>F ztiuv;{eX4@`vct~xay z{}}Tf>C;NxnQ8;p7fsB4ElXPs1-WyxfF+R{0csuKR%4&hV!RWswoR#yho%@*GIR;U z5bB(H+DsIF;{4zS%o%|7Z7QyAaLhX~o5|Nu!1^?#Cu8yG&7SD_w=8`G29%=+>hG_K z$Aft1957Psm-)(yLDECWZ~9I={#cgzXGGD8{M@H9IDQ!W*=4$sxo{U?t-%i0?LFc8 zMRr`vfJ;Ro=J>PYarQfn?iDjr$X+jWX+hbEC_BCfc|%{b$eYs7_n`j#bK`NG*but- z@K}Si#YlTZr;U>NGWT+MAgvt#zlk#Lx_BJnV389L!#%u!B}Z^(rfsPHh5XDw5%V|myBfPV{biG`6S;lCJUZC8r zMm-DlPU;@d0Jx?LJ%<)RYj+XQp#{00WJ!j7xd3gI6oKy=<8glvW0$?H>?-2CEKLpN zm~_EwSV05YzYFa*UVuf_EIbbpnU=xd^#vDZPz|!h`ExtqL~(3N>;?R$yzN2Sn@AJe z1@sr4Jq4B;toX&q_aaKa1Nm*;_kAMV?5?PTq$`-5;mme7f8@$!lfgQmKj*5ufWxkf zuuRZB4uRQO+8SCgJqu#>1=WDL6EIg`f7(X8r^&7_7j8nrpND&Jeu|nLDzaP#VwMNu z@q2XLEPA}6B`ZGY3+hZm{o7H$ipE@a9Sgtu;A}$HP_Q6NmxgAYi-s2DeJ+YRgR1D$ z#4JD>_%%Y8djRa5%i{5Q-Ru8A?xd{MZ|$bbGKMXHTX$tVUev=FF3JL+z974_Q0J}z z%r7Sn@uFgE*?Bs{qbaFPJxj z_G$6>&HqAu^3ev=-*9a_etZx0bBAZ?R6|xB0$owh!d(VT_#e*Vf*J8RV_e^xiN!)Ms%H3TGM{>__;50jwvP zI*`Xtg*f^sHyQPLXwcFckurom0X}=c$I@jQA7rBxh>yc4{$(@)yBh0Bu(IUA0$QC` z1_EXqoJ9CRg#YF+>CaY%Iv{dC!oPdX(9j#%51Rx^gM=CKzh;2z zd7XY0vLRPuuk$U-l_4k0Gru+%hz@ zAMKd;J6ZEG=6NDRreX0hQx=mViT7fpWBRS4@ACdUEAK%ZL(9OtF0VOb)`@m*4|pHE zeQ4;9)Xp8x%6skl44kPPQ(yizcqH!}8hVR(jD9iO9%jt*-HZ|8gS@mNkTuc^Aa5mJ z{c`N?p`l-symPb4%Nl1inb-%z`0wjOLv!J_+U8<7C(%8w^u6ygU`QrA@$k^l(?p=G zp%i23Q0lvRJ;=t7+7#lq4g8QC`^rX7v}CIB%4DlA;Ct`OY3JPz{&#bH?XmB+5VKeD z1Uq%j{ic~wtTA7y#@?O{vmfJ<3v?*@JX*c#Z!HX+>dXre{yDS(usaa1qfHpU%z_Pb45sxi;Fn=d24*e7?g6&u zO8C!2x!3?dKjgKc{aOXuBDC}H-vT=cdlHNf?pI*%hur}CC~SrTG;=!2=gp`S1BlBO z#NktD!=6IFvKc(CMtR+VvH16p2GAaZ>4%vHeH1`e^`r261o9B)?aZ&`HNa%&VFafACldQ@8|rg05%Y4*%P*c2^2|m{=9~RHFZaT>xGS zpvQs)Xbhwg*vcgG4ENpx826;0W6*4W5NSZ(>*X4o3R48L1ZEw~W|-Sy_QU)H=53fi z!<>qzC1=AdgINzV2y+X}ewY_v-hg=zWtVcvlGH<-&%%5KW)IAhFfYOU7Us_|Q|4%_6lOk58%z);4)ZyfJ7M<2`~c?H zF#irSX0FDj!_>lj0_JL%oiGo=9ESNVj5beWXTX%h)WNKR>4mu#=60CJVP1xL2S&rw za;L$R!nDHlz+4aWMVJR*4#NB#=JzmK4fF;x17<+HJD96JSLq48`27Aq#P$?Q~i8k@qVveVfaXot>ZXR)(cAv*`v?>wAg zIiFp?F2uQoi`d2N60C<`%8J-5REH8)%4TB^wVYM3N>;_@u(@m=#{D(yGB%$rV707{ z)w2fH$QH6i*kfs8OW5VCnJs0@SPRw;KE~QuJH~p;*$Q?A`odLgHM2{E3}44Flm8b z_O!q=TUu9ZU51 zqLF~n8ISq{UH*iVDI88u7Y8L-97%-MMf_nqhdD+k zVgpiAIjkTVQ%|{b;&O|;A-C-A2r!lY0Dms40X?8 z2+yq1fS(kNNUGPs5%4Y?7LUdg?lhw?IxTB_BdJkoj7(Tcfr-TflHyI2XWlSg8{-*6 z#Rsy4i4SBBk(TYI^o+C_OM?ht6^Ha3cll;Wx|MtBp%TGxw_TQz52CS@`&S#5GKtU( zZ3Ey8Z3Cg{Z9^g!iYS@KK`H(mkurcI$+Viq!88ryxdSsnb`mMCyU^&Y3mT&7iMxx} z#!v)L1DHjR`e}?cvipm6RP`sZMmO4EJRBjFUcp{8d|A1h-frgfhyof-=P?BXPjmEz zP(RR`1*k$0MJyUKy23$!Bt=a=iMo}k<+2b1|E!K7;gVJxdIDlZXm8gyy|CbE#?t0))fXe1J8;yxUj9j zdVsBTVTXZT11!_E7x)t}&%?Y9^B#<^USowYMKEaTZp+N3|&)T&xBbF(_vy^*jr(4g*gOs943De z(hO4t(+sl)CJaN@M>_go9ksD~s?m%2AdP*7)$;*YV;=@jwT#_w-nR#P{jr{CJP7Qk ztbvcf>e0Z2y8PkxU>AR`>UXRm6~hb*15=aRG1eHt6L0B+Y^}C19t<{zI^+KM0Q-)% zD44A01JGov7!t5&RIDk$zUM+a_*9g!XB7;D>~DlNWANV?k4NK-jrZVwX*7T)O~ei9 zs3nX9){2ML7^|}oj7n;|yQv);Sn5w;HERR;v=3lR+1n6`Bf-)5KrP!$sE9~?6e;b; zI?#;{)@nAw>~&8QScUvyOd8XbQKUBq{e23|O7v#TUrZe#wkf>@t*ND-m-RN}fj<$n zGlsF>07KKwvyQR1=q}Y8wDfe5x83%uC$~#Ge!5RfN65kKbPRgi!@*#TZS}UN(AP%- zCi5?PJHm4Q6yxFT~gw%?L$18C&G>cShq$*6u;^=BZla5l;wqdb+#A=yI`~ z=;@|JvH?%`MpOj2_IP?t>X$uO*@&Pbuw$OSZuG^GWH+@(B2`Slym1nVrDk0d=i66hZxKUD(&xi1k|TH^+vtY{_2A zH&%D7SXNSO7+wAS{?1TeNl7K#^t`D-qd$Y5Ol@TUpwg8hOd_O`vTMf@Grz%;5An_@ zhOz5SKb{>1bkfzsZZI*@F1q<(KgNwoECbwV;>akIlCdwESUiZli*95)&3kc&wQJn3 z>+d&Wc#JfPDLV~%`mmPEs1N=xYA$bVTh`b-TP&g>s*@1Uk(%B_UspWIpC4v3z{hPe zJcrDxL36SO;Sm0_bB3wg^crZX7)nZFwsAXs>u)W@QX}Qgr^ryk5}D;97wl9Rt6O zd~c@-`#j5V--O?VaMSg)_h0@-az|r#%5Bf*zSyUPO+M~xCYRHWy`DIBfg%`hEQB4# z&QKHXy0IICeN#?TjImZRcB07cr1)bm>1;@AMaU@jl2X`FN+LFX>>}|Y4t!b==O*kw z6=E-{2XsOBVm$%(#5oT83NAH(yNw)gMyN&D$)am6_SDk(uK*9)n+hQWRj^X*niV2e z^jpGzDP|1&W)#*+OM0m6;X1Hm=Y(s3v;@*cx``ommQ;)tk<#UmS&x0YOJMh7m#zZ# z45VJ@aU*u{O0lzt;Rs5C#`T%xv5!CtVV9Y=YfvqY^&cq&ms zS_Cc%-wByX@S`$N2rY@dQk)sUPO6nQ(gx9E(!ix*I{;;A7XA{hbXln>;@sB3SJqzw zyRjR&wkaQHfmZ_z)kD&PrG2NC;5-MfVIA-#-6oJne#F8lGo!uxs0w~`{r{=~H28ET zb#(E>;u*y?#aoLXFFsuS9W>@Uu~XZ9`B)E9~~z8gz#DIHb5sv=bJ$%@-6{-NTniqBN$R$W>3>8jVN z{<-RwIkV=r&Hen`JLdk|+&|6r&KooD$MZH5|w^Te(@kGVJiWe*1srZ+QcPsSDsg>td zR#Y}suBqHm*;jdW<(DcSul#A{JC*rWQ>!kiDyv#p)mD|N+FSLPsu^=@=YDG5HPv6O zeyI9T^~=?-SHD|*Va+`?sK}@jjGb2cOlimLjkDL5FRoZyacku>l}G1|s$O&1CY&K+ zYX!}r(zi^>N!<&56yjNZez{Pn#V})LCDn@n^?TB_-DnFXRn#PY4$+*!{y&D|4#Y$%YRb- zzsg@P|6Tb%mzP!4RD8Umt74$y>56}>_<7|&RsOAVR8@Y}sa5Adn-x_xRsO1Y)hDZN zue!f#f7R<%f2}%m&XPHSIh*I)HRqdio}Kf;oZrs*;~Z`7w7JD|FPpo3?z*{&x%VL^ zC+3ctS1|9id6n}npLfN)tLNP|@1c2b&UbC0D)t%Mr ztK-#IS8uMqvHIrfTdMD>exUl%>L;qdSN%M4?U&VWR{vA=AFBUc?Wq}4Goj}6nsaL| zt|_gVTT@rFq^7NAbxmi@`kHvn)is-IZmhYv=9ZefY96S0wC0JL@6|kC^K#8EYu>E+ zr%FYBK_IDgChTj%ed&yI-l=qt`goq)cMi}QE~io+$bl4MDL$u%W|C0k0i z;w+anX1cOupyYh22L(8j1Z(`9$LFNx)z;ir^QW4c%O1LnwPO7jxVP9>j73FW-dQo- z<&%W}XBV9_6yqzinJ>VrVo^4%OZeZND@I~ir@=h&Ny`(q@c-f|mj6D?CI8|YunGSl zo`ewm>2u4Z4te-~6H{F|TO9r=w*zD@X%<3B_L)J%x%qCBq4!H6LoUg{4;>f1Wn zlYYELEykV)ils6-it#5n4Rhg=;^ReDc^Cyf?!(J+VPhF^AYvrL2`tVbz>6G8hVf#! z09K;et$1gI=^MaW=fl=5_IR<1dCM2w+*)z{}yVm4A=oholIv=hM9ssi^5i zdZ1?7!>Ag(j0As;v=j@5W>-}h=IiP3PCcy7`bhr4I-1*UZIieaT-+dzr9KDOS{TD< zTfVHLX=$U;+TOUlp~YxxY;LJHnw#1?jK*aRhQS}+9gYg;RzJG_<1}zLSUdIqCfu3T z+eotS(;aphQr)ZzrEXzUb0hl^91T~rv^DTIfL+nFtgfY{nY{&+J5IpSx}v42f#rc~ zvv5v@b8+pm1_Zwl&eo=-wTl|5@EG;A&CPYS^_Q~{@D;UfjmwrVWxqu!jNw26H8@7M zDCY0-Xj<*e$8jeju?a3K5;!F-BEb-e<;^Y27O}B*bf&>&M(1KU&FI7}N~5)-jZwds zDRNH&H=IZ}G_^I>ceJ#vnqA61i+E@Dxi_;9`>`vfO;neJf{(z-6?}}a))fn>E-^L= zqMddHXVy`p)I9#-)h4QCHjE zRBtpbTiDXJw6>$EWf?XU8O!R~4^K0qr(j`)NAwIhV##ckr~MT=)( zzp2;gD`DRpo}iRXLbhk*&{v9dF`PvvRai~tZrpXJB3%Ze04HPb!8lFbCvYcJF$kAc zLhdCumEFI-$EV^f*DRccp3i(7nKO!)3eUCV_MC^?E{ekz&f+}pC>;C6xggJV z!rM2>=nnhWG0*kFKOgrYocQtFAnpqg95SdC^lTN-B(O+zqk=KdX9Y54l#%R>bw}{J zT;{n^FrJ3Ee+RMl@#^CF95tsNPmWN8Ncbq85?Lu)uBkYlGLPo7y#?SCfz*&bQQ*CA zT!Uo$tTOL?lVa%Ot+V%cuaZW*-tKS|JH9^AJm;(!jS?{0>oY5Yxc8ZLA!jG~>Y3Wb zoQLx3;pg=>;TIdy_|2mZAdfl;?m~`v1JM+Y+p~PDdH3cfsD&T90waK>tsSd`bJ=pd z8(1T-sg!xRUB+~>+sg0)9kQ!f4%zjqST5NOwH=LY6uIi!(Rlkf*3{lw+fl!mji$TC zwzifwHinZ>FO^3wM5C#8SqICfo8`+cU)FNPGB%cO8XD`-lsB_+bkkJA3OEHEmOA!b?gXdC+NJD!=1of*dS~{m%d4@$XsKI*W{w@QX&mf1juAhL z>iZn2U&wwS9L?<*4;&U`5N2@MLp<^;cu&d(3n_{s@q=arSG6@RWP^jeOh9*yZ7ZON z!9h_rnwuLJA*=8XRGw^cyR^3ba?l9Q9jjU!3BWD$xrYOxgjJvV3 z≫⃒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

    tXN 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 z literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x86/pthreadVC2.dll b/Src/3rd_party/pthread/bin/x86/pthreadVC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..fcb5d9dcc1c18b0be18845a34c3aaf0ae37be200 GIT binary patch literal 55808 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 literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x86/pthreadVSE2.dll b/Src/3rd_party/pthread/bin/x86/pthreadVSE2.dll new file mode 100644 index 0000000000000000000000000000000000000000..8129116fd41891ebbeccaef3422b1636b28f2b21 GIT binary patch literal 57344 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 S9*;{>sx=jtk-z`J82EocyWblC literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/include/pthread.h b/Src/3rd_party/pthread/include/pthread.h new file mode 100644 index 0000000..770f431 --- /dev/null +++ b/Src/3rd_party/pthread/include/pthread.h @@ -0,0 +1,1368 @@ +/* This is an implementation of the threads API of POSIX 1003.1-2001. + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#if !defined( PTHREAD_H ) +#define PTHREAD_H + +/* + * See the README file for an explanation of the pthreads-win32 version + * numbering scheme and how the DLL is named etc. + */ +#define PTW32_VERSION 2,9,1,0 +#define PTW32_VERSION_STRING "2, 9, 1, 0\0" + +/* There are three implementations of cancel cleanup. + * Note that pthread.h is included in both application + * compilation units and also internally for the library. + * The code here and within the library aims to work + * for all reasonable combinations of environments. + * + * The three implementations are: + * + * WIN32 SEH + * C + * C++ + * + * Please note that exiting a push/pop block via + * "return", "exit", "break", or "continue" will + * lead to different behaviour amongst applications + * depending upon whether the library was built + * using SEH, C++, or C. For example, a library built + * with SEH will call the cleanup routine, while both + * C++ and C built versions will not. + */ + +/* + * Define defaults for cleanup code. + * Note: Unless the build explicitly defines one of the following, then + * we default to standard C style cleanup. This style uses setjmp/longjmp + * in the cancelation and thread exit implementations and therefore won't + * do stack unwinding if linked to applications that have it (e.g. + * C++ apps). This is currently consistent with most/all commercial Unix + * POSIX threads implementations. + */ +#if !defined( __CLEANUP_SEH ) && !defined( __CLEANUP_CXX ) && !defined( __CLEANUP_C ) +# define __CLEANUP_C +#endif + +#if defined( __CLEANUP_SEH ) && ( !defined( _MSC_VER ) && !defined(PTW32_RC_MSC)) +#error ERROR [__FILE__, line __LINE__]: SEH is not supported for this compiler. +#endif + +/* + * Stop here if we are being included by the resource compiler. + */ +#if !defined(RC_INVOKED) + +#undef PTW32_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if ( defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112 ) || !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + +#if defined(_UWIN) +# define HAVE_STRUCT_TIMESPEC 1 +# define HAVE_SIGNAL_H 1 +# undef HAVE_PTW32_CONFIG_H +# pragma comment(lib, "pthread") +#endif + +/* + * ------------------------------------------------------------- + * + * + * Module: pthread.h + * + * Purpose: + * Provides an implementation of PThreads based upon the + * standard: + * + * POSIX 1003.1-2001 + * and + * The Single Unix Specification version 3 + * + * (these two are equivalent) + * + * in order to enhance code portability between Windows, + * various commercial Unix implementations, and Linux. + * + * See the ANNOUNCE file for a full list of conforming + * routines and defined constants, and a list of missing + * routines and constants not defined in this implementation. + * + * Authors: + * There have been many contributors to this library. + * The initial implementation was contributed by + * John Bossom, and several others have provided major + * sections or revisions of parts of the implementation. + * Often significant effort has been contributed to + * find and fix important bugs and other problems to + * improve the reliability of the library, which sometimes + * is not reflected in the amount of code which changed as + * result. + * As much as possible, the contributors are acknowledged + * in the ChangeLog file in the source code distribution + * where their changes are noted in detail. + * + * Contributors are listed in the CONTRIBUTORS file. + * + * As usual, all bouquets go to the contributors, and all + * brickbats go to the project maintainer. + * + * Maintainer: + * The code base for this project is coordinated and + * eventually pre-tested, packaged, and made available by + * + * Ross Johnson + * + * QA Testers: + * Ultimately, the library is tested in the real world by + * a host of competent and demanding scientists and + * engineers who report bugs and/or provide solutions + * which are then fixed or incorporated into subsequent + * versions of the library. Each time a bug is fixed, a + * test case is written to prove the fix and ensure + * that later changes to the code don't reintroduce the + * same error. The number of test cases is slowly growing + * and therefore so is the code reliability. + * + * Compliance: + * See the file ANNOUNCE for the list of implemented + * and not-implemented routines and defined options. + * Of course, these are all defined is this file as well. + * + * Web site: + * The source code and other information about this library + * are available from + * + * http://sources.redhat.com/pthreads-win32/ + * + * ------------------------------------------------------------- + */ + +/* Try to avoid including windows.h */ +#if (defined(__MINGW64__) || defined(__MINGW32__)) && defined(__cplusplus) +#define PTW32_INCLUDE_WINDOWS_H +#endif + +#if defined(PTW32_INCLUDE_WINDOWS_H) +#include +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 || defined(__DMC__) +/* + * VC++6.0 or early compiler's header has no DWORD_PTR type. + */ +typedef unsigned long DWORD_PTR; +typedef unsigned long ULONG_PTR; +#endif +/* + * ----------------- + * autoconf switches + * ----------------- + */ + +#if defined(HAVE_PTW32_CONFIG_H) +#include "config.h" +#endif /* HAVE_PTW32_CONFIG_H */ + +#if !defined(NEED_FTIME) +#include +#else /* NEED_FTIME */ +/* use native WIN32 time API */ +#endif /* NEED_FTIME */ + +#if defined(HAVE_SIGNAL_H) +#include +#endif /* HAVE_SIGNAL_H */ + +#include + +/* + * Boolean values to make us independent of system includes. + */ +enum { + PTW32_FALSE = 0, + PTW32_TRUE = (! PTW32_FALSE) +}; + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#if !defined(PTW32_CONFIG_H) +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(__MINGW64__) +# define HAVE_STRUCT_TIMESPEC +# define HAVE_MODE_T +# elif defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#if defined(NEED_ERRNO) +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Several systems don't define some error numbers. + */ +#if !defined(ENOTSUP) +# define ENOTSUP 48 /* This is the value in Solaris. */ +#endif + +#if !defined(ETIMEDOUT) +# define ETIMEDOUT 10060 /* Same as WSAETIMEDOUT */ +#endif + +#if !defined(ENOSYS) +# define ENOSYS 140 /* Semi-arbitrary value */ +#endif + +#if !defined(EDEADLK) +# if defined(EDEADLOCK) +# define EDEADLK EDEADLOCK +# else +# define EDEADLK 36 /* This is the value in MSVC. */ +# endif +#endif + +/* POSIX 2008 - related to robust mutexes */ +#if !defined(EOWNERDEAD) +# define EOWNERDEAD 43 +#endif +#if !defined(ENOTRECOVERABLE) +# define ENOTRECOVERABLE 44 +#endif + +#include "..\..\mysql\include\sched.h" + +/* + * To avoid including windows.h we define only those things that we + * actually need from it. + */ +#if !defined(PTW32_INCLUDE_WINDOWS_H) +#if !defined(HANDLE) +# define PTW32__HANDLE_DEF +# define HANDLE void * +#endif +#if !defined(DWORD) +# define PTW32__DWORD_DEF +# define DWORD unsigned long +#endif +#endif + +#if !defined(HAVE_STRUCT_TIMESPEC) +#define HAVE_STRUCT_TIMESPEC +#if !defined(_TIMESPEC_DEFINED) +#define _TIMESPEC_DEFINED +struct timespec { + time_t tv_sec; + long tv_nsec; +}; +#endif /* _TIMESPEC_DEFINED */ +#endif /* HAVE_STRUCT_TIMESPEC */ + +#if !defined(SIG_BLOCK) +#define SIG_BLOCK 0 +#endif /* SIG_BLOCK */ + +#if !defined(SIG_UNBLOCK) +#define SIG_UNBLOCK 1 +#endif /* SIG_UNBLOCK */ + +#if !defined(SIG_SETMASK) +#define SIG_SETMASK 2 +#endif /* SIG_SETMASK */ + +#if defined(__cplusplus) +extern "C" +{ +#endif /* __cplusplus */ + +/* + * ------------------------------------------------------------- + * + * POSIX 1003.1-2001 Options + * ========================= + * + * Options are normally set in , which is not provided + * with pthreads-win32. + * + * For conformance with the Single Unix Specification (version 3), all of the + * options below are defined, and have a value of either -1 (not supported) + * or 200112L (supported). + * + * These options can neither be left undefined nor have a value of 0, because + * either indicates that sysconf(), which is not implemented, may be used at + * runtime to check the status of the option. + * + * _POSIX_THREADS (== 200112L) + * If == 200112L, you can use threads + * + * _POSIX_THREAD_ATTR_STACKSIZE (== 200112L) + * If == 200112L, you can control the size of a thread's + * stack + * pthread_attr_getstacksize + * pthread_attr_setstacksize + * + * _POSIX_THREAD_ATTR_STACKADDR (== -1) + * If == 200112L, you can allocate and control a thread's + * stack. If not supported, the following functions + * will return ENOSYS, indicating they are not + * supported: + * pthread_attr_getstackaddr + * pthread_attr_setstackaddr + * + * _POSIX_THREAD_PRIORITY_SCHEDULING (== -1) + * If == 200112L, you can use realtime scheduling. + * This option indicates that the behaviour of some + * implemented functions conforms to the additional TPS + * requirements in the standard. E.g. rwlocks favour + * writers over readers when threads have equal priority. + * + * _POSIX_THREAD_PRIO_INHERIT (== -1) + * If == 200112L, you can create priority inheritance + * mutexes. + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PRIO_PROTECT (== -1) + * If == 200112L, you can create priority ceiling mutexes + * Indicates the availability of: + * pthread_mutex_getprioceiling + * pthread_mutex_setprioceiling + * pthread_mutexattr_getprioceiling + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprioceiling + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PROCESS_SHARED (== -1) + * If set, you can create mutexes and condition + * variables that can be shared with another + * process.If set, indicates the availability + * of: + * pthread_mutexattr_getpshared + * pthread_mutexattr_setpshared + * pthread_condattr_getpshared + * pthread_condattr_setpshared + * + * _POSIX_THREAD_SAFE_FUNCTIONS (== 200112L) + * If == 200112L you can use the special *_r library + * functions that provide thread-safe behaviour + * + * _POSIX_READER_WRITER_LOCKS (== 200112L) + * If == 200112L, you can use read/write locks + * + * _POSIX_SPIN_LOCKS (== 200112L) + * If == 200112L, you can use spin locks + * + * _POSIX_BARRIERS (== 200112L) + * If == 200112L, you can use barriers + * + * + These functions provide both 'inherit' and/or + * 'protect' protocol, based upon these macro + * settings. + * + * ------------------------------------------------------------- + */ + +/* + * POSIX Options + */ +#undef _POSIX_THREADS +#define _POSIX_THREADS 200809L + +#undef _POSIX_READER_WRITER_LOCKS +#define _POSIX_READER_WRITER_LOCKS 200809L + +#undef _POSIX_SPIN_LOCKS +#define _POSIX_SPIN_LOCKS 200809L + +#undef _POSIX_BARRIERS +#define _POSIX_BARRIERS 200809L + +#undef _POSIX_THREAD_SAFE_FUNCTIONS +#define _POSIX_THREAD_SAFE_FUNCTIONS 200809L + +#undef _POSIX_THREAD_ATTR_STACKSIZE +#define _POSIX_THREAD_ATTR_STACKSIZE 200809L + +/* + * The following options are not supported + */ +#undef _POSIX_THREAD_ATTR_STACKADDR +#define _POSIX_THREAD_ATTR_STACKADDR -1 + +#undef _POSIX_THREAD_PRIO_INHERIT +#define _POSIX_THREAD_PRIO_INHERIT -1 + +#undef _POSIX_THREAD_PRIO_PROTECT +#define _POSIX_THREAD_PRIO_PROTECT -1 + +/* TPS is not fully supported. */ +#undef _POSIX_THREAD_PRIORITY_SCHEDULING +#define _POSIX_THREAD_PRIORITY_SCHEDULING -1 + +#undef _POSIX_THREAD_PROCESS_SHARED +#define _POSIX_THREAD_PROCESS_SHARED -1 + + +/* + * POSIX 1003.1-2001 Limits + * =========================== + * + * These limits are normally set in , which is not provided with + * pthreads-win32. + * + * PTHREAD_DESTRUCTOR_ITERATIONS + * Maximum number of attempts to destroy + * a thread's thread-specific data on + * termination (must be at least 4) + * + * PTHREAD_KEYS_MAX + * Maximum number of thread-specific data keys + * available per process (must be at least 128) + * + * PTHREAD_STACK_MIN + * Minimum supported stack size for a thread + * + * PTHREAD_THREADS_MAX + * Maximum number of threads supported per + * process (must be at least 64). + * + * SEM_NSEMS_MAX + * The maximum number of semaphores a process can have. + * (must be at least 256) + * + * SEM_VALUE_MAX + * The maximum value a semaphore can have. + * (must be at least 32767) + * + */ +#undef _POSIX_THREAD_DESTRUCTOR_ITERATIONS +#define _POSIX_THREAD_DESTRUCTOR_ITERATIONS 4 + +#undef PTHREAD_DESTRUCTOR_ITERATIONS +#define PTHREAD_DESTRUCTOR_ITERATIONS _POSIX_THREAD_DESTRUCTOR_ITERATIONS + +#undef _POSIX_THREAD_KEYS_MAX +#define _POSIX_THREAD_KEYS_MAX 128 + +#undef PTHREAD_KEYS_MAX +#define PTHREAD_KEYS_MAX _POSIX_THREAD_KEYS_MAX + +#undef PTHREAD_STACK_MIN +#define PTHREAD_STACK_MIN 0 + +#undef _POSIX_THREAD_THREADS_MAX +#define _POSIX_THREAD_THREADS_MAX 64 + + /* Arbitrary value */ +#undef PTHREAD_THREADS_MAX +#define PTHREAD_THREADS_MAX 2019 + +#undef _POSIX_SEM_NSEMS_MAX +#define _POSIX_SEM_NSEMS_MAX 256 + + /* Arbitrary value */ +#undef SEM_NSEMS_MAX +#define SEM_NSEMS_MAX 1024 + +#undef _POSIX_SEM_VALUE_MAX +#define _POSIX_SEM_VALUE_MAX 32767 + +#undef SEM_VALUE_MAX +#define SEM_VALUE_MAX INT_MAX + + +#if defined(__GNUC__) && !defined(__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the library, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the library, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#if !defined(PTW32_STATIC_LIB) +# if defined(PTW32_BUILD) +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * The Open Watcom C/C++ compiler uses a non-standard calling convention + * that passes function args in registers unless __cdecl is explicitly specified + * in exposed function prototypes. + * + * We force all calls to cdecl even though this could slow Watcom code down + * slightly. If you know that the Watcom compiler will be used to build both + * the DLL and application, then you can probably define this as a null string. + * Remember that pthread.h (this file) is used for both the DLL and application builds. + */ +#define PTW32_CDECL __cdecl + +#if defined(_UWIN) && PTW32_LEVEL >= PTW32_LEVEL_MAX +# include +#else +/* + * Generic handle type - intended to extend uniqueness beyond + * that available with a simple pointer. It should scale for either + * IA-32 or IA-64. + */ +typedef struct { + void * p; /* Pointer to actual object */ + unsigned int x; /* Extra information - reuse count etc */ +} ptw32_handle_t; + +typedef ptw32_handle_t pthread_t; +typedef struct pthread_attr_t_ * pthread_attr_t; +typedef struct pthread_once_t_ pthread_once_t; +typedef struct pthread_key_t_ * pthread_key_t; +typedef struct pthread_mutex_t_ * pthread_mutex_t; +typedef struct pthread_mutexattr_t_ * pthread_mutexattr_t; +typedef struct pthread_cond_t_ * pthread_cond_t; +typedef struct pthread_condattr_t_ * pthread_condattr_t; +#endif +typedef struct pthread_rwlock_t_ * pthread_rwlock_t; +typedef struct pthread_rwlockattr_t_ * pthread_rwlockattr_t; +typedef struct pthread_spinlock_t_ * pthread_spinlock_t; +typedef struct pthread_barrier_t_ * pthread_barrier_t; +typedef struct pthread_barrierattr_t_ * pthread_barrierattr_t; + +/* + * ==================== + * ==================== + * POSIX Threads + * ==================== + * ==================== + */ + +enum { +/* + * pthread_attr_{get,set}detachstate + */ + PTHREAD_CREATE_JOINABLE = 0, /* Default */ + PTHREAD_CREATE_DETACHED = 1, + +/* + * pthread_attr_{get,set}inheritsched + */ + PTHREAD_INHERIT_SCHED = 0, + PTHREAD_EXPLICIT_SCHED = 1, /* Default */ + +/* + * pthread_{get,set}scope + */ + PTHREAD_SCOPE_PROCESS = 0, + PTHREAD_SCOPE_SYSTEM = 1, /* Default */ + +/* + * pthread_setcancelstate paramters + */ + PTHREAD_CANCEL_ENABLE = 0, /* Default */ + PTHREAD_CANCEL_DISABLE = 1, + +/* + * pthread_setcanceltype parameters + */ + PTHREAD_CANCEL_ASYNCHRONOUS = 0, + PTHREAD_CANCEL_DEFERRED = 1, /* Default */ + +/* + * pthread_mutexattr_{get,set}pshared + * pthread_condattr_{get,set}pshared + */ + PTHREAD_PROCESS_PRIVATE = 0, + PTHREAD_PROCESS_SHARED = 1, + +/* + * pthread_mutexattr_{get,set}robust + */ + PTHREAD_MUTEX_STALLED = 0, /* Default */ + PTHREAD_MUTEX_ROBUST = 1, + +/* + * pthread_barrier_wait + */ + PTHREAD_BARRIER_SERIAL_THREAD = -1 +}; + +/* + * ==================== + * ==================== + * Cancelation + * ==================== + * ==================== + */ +#define PTHREAD_CANCELED ((void *)(size_t) -1) + + +/* + * ==================== + * ==================== + * Once Key + * ==================== + * ==================== + */ +#define PTHREAD_ONCE_INIT { PTW32_FALSE, 0, 0, 0} + +struct pthread_once_t_ +{ + int done; /* indicates if user function has been executed */ + void * lock; + int reserved1; + int reserved2; +}; + + +/* + * ==================== + * ==================== + * Object initialisers + * ==================== + * ==================== + */ +#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t)(size_t) -1) +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER ((pthread_mutex_t)(size_t) -2) +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ((pthread_mutex_t)(size_t) -3) + +/* + * Compatibility with LinuxThreads + */ +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PTHREAD_RECURSIVE_MUTEX_INITIALIZER +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP PTHREAD_ERRORCHECK_MUTEX_INITIALIZER + +#define PTHREAD_COND_INITIALIZER ((pthread_cond_t)(size_t) -1) + +#define PTHREAD_RWLOCK_INITIALIZER ((pthread_rwlock_t)(size_t) -1) + +#define PTHREAD_SPINLOCK_INITIALIZER ((pthread_spinlock_t)(size_t) -1) + + +/* + * Mutex types. + */ +enum +{ + /* Compatibility with LinuxThreads */ + PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_TIMED_NP = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_ADAPTIVE_NP = PTHREAD_MUTEX_FAST_NP, + /* For compatibility with POSIX */ + PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL +}; + + +typedef struct ptw32_cleanup_t ptw32_cleanup_t; + +#if defined(_MSC_VER) +/* Disable MSVC 'anachronism used' warning */ +#pragma warning( disable : 4229 ) +#endif + +typedef void (* PTW32_CDECL ptw32_cleanup_callback_t)(void *); + +#if defined(_MSC_VER) +#pragma warning( default : 4229 ) +#endif + +struct ptw32_cleanup_t +{ + ptw32_cleanup_callback_t routine; + void *arg; + struct ptw32_cleanup_t *prev; +}; + +#if defined(__CLEANUP_SEH) + /* + * WIN32 SEH version of cancel cleanup. + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + _cleanup.routine = (ptw32_cleanup_callback_t)(_rout); \ + _cleanup.arg = (_arg); \ + __try \ + { \ + +#define pthread_cleanup_pop( _execute ) \ + } \ + __finally \ + { \ + if( _execute || AbnormalTermination()) \ + { \ + (*(_cleanup.routine))( _cleanup.arg ); \ + } \ + } \ + } + +#else /* __CLEANUP_SEH */ + +#if defined(__CLEANUP_C) + + /* + * C implementation of PThreads cancel cleanup + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + ptw32_push_cleanup( &_cleanup, (ptw32_cleanup_callback_t) (_rout), (_arg) ); \ + +#define pthread_cleanup_pop( _execute ) \ + (void) ptw32_pop_cleanup( _execute ); \ + } + +#else /* __CLEANUP_C */ + +#if defined(__CLEANUP_CXX) + + /* + * C++ version of cancel cleanup. + * - John E. Bossom. + */ + + class PThreadCleanup { + /* + * PThreadCleanup + * + * Purpose + * This class is a C++ helper class that is + * used to implement pthread_cleanup_push/ + * pthread_cleanup_pop. + * The destructor of this class automatically + * pops the pushed cleanup routine regardless + * of how the code exits the scope + * (i.e. such as by an exception) + */ + ptw32_cleanup_callback_t cleanUpRout; + void * obj; + int executeIt; + + public: + PThreadCleanup() : + cleanUpRout( 0 ), + obj( 0 ), + executeIt( 0 ) + /* + * No cleanup performed + */ + { + } + + PThreadCleanup( + ptw32_cleanup_callback_t routine, + void * arg ) : + cleanUpRout( routine ), + obj( arg ), + executeIt( 1 ) + /* + * Registers a cleanup routine for 'arg' + */ + { + } + + ~PThreadCleanup() + { + if ( executeIt && ((void *) cleanUpRout != (void *) 0) ) + { + (void) (*cleanUpRout)( obj ); + } + } + + void execute( int exec ) + { + executeIt = exec; + } + }; + + /* + * C++ implementation of PThreads cancel cleanup; + * This implementation takes advantage of a helper + * class who's destructor automatically calls the + * cleanup routine if we exit our scope weirdly + */ +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + PThreadCleanup cleanup((ptw32_cleanup_callback_t)(_rout), \ + (void *) (_arg) ); + +#define pthread_cleanup_pop( _execute ) \ + cleanup.execute( _execute ); \ + } + +#else + +#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined. + +#endif /* __CLEANUP_CXX */ + +#endif /* __CLEANUP_C */ + +#endif /* __CLEANUP_SEH */ + +/* + * =============== + * =============== + * Methods + * =============== + * =============== + */ + +/* + * PThread Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_attr_init (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_destroy (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getdetachstate (const pthread_attr_t * attr, + int *detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstackaddr (const pthread_attr_t * attr, + void **stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstacksize (const pthread_attr_t * attr, + size_t * stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setdetachstate (pthread_attr_t * attr, + int detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstackaddr (pthread_attr_t * attr, + void *stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstacksize (pthread_attr_t * attr, + size_t stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedparam (const pthread_attr_t *attr, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedparam (pthread_attr_t *attr, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedpolicy (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedpolicy (const pthread_attr_t *, + int *); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setinheritsched(pthread_attr_t * attr, + int inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getinheritsched(const pthread_attr_t * attr, + int * inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setscope (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getscope (const pthread_attr_t *, + int *); + +/* + * PThread Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid, + const pthread_attr_t * attr, + void *(PTW32_CDECL *start) (void *), + void *arg); + +PTW32_DLLPORT int PTW32_CDECL pthread_detach (pthread_t tid); + +PTW32_DLLPORT int PTW32_CDECL pthread_equal (pthread_t t1, + pthread_t t2); + +PTW32_DLLPORT void PTW32_CDECL pthread_exit (void *value_ptr); + +PTW32_DLLPORT int PTW32_CDECL pthread_join (pthread_t thread, + void **value_ptr); + +PTW32_DLLPORT pthread_t PTW32_CDECL pthread_self (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_cancel (pthread_t thread); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcancelstate (int state, + int *oldstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcanceltype (int type, + int *oldtype); + +PTW32_DLLPORT void PTW32_CDECL pthread_testcancel (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_once (pthread_once_t * once_control, + void (PTW32_CDECL *init_routine) (void)); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +PTW32_DLLPORT ptw32_cleanup_t * PTW32_CDECL ptw32_pop_cleanup (int execute); + +PTW32_DLLPORT void PTW32_CDECL ptw32_push_cleanup (ptw32_cleanup_t * cleanup, + ptw32_cleanup_callback_t routine, + void *arg); +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread Specific Data Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_key_create (pthread_key_t * key, + void (PTW32_CDECL *destructor) (void *)); + +PTW32_DLLPORT int PTW32_CDECL pthread_key_delete (pthread_key_t key); + +PTW32_DLLPORT int PTW32_CDECL pthread_setspecific (pthread_key_t key, + const void *value); + +PTW32_DLLPORT void * PTW32_CDECL pthread_getspecific (pthread_key_t key); + + +/* + * Mutex Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_init (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_destroy (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getpshared (const pthread_mutexattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setpshared (pthread_mutexattr_t * attr, + int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_settype (pthread_mutexattr_t * attr, int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_gettype (const pthread_mutexattr_t * attr, int *kind); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setrobust( + pthread_mutexattr_t *attr, + int robust); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getrobust( + const pthread_mutexattr_t * attr, + int * robust); + +/* + * Barrier Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_init (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_destroy (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_getpshared (const pthread_barrierattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_setpshared (pthread_barrierattr_t * attr, + int pshared); + +/* + * Mutex Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_init (pthread_mutex_t * mutex, + const pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_destroy (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_lock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_timedlock(pthread_mutex_t * mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_trylock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_unlock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_consistent (pthread_mutex_t * mutex); + +/* + * Spinlock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_spin_init (pthread_spinlock_t * lock, int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_destroy (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_lock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_trylock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_unlock (pthread_spinlock_t * lock); + +/* + * Barrier Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_init (pthread_barrier_t * barrier, + const pthread_barrierattr_t * attr, + unsigned int count); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_destroy (pthread_barrier_t * barrier); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_wait (pthread_barrier_t * barrier); + +/* + * Condition Variable Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_init (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_destroy (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_getpshared (const pthread_condattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_setpshared (pthread_condattr_t * attr, + int pshared); + +/* + * Condition Variable Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_cond_init (pthread_cond_t * cond, + const pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_destroy (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_wait (pthread_cond_t * cond, + pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_timedwait (pthread_cond_t * cond, + pthread_mutex_t * mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_signal (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_broadcast (pthread_cond_t * cond); + +/* + * Scheduling + */ +PTW32_DLLPORT int PTW32_CDECL pthread_setschedparam (pthread_t thread, + int policy, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_getschedparam (pthread_t thread, + int *policy, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_setconcurrency (int); + +PTW32_DLLPORT int PTW32_CDECL pthread_getconcurrency (void); + +/* + * Read-Write Lock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_init(pthread_rwlock_t *lock, + const pthread_rwlockattr_t *attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_destroy(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_tryrdlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_trywrlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_rdlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedrdlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_wrlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedwrlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_unlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_init (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr, + int pshared); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 + +/* + * Signal Functions. Should be defined in but MSVC and MinGW32 + * already have signal.h that don't define these. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_kill(pthread_t thread, int sig); + +/* + * Non-portable functions + */ + +/* + * Compatibility with Linux. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setkind_np(pthread_mutexattr_t * attr, + int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getkind_np(pthread_mutexattr_t * attr, + int *kind); + +/* + * Possibly supported by other POSIX threads implementations + */ +PTW32_DLLPORT int PTW32_CDECL pthread_delay_np (struct timespec * interval); +PTW32_DLLPORT int PTW32_CDECL pthread_num_processors_np(void); +PTW32_DLLPORT unsigned __int64 PTW32_CDECL pthread_getunique_np(pthread_t thread); + +/* + * Useful if an application wants to statically link + * the lib rather than load the DLL at run-time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_detach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_detach_np(void); + +/* + * Features that are auto-detected at load/run time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_test_features_np(int); +enum ptw32_features { + PTW32_SYSTEM_INTERLOCKED_COMPARE_EXCHANGE = 0x0001, /* System provides it. */ + PTW32_ALERTABLE_ASYNC_CANCEL = 0x0002 /* Can cancel blocked threads. */ +}; + +/* + * Register a system time change with the library. + * Causes the library to perform various functions + * in response to the change. Should be called whenever + * the application's top level window receives a + * WM_TIMECHANGE message. It can be passed directly to + * pthread_create() as a new thread if desired. + */ +PTW32_DLLPORT void * PTW32_CDECL pthread_timechange_handler_np(void *); + +#endif /*PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* + * Returns the Win32 HANDLE for the POSIX thread. + */ +PTW32_DLLPORT HANDLE PTW32_CDECL pthread_getw32threadhandle_np(pthread_t thread); +/* + * Returns the win32 thread ID for POSIX thread. + */ +PTW32_DLLPORT DWORD PTW32_CDECL pthread_getw32threadid_np (pthread_t thread); + + +/* + * Protected Methods + * + * This function blocks until the given WIN32 handle + * is signaled or pthread_cancel had been called. + * This function allows the caller to hook into the + * PThreads cancel mechanism. It is implemented using + * + * WaitForMultipleObjects + * + * on 'waitHandle' and a manually reset WIN32 Event + * used to implement pthread_cancel. The 'timeout' + * argument to TimedWait is simply passed to + * WaitForMultipleObjects. + */ +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableWait (HANDLE waitHandle); +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableTimedWait (HANDLE waitHandle, + DWORD timeout); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread-Safe C Runtime Library Mappings. + */ +#if !defined(_UWIN) +# if defined(NEED_ERRNO) + PTW32_DLLPORT int * PTW32_CDECL _errno( void ); +# else +# if !defined(errno) +# if (defined(_MT) || defined(_DLL)) + __declspec(dllimport) extern int * __cdecl _errno(void); +# define errno (*_errno()) +# endif +# endif +# endif +#endif + +/* + * Some compiler environments don't define some things. + */ +#if defined(__BORLANDC__) +# define _ftime ftime +# define _timeb timeb +#endif + +#if defined(__cplusplus) + +/* + * Internal exceptions + */ +class ptw32_exception {}; +class ptw32_exception_cancel : public ptw32_exception {}; +class ptw32_exception_exit : public ptw32_exception {}; + +#endif + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* FIXME: This is only required if the library was built using SEH */ +/* + * Get internal SEH tag + */ +PTW32_DLLPORT DWORD PTW32_CDECL ptw32_get_exception_services_code(void); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#if !defined(PTW32_BUILD) + +#if defined(__CLEANUP_SEH) + +/* + * Redefine the SEH __except keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#define __except( E ) \ + __except( ( GetExceptionCode() == ptw32_get_exception_services_code() ) \ + ? EXCEPTION_CONTINUE_SEARCH : ( E ) ) + +#endif /* __CLEANUP_SEH */ + +#if defined(__CLEANUP_CXX) + +/* + * Redefine the C++ catch keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#if defined(_MSC_VER) + /* + * WARNING: Replace any 'catch( ... )' with 'PtW32CatchAll' + * if you want Pthread-Win32 cancelation and pthread_exit to work. + */ + +#if !defined(PtW32NoCatchWarn) + +#pragma message("Specify \"/DPtW32NoCatchWarn\" compiler flag to skip this message.") +#pragma message("------------------------------------------------------------------") +#pragma message("When compiling applications with MSVC++ and C++ exception handling:") +#pragma message(" Replace any 'catch( ... )' in routines called from POSIX threads") +#pragma message(" with 'PtW32CatchAll' or 'CATCHALL' if you want POSIX thread") +#pragma message(" cancelation and pthread_exit to work. For example:") +#pragma message("") +#pragma message(" #if defined(PtW32CatchAll)") +#pragma message(" PtW32CatchAll") +#pragma message(" #else") +#pragma message(" catch(...)") +#pragma message(" #endif") +#pragma message(" {") +#pragma message(" /* Catchall block processing */") +#pragma message(" }") +#pragma message("------------------------------------------------------------------") + +#endif + +#define PtW32CatchAll \ + catch( ptw32_exception & ) { throw; } \ + catch( ... ) + +#else /* _MSC_VER */ + +#define catch( E ) \ + catch( ptw32_exception & ) { throw; } \ + catch( E ) + +#endif /* _MSC_VER */ + +#endif /* __CLEANUP_CXX */ + +#endif /* ! PTW32_BUILD */ + +#if defined(__cplusplus) +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#if defined(PTW32__HANDLE_DEF) +# undef HANDLE +#endif +#if defined(PTW32__DWORD_DEF) +# undef DWORD +#endif + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* ! RC_INVOKED */ + +#endif /* PTHREAD_H */ diff --git a/Src/3rd_party/pthread/lib/x64/pthreadVC2.lib b/Src/3rd_party/pthread/lib/x64/pthreadVC2.lib new file mode 100644 index 0000000000000000000000000000000000000000..1b07e0e9aa43b91f04d2884e2f594bbeca4fdac1 GIT binary patch literal 29738 zcmd^HNsL@qx&Grg2^bTMGvL^X=_HQhIF85ej_ue^d!DCudmgH*tFGN;S67v*sy%?j zV}Y`WA{GcC6a~a0i??9&B#T9a5JW5yvJgT~5sNIMEFy%EKv5JS@xC+Nd(OH4IptgK z_U5(p`_}!3^PT^U{~4~I-%@T@Cb!L7GvNNM+puBd+D#ia4zG9lYxV2v>(=X^uE)MKOz#e=VL{CFW?=tZ&cAC;0QYSxuQc2yn_xSUxLP#Dmn^$LDf$c9h*ZW=;SR$ z=av%*I{h0(XYLRQI=@KKg#|={&i+!-`5%K1I=fcU*xSGXoqJEwnT@aq9si4>(J>-H z$0rnBfUcmc|I%~=Pte3q6_tNMBxqu>q6)AD?L=7%dh@QPQ+R@Qysu~*%1Y3&TZ*P2 zlk^8XL5(|_cHjwW!e>F(Q3ryWKT^~=LnP?(2Z~N?CXxjIPk@g7T~Y0GB0<;Migpel zf1o3<6IA)3qU29Rf=+&>=oIQu(CGt;PW>70paeWY?Y}9y4!;Do=PPPW5(&DpO3~y( zB0*Pvu4qpk?F3YBYx)FF(1CXq?cYWu=~~KLkClsJs&G3sged zr30nX{=>(Pj*gXf?LM({bpNrjqobu(ce0(7tEYCZr&8(2$wP2%x8+pe-uK4_7gwHKpDt$#>dnwV9gkCb!%H%VIIQ`O1^}89QWSYRQyhW@aQIORJLd#+EQ-Z&g#?-V#Ra zySJ_JAj>(7r6gpD%mplsBxH${+s%pTPBxD?s9F}XM(RQ9OvYGa(ra{Too>?bfNcR| zC#xTMzE*2gOO00A_MJ={VaOgiGb;IdvV^WZ1enCtL^xS)RO^YI0bUwTq2^@YwpNmy z<#eNVecFf2n67?fdaBfFHxXTQn(dCA&%DI7yjg1?Qpu)!gaEdAK+L9rkfU@tDR-ya z2y(2t(rNf&H;wkqdJ`c)F3R!wK`YbQO$}RyMijI~b@-ExWo}7>DOkOo$$%wwQ!vfD zQ<+Svt!BMu4auC0u}1EK)}Ab8U2d1B(%vPZQ)4M&Y~fX{F`2Y$Dg&;yFJ$Zi5VrS) zuDzQ#Q{paHS;(@Hk$9_JYa*|=OH<`r6kvyKY7N!~Y}*E2%ao_t?kvH=r<5%jBS0nJLFktJZL*?u_GV zUQ2G5Dp)UBO9qRT*R3Y?#M9L*S6387E2-2j*D7hVvU#qy8esJJS5oVk`~Z}fbE)W& zFqBA6?55%N*?gZejiJT#DFvCjt6$ypJ4yX=sv%!|dA{5PrYTETTTY@SG47^mS}abt zTboL%j6ufdtkB5v7@EoHciXoa1H*MK+!9S&uo)Dsau!_#Y1}Mh4Q>Bc=?PA&iLfaB z;qpsU)AeqxRaZSqXl9&D6K(WHxjwCw(ibnsqIWs|$=)S7jHXw3QkN||DKUkV7K8vZ zOgDACG%LvhG5QYE=!?CKR*Gb!t}{*1E;! zG1?RDX1Q7^TOq78$k?8ZJ3i^*%GJS&C|j1x7gw&jg^43fVYyt_LiC6V!p18}Nd=E> zdqm=;ZVD;_DS7qpa+QT>vT|!E{gLrao`uN#c5!68F(Tud!*umc6LB`9944>tLl_go zcdcZVIuIF$(KLye{7YrSboF%!I%(THR@)?H($@xt?b={Ws+7^9>exuKW;>4^uyNLP zsR^?h>>{96FzM)RvE0->J^kwHB)6>1Mfu{$cS?`S6ZPb@7(sPGc-(-+n3y(rvrOdJ zqOI1;x9u*IGF&}(?#KMgE?~^upUK@+bal4fxy|LW7NR3pc{5wY+qQ29EK@wZfYzELBRy&jL53XMx`(qLmwQ`mmYk-8YE-agFF#+lfBgiW3T)w5-@c z^x+86JG*eA0sf=AaYBdpr}6&LUZRh2nsNcp(S5KvMD+PVoJ`?4ewb+K80?P{eOe`& zbByTLNuuRA0r|~oqC01Z7M&+raDnKTXNi7{-)r%F8#eDjXCvf)IgV4gQJkRR`8M?a zb(QD_=%*7zzbF$e#&bah`QAx%_f4EYf!;@cKSI850q@il(H}r}8aQ18EpNhy>qI}o z^Gt{6gUdJpJpp{s-=Y6`4W};fe*o!yaD?cGNc&F-{6fAjoWcna^8IH#FM#(qoKU@w zwC3YE*&TcX2|qZ9mapQ66(Iq8yOc7tp&ij@A_4}X|(7yxyPodiaeTh7@P=0U0<}S+kCn(P@>gG$F79BtyZy?X-E{ z-4Eg0_mHnYqU?Tv`gk0)66JLO@_!Q1Lo|mPG)0fnV>Cn?Xgz(MzDi%CN9ZdwNV{nr zJx=pzK2>OfPSOJUCVhhz(lt6u303I~-KJagBt1n>(9?8|nzV-&QJogkFzujM=w(_# zH)({n)3<0DU7#hjpAOI=I!Mpaa#~6+()08zy+q%p7ifT1(s{ZATdSE?uJU z&}v#kuhClCOOsTiXXr5PqcXioqclb*=qg>L7VV@}^eXM5xpaz7)9bX6Hq$10gSOH* z9j6j)p`&z+ZqO0hM$`ZA{Cybf`TkA?ov|yM7st}MFQ9N*Os9@a9rp3Dh1r@>%R?1l zFveJWID0F+irP4`)<|X?H7H6!?MsD{5GPszS$e7$k%!LNWPHZhAtdVIJ2ZOv4wbm? zsY1|qsS)s9$YQ=5IpcdgDmGH4aE!>B=S^*Dk*rj{RoNP7I%YG}Eq+@moB@df2l+JE z$Wz7)!R4D+Ao97Ec1%trU#}@$7o;Qx!T@dCJRjun$ z80N&vwTLbIm5ixpC15srlt8x0uvBk|art2Pf@GqRHk-*&59Vx)NnIuZ6}~e&sq{WM zWY(}*3ZWu1eWO<@WYlCG9GJ*UKP6aVJ~fZDnu7Y z_qw`d@!964^A6A zM)upJi1AwTnIS{+`7K{5VrwwCb0h|){641JG(vkYF)cMyi47i3OJv76OJ!G1{h%+9g?%y2Mx8kN zX(~4vU!+;i7s2IyF%riYvm`Ms(4ZT>)ilHx0uC%y~1Eyx#^K z%h(M+84cx=%)2eKfz}qz2GbO?Y(N;y24qe)^6|OJpp4ATiw;u#dedyj1I-JZ&h$9) z37k8mQMZ?$?9j#&c-5}+#HP-+24!HHgJ0v8lyTkEo^R#dKZzf6I+cV<#nN}#5D8{C)J(lP(W!Wmbt`_zh*bP=F6m@Z zs1Rk7s(iEV`skQbu;mmR`+`T8FTMa;U9H#G4D|X!aA@tY{#I~&UiGbqY5D*B?&!jY z2xmg-Z*?`9n7*-5^=I=e{3tLlgQ&7GM|>RFx?G*J?Ez3{$k4|28lBDO$~JwT64C-{+8z;Ky|H+!oWjzpdY7 zKGn_o+s8mqwau4Yq`wX6LX!Q}bO^&6p%K&H1`SX2>;E+``YH5gqlE9JbO(@!nQWQa zm(b%F7et?}z9q)AmD1O_W+4z+sq3NBXx{rs`nv8C11|DQKz;}#jczF!vb_;tKnu60 zAv1h?sLOylCqrd78VqRBHau)PzlZrMFdz0Y5wA0Ae*<0E-{C>Nlve?JE`!bOEg0~^ zew(iA`7PSkBMf#vHtZWYQsR#Wk)53p16{bpha&CzF&5j}dFdNv{M*&qub`4SgfVdh*wNL`1_6swdB9upd-U{yKyGV0!Z7 zLFC!*$=_hHXS*ja;7}h(PyS{Id1iX@CVupK89jNSg>1Hs%?i?;k*N4s9f<~>U~suX z#LYwRh&C31Jjr0%yToSTDvB8!x1P#layv{0x^Ni}2g-Og>h|dXc7D^+z!&!1{MSC- z9I*oE-wL45L@md>UKGH^gyC($_3QOe!0V;(`WXw|J^tt$Z6n`%mji9FjmDm?^o<&5 zS0a*M;-dxzjH0b67I8f5V{*-?aPJu!;>%X-d_3nPx@S+OV2fi$>T{`&nQg>{&|b38{DarN@gm+WckmDp8^SWg!Dwylf+L%f>ve`@eAXj~W^0H|0>>I~fMFXr0DJ+RZ*@ zERZi;#?3F51@_h3}Mem8;gx);wx@FHkN|d zAp36Ju_$M^g=}B5G+;$X>{w6QV`1rQkb!WcFlt~uAokMs=25xMTX?jJ#Z~+AnC>N1 zQ@n*yW4Ai?=TT+Q!9y1<;~4UR0J3|_)i_-kIj|EVcJmH0$o>`G*}y%-;?9h}v6gi> zkIRQzg8wb*+~v{Mq-PYuctiJ{Fgnm;p=G^^#Sd1 zp7Bs0#2)8a8&mu@jWZR~L{81l`Is{@HpI?A&-;kn21?=4KXzl{f{*BZ`_Xr+4UGFD zR=ntA&eYr*Ska5U)Jqm-{?nCyy^M|XB^#0bF5I0@_*bS9R9zF13(rlntL+HVxFYJ3D zu3CqZPb7Np&#LaP?qj3q_9dN``2E7UmYK*?Z7c-n)gx;*Cil%IQA; z)!8hf)&i*6|9jbQdybteT(wc9)A9qk`}H$YpVv}Egyl1aI2ahOm!Q|wQ$+Q}pXXfR zv3W2s|2_k(DS?HL`_vbO_l*?ss-ckr!CG;z0z1g!Mnh9$Vfm>3zYfna_qBKL8w|9M zMZjulEG{6NNu28%M}E)gCR}(liTTskIP!DR`^1Tj%pD6S`x?~)EZ!;tvyr$})76N< rYj(xaBJF+Jpy{t^@7uFLgtHi)-OyP2Yh4e@eZ9Zdep4fHmj?e2Ow{yk literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/lib/x86/pthreadVC2.lib b/Src/3rd_party/pthread/lib/x86/pthreadVC2.lib new file mode 100644 index 0000000000000000000000000000000000000000..c20ee200db381086a324bd98acc2918b20b6a4c2 GIT binary patch literal 30334 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 literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/lib/x86/pthreadVSE2.lib b/Src/3rd_party/pthread/lib/x86/pthreadVSE2.lib new file mode 100644 index 0000000000000000000000000000000000000000..3f3335d46981ea9ed40ad3e648a05c210ee77014 GIT binary patch literal 30460 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 literal 0 HcmV?d00001 diff --git a/Src/Char Server/charclient.cpp b/Src/Char Server/charclient.cpp new file mode 100644 index 0000000..5420141 --- /dev/null +++ b/Src/Char Server/charclient.cpp @@ -0,0 +1,46 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +// Constructor +CCharClient::CCharClient( ) +{ + userid = 0; + platinum = false; + memset( &username, '\0', 17 ); + memset( &password, '\0', 33 ); + memset( &charname, '\0', 17 ); + accesslevel = 0; + channel = 0; + clanid = 0; + clan_rank = 0; + level = 0; + job = 0; + charid = 0; + isLoggedIn = false; + logout = false; +} + +// Deconstructor +CCharClient::~CCharClient( ) +{ + +} diff --git a/Src/Char Server/charpackets.cpp b/Src/Char Server/charpackets.cpp new file mode 100644 index 0000000..d77108d --- /dev/null +++ b/Src/Char Server/charpackets.cpp @@ -0,0 +1,579 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +// Do player identification +bool CCharServer::pakDoIdentify( CCharClient* thisclient, CPacket* P ) +{ + if (thisclient->isLoggedIn) return false; + MYSQL_RES *result; + MYSQL_ROW row; + thisclient->userid = GETDWORD((*P), 0x00); + memcpy( thisclient->password, &P->Buffer[4], 32 ); + result = DB->QStore("SELECT username,lastsvr,accesslevel,platinum FROM accounts WHERE id=%i AND password='%s'", thisclient->userid, thisclient->password); + if(result==NULL) return false; + if (mysql_num_rows( result ) != 1) + { + Log( MSG_HACK, "Someone tried to connect to char server with an invalid account" ); + DB->QFree( ); + return false; + } + else + { + row = mysql_fetch_row(result); + strncpy_s (thisclient->username, row[0], 16); + thisclient->channel = atoi(row[1]); + thisclient->accesslevel = atoi(row[2]); + thisclient->platinum = atoi(row[3]); + DB->QFree( ); + } + + Log( MSG_INFO,"User '%s'(#%i) logged in", thisclient->username, thisclient->userid ); + + BEGINPACKET( pak, 0x070c ); + ADDBYTE ( pak, 0 ); + ADDDWORD ( pak, 0x87654321); + ADDDWORD ( pak, 0x00000000 ); + thisclient->SendPacket( &pak ); + result = DB->QStore( "SELECT online FROM accounts WHERE username='%s'", thisclient->username ); + if(result==NULL) return false; + row = mysql_fetch_row(result); + bool online = atoi(row[0]); + DB->QFree( ); + if(online) + return false; + if(!DB->QExecute( "UPDATE accounts SET online=1 WHERE username='%s'", thisclient->username )) + return false; + thisclient->isLoggedIn = true; + thisclient->returnedfromWS=false; + return true; +} + +// Get this clients character list +bool CCharServer::pakGetCharacters( CCharClient* thisclient, CPacket* P ) +{ + if (!thisclient->isLoggedIn) return false; + // Properly delete items, quests, friends when deleting a character + // Hooray for joins. + MYSQL_RES *result; + MYSQL_ROW row; + DB->QExecute( "DELETE FROM characters, items, list_friend, list_quest USING characters LEFT JOIN items ON items.owner = characters.id LEFT JOIN list_friend \ + ON (list_friend.id = characters.id OR list_friend.idfriend = characters.id) LEFT JOIN list_quest ON list_quest.owner = characters.id WHERE (characters.deletetime > 0 AND characters.deletetime <= %i)", GetServerTime( ) ); + + CItem items[10]; + unsigned int charnum=0; + CCharacter chars[5]; + // 0 1 2 3 4 5 6 7 + result = DB->QStore("SELECT char_name,level,face,hairStyle,sex,classid,id,deletetime FROM characters WHERE account_name='%s'", thisclient->username); + if(result==NULL) return false; + while (row = mysql_fetch_row(result)) + { + strcpy( chars[charnum].char_name , row[0] ); + chars[charnum].level = atoi(row[1]); + chars[charnum].face = atoi(row[2]); + chars[charnum].hairStyle = atoi(row[3]); + chars[charnum].sex = atoi(row[4]); + chars[charnum].classid = atoi(row[5]); + chars[charnum].id = atoi(row[6]); + chars[charnum].DeleteTime = atoi(row[7]); + if(chars[charnum].DeleteTime > 0) + { + chars[charnum].DeleteTime = chars[charnum].DeleteTime - GetServerTime( ); + } + charnum++; + } + DB->QFree( ); + BEGINPACKET( pak, 0x0712 ); + ADDBYTE ( pak, charnum ); + for (unsigned k=0;kQStore("SELECT itemnum,itemtype,refine,durability,lifespan,slotnum FROM items WHERE owner=%i AND slotnum<10", chars[k].id); + result = DB->QStore("SELECT itemid, itemtype, refine, durability, lifespan FROM charitems WHERE owner=%i", chars[k].id); + + if(result == NULL) return false; + char *tmpid; + char *itmp; + while( row = mysql_fetch_row(result) ) + { + tmpid = strtok( row[0] , "|"); + for(unsigned int i=1;i<10; i++) + { + if((tmpid = strtok( NULL , "|"))!= NULL) + { + items[i].itemnum = atoi(tmpid); + } + } + tmpid = strtok( row[1] , "|"); + for(unsigned int i=1;i<10; i++) + { + if((tmpid = strtok( NULL , "|"))!= NULL) + { + items[i].itemtype = atoi(tmpid); + } + } + tmpid = strtok( row[2] , "|"); + for(unsigned int i=1;i<10; i++) + { + if((tmpid = strtok( NULL , "|"))!= NULL) + { + items[i].refine = atoi(tmpid); + } + } + tmpid = strtok( row[3] , "|"); + for(unsigned int i=1;i<10; i++) + { + if((tmpid = strtok( NULL , "|"))!= NULL) + { + items[i].durability = atoi(tmpid); + } + } + tmpid = strtok( row[4] , "|"); + for(unsigned int i=1;i<10; i++) + { + if((tmpid = strtok( NULL , "|"))!= NULL) + { + items[i].lifespan = atoi(tmpid); + } + } + } + /* + while(row = mysql_fetch_row(result)) + { + unsigned itemnum = atoi(row[5]); + items[itemnum].itemnum = atoi(row[0]); + items[itemnum].itemtype = atoi(row[1]); + items[itemnum].refine = atoi(row[2]); + items[itemnum].durability = atoi(row[3]); + items[itemnum].lifespan = atoi(row[4]); + }*/ + DB->QFree( ); + + ADDSTRING ( pak, chars[k].char_name ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, chars[k].sex ); // SEX + ADDWORD ( pak, chars[k].level ); // LEVEL + ADDWORD ( pak, chars[k].classid ); // CLASSID + ADDDWORD ( pak, chars[k].DeleteTime ); // DELETE TIME + ADDBYTE ( pak, 0x00 );//thisclient->platinum?0x01:0x00 ); // IS PLATINUM? 00-NO;01-YES;02-YES BUT USER IS NOT + ADDDWORD ( pak, chars[k].face ); // FACE + ADDDWORD ( pak, chars[k].hairStyle ); // HAIR + ADDWORD ( pak, items[2].itemnum ); // CAP + ADDWORD ( pak, items[2].refine ); // CAP REFINE + ADDWORD ( pak, items[3].itemnum ); // BODY + ADDWORD ( pak, items[3].refine ); // BODY REFINE + ADDWORD ( pak, items[5].itemnum ); // GLOVES + ADDWORD ( pak, items[5].refine ); // GLOVES REFINE + ADDWORD ( pak, items[6].itemnum ); // BOOTS + ADDWORD ( pak, items[6].refine ); // BOOTS REFINE + ADDWORD ( pak, items[1].itemnum ); // FACE + ADDWORD ( pak, items[1].refine ); // FACE REFINE + ADDWORD ( pak, items[4].itemnum ); // BACK + ADDWORD ( pak, items[4].refine ); // BACK REFINE + ADDWORD ( pak, items[7].itemnum ); // WEAPON + ADDWORD ( pak, items[7].refine ); // WEAPON REFINE + ADDWORD ( pak, items[8].itemnum ); // SUBWEAPON + ADDWORD ( pak, items[8].refine ); // SUBWEAPON REFINE + } + thisclient->SendPacket( &pak ); + return true; +} + +// Send away the world IP +bool CCharServer::pakRequestWorld( CCharClient* thisclient, CPacket* P ) +{ + if (!thisclient->isLoggedIn) return false; + MYSQL_ROW row; + MYSQL_RES *result; + memcpy( thisclient->charname, &P->Buffer[3], P->Size-6-4 ); + thisclient->charname[P->Size-6-4] = 0; + Log( MSG_INFO,"User %s(%i) selected char '%s'", thisclient->username, thisclient->userid, thisclient->charname); + if(!DB->QExecute("UPDATE accounts SET lastchar='%s' WHERE id=%i", thisclient->charname, thisclient->userid)) + return false; + result = DB->QStore("SELECT host,port FROM channels WHERE id=%i", thisclient->channel); + if(result==NULL) return false; + if(mysql_num_rows( result )!=1 ) + { + Log( MSG_WARNING, "Invalid Server: %i", thisclient->channel ); + DB->QFree( ); + return false; + } + row = mysql_fetch_row( result ); + BEGINPACKET( pak, 0x711 ); + ADDWORD ( pak, atoi(row[1]) ); // PORT + ADDDWORD ( pak, thisclient->userid ); + ADDDWORD ( pak, 0x87654321 ); + ADDSTRING ( pak, row[0] );//IP + ADDBYTE ( pak, 0 ); + thisclient->SendPacket( &pak ); + DB->QFree( ); +//CHAR INFORMATION 0 1 2 3 4 + result = DB->QStore("SELECT clanid,clan_rank,id,level,classid FROM characters WHERE char_name='%s'", thisclient->charname); + if(result==NULL) return false; + if(mysql_num_rows(result)!=1) + { + Log( MSG_WARNING, "Number of user with charname '%s' is %i", thisclient->charname,mysql_num_rows(result)); + DB->QFree( ); + return false; + } + row = mysql_fetch_row( result ); + thisclient->clanid = atoi( row[0] ); + thisclient->clan_rank = atoi( row[1] ); + thisclient->charid = atoi( row[2] ); + thisclient->level = atoi( row[3] ); + thisclient->job = atoi( row[4] ); + DB->QFree( ); +//MESSENGER INFORMATION +// // 0 1 + result = DB->QStore("SELECT idfriend,namefriend FROM list_friend WHERE id=%i", thisclient->charid); + if(result==NULL) return false; + BYTE nfriends = mysql_num_rows(result) & 0xff; + + RESETPACKET( pak, 0x7e1 );// Friend list command + ADDBYTE ( pak, 0x06 );// Sending Friend List + ADDBYTE ( pak, nfriends );// # friends + while(row = mysql_fetch_row( result )) + { + CFriendList * newfriend = new CFriendList; + assert(newfriend); + newfriend->id = atoi(row[0]); //friendid + strcpy(newfriend->name, row[1]); //friend name + thisclient->FriendList.push_back( newfriend ); + ADDWORD (pak, newfriend->id);// friend id + ADDWORD (pak, 0x0000 ); + CCharClient* otherclient = (CCharClient*) GetClientByID( newfriend->id ); + if(otherclient!=NULL) //is online?? + { + ADDBYTE (pak, 0x07 ); //online + ChangeMessengerStatus ( thisclient, otherclient, 0x07); + } + else + { + ADDBYTE (pak, 0x08 );//offline + } + ADDSTRING (pak, newfriend->name); //friend name + ADDBYTE (pak, 0x00 ); + } + thisclient->SendPacket( &pak ); + DB->QFree( ); + thisclient->logout = false; + + //SEND CLAN INFORMATION + SendClanInfo (thisclient); + + return true; +} + +// Create a character +bool CCharServer::pakCreateChar( CCharClient* thisclient, CPacket* P ) +{ + if (!thisclient->isLoggedIn) return false; + + // Get info from packet + unsigned sex = GETBYTE((*P), 0 ) % 2; + unsigned hairstyle = GETBYTE((*P), 2 ); + unsigned face = GETBYTE((*P), 3 ); + string newname = (char*)&P->Buffer[7]; + MYSQL_RES *result; + result = DB->QStore("SELECT id FROM characters WHERE account_name='%s'", thisclient->username); + if(result==NULL) return false; + if ( mysql_num_rows( result ) == 5 ) + { + STARTPACKET( pak, 0x0713, 8 ); + SETWORD ( pak, 0x00, 4 ) + thisclient->SendPacket( &pak ); + DB->QFree( ); + return true; + } + DB->QFree( ); + result = DB->QStore("SELECT id FROM characters WHERE char_name='%s'", newname.c_str()); + if(result==NULL) return false; + if ( mysql_num_rows( result ) > 0 ) + { + STARTPACKET( pak, 0x0713, 8 ); + SETWORD ( pak, 0x00, 2 ); + thisclient->SendPacket( &pak ); + DB->QFree( ); + return true; + } + DB->QFree( ); + + if(!DB->QExecute("INSERT INTO characters (account_name, char_name, face, hairStyle, sex) VALUES('%s','%s',%i,%i,%i)", thisclient->username, newname.c_str(), face, hairstyle, sex)) + return false; + unsigned int mid = (unsigned)mysql_insert_id( DB->Mysql ); + MYSQL_ROW row; + + char tmp1[1024] = ""; //itemid + char tmp2[1024] = ""; //itemtype + char tmp3[1024] = ""; //refine + char tmp4[1024] = ""; //durability + char tmp5[1024] = ""; //lifespan + char tmp6[1024] = ""; //count + char tmp7[1024] = ""; //stats + char tmp8[1024] = ""; //socketed + char tmp9[1024] = ""; //appraised + + sprintf(&tmp2[strlen(tmp2)], "0|0|0|3|0|0|0|8|0|0|0|0|2|0"); + sprintf(&tmp3[strlen(tmp3)], "0|0|0|0|0|0|0|0|0|0|0|0|0|0"); + sprintf(&tmp4[strlen(tmp4)], "0|0|0|40|0|0|0|40|0|0|0|0|40|0"); + sprintf(&tmp5[strlen(tmp5)], "0|0|0|100|0|0|0|100|0|0|0|0|100|0"); + sprintf(&tmp6[strlen(tmp6)], "0|0|0|1|0|0|0|1|0|0|0|0|1|0"); + sprintf(&tmp7[strlen(tmp7)], "0|0|0|0|0|0|0|0|0|0|0|0|0|0"); + sprintf(&tmp8[strlen(tmp8)], "0|0|0|0|0|0|0|0|0|0|0|0|0|0"); + sprintf(&tmp9[strlen(tmp9)], "0|0|0|1|0|0|0|1|0|0|0|0|1|0"); + int tran = 1; + + //if(!DB->QExecute("INSERT INTO items VALUES(%i,30,3,0,40,100,3,1,0,0,0,0)", mid)) + // return false; + //if(!DB->QExecute("INSERT INTO items VALUES(%i,1,8,0,40,100,7,1,0,0,0,0)", mid)) + // return false; + if(sex==0) + { + //if(!DB->QExecute("INSERT INTO items VALUES(%i,222,2,0,40,100,12,1,0,0,0,0)", mid)) + // return false; + sprintf(&tmp1[strlen(tmp1)], "0|0|0|30|0|0|0|1|0|0|0|0|222|0"); + if(!DB->QExecute("INSERT INTO charitems (owner,itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,trans_action) VALUES('%i','%s','%s','%s','%s','%s','%s','%s','%s','%s','%i')", + mid,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,tran)) + return false; + } + else + { + //if(!DB->QExecute("INSERT INTO items VALUES(%i,221,2,0,40,100,12,1,0,0,0,0)", mid)) + // return false; + sprintf(&tmp1[strlen(tmp1)], "0|0|0|30|0|0|0|1|0|0|0|0|221|0"); + if(!DB->QExecute("INSERT INTO charitems (owner,itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,trans_action) VALUES('%i','%s','%s','%s','%s','%s','%s','%s','%s','%s','%i')", + mid,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,tran)) + return false; + } + STARTPACKET( pak, 0x0713, 8 ); + SETWORD ( pak, 0x00, 0 ); + thisclient->SendPacket( &pak ); + + return true; +} + +// Connect WorldServer +bool CCharServer::pakWSReady ( CCharClient* thisclient, CPacket* P ) +{ + + if(thisclient->isLoggedIn) return false; + if(GETDWORD((*P),0) != Config.CharPass ) + { + Log( MSG_HACK, "WorldServer Tried to connect CharServer with wrong password "); + return true; + } + thisclient->userid = GETDWORD((*P), 4); //our channelid + thisclient->accesslevel = 0xffff; //this will identify thisclient as channel + + // add channel + CChanels* newchannel = new (nothrow) CChanels; + if(newchannel==NULL) + return false; + + newchannel->id = GETDWORD((*P), 4); + newchannel->ip = inet_ntoa( thisclient->clientinfo.sin_addr ); + newchannel->port = GETWORD((*P),8 ); + Log( MSG_INFO, "Channel #%i connected from ip %s", newchannel->id, newchannel->ip ); + + // Connect to charserver + newchannel->sock = socket( AF_INET, SOCK_STREAM, 0 ); + if (newchannel->sock == INVALID_SOCKET) + { + Log( MSG_WARNING, "Could not access worldserver IP: %s , Port: %i", newchannel->ip, newchannel->port ); + delete newchannel; + return false; + } + struct sockaddr_in ain; + ain.sin_family = AF_INET; + ain.sin_addr.s_addr = inet_addr(newchannel->ip); + ain.sin_port = htons( newchannel->port ); + memset(&(ain.sin_zero), '\0', 8); + if ( connect( newchannel->sock , (SOCKADDR*) &ain, sizeof(ain) ) == SOCKET_ERROR) + { + Log( MSG_WARNING, "Could not access worldserver IP: %s , Port: %i", newchannel->ip , newchannel->port ); + delete newchannel; + return false; + } + ChannelList.push_back( newchannel ); + + thisclient->isLoggedIn = true; + BEGINPACKET( pak, 0x500 ); + ADDDWORD ( pak, Config.WorldPass ); + //cryptPacket( (char*)&pak, NULL ); + //EncryptBuffer( CryptTable,(unsigned char*)&pak ); + Log( MSG_INFO, "Accessing worldserver IP: %s , Port: %i", newchannel->ip , newchannel->port ); + send( newchannel->sock, (char*)&pak, pak.Size, 0 ); + return true; + +} + +// Login Connected +bool CCharServer::pakLoginConnected( CCharClient* thisclient, CPacket* P ) +{ + if(thisclient->isLoggedIn) return false; + if(GETDWORD((*P),0) != Config.CharPass ) + { + Log( MSG_HACK, "LoginServer Tried to connect CharServer with wrong password "); + return true; + } + thisclient->isLoggedIn = true; + return true; +} + +// Return to char select +bool CCharServer::pakWSCharSelect ( CCharClient* thisclient, CPacket* P ) +{ + //if(!thisclient->isLoggedIn) return false; + Log( MSG_INFO, "World server requested char select" ); + + unsigned int userid = GETDWORD( (*P), 0 ); + CCharClient* otherclient = GetClientByUserID( userid ); + if(otherclient==NULL) + { + Log(MSG_WARNING, "Invalid userid: %i", userid ); + return true; + } + BEGINPACKET( pak, 0x71c ); + //ADDBYTE ( pak, 0x00 ); + otherclient->SendPacket( &pak ); + Log( MSG_INFO, "Client returning to char select" ); + return true; +} + +// ??? +bool CCharServer::pak7e5 ( CCharClient* thisclient, CPacket* P ) +{ + BEGINPACKET( pak, 0 ); + if(!thisclient->isLoggedIn) return false; + unsigned int action = GETBYTE((*P),0); + switch(action) + { + case 0x03: + RESETPACKET( pak, 0x7e5 ); + ADDBYTE ( pak, 0x01 ); + ADDWORD ( pak, 0x0000 ); + thisclient->SendPacket( &pak ); + break; + default: + Log( MSG_WARNING,"Unknown 7e5 action %i",action); + } + return true; +} + +// Create a chatroom? +bool CCharServer::pakChatrooms ( CCharClient* thisclient, CPacket* P ) +{ + if(!thisclient->isLoggedIn) return false; + return true; +} + +// delete/resurect character +bool CCharServer::pakDeleteChar( CCharClient* thisclient, CPacket* P ) +{ + if(!thisclient->isLoggedIn) return false; + short int action = GETBYTE((*P), 1 ); + unsigned long int DeleteTime = 0; + switch(action) + { + case 0x00://Resurect + { + DeleteTime = 0; + if(!DB->QExecute(" UPDATE characters SET deletetime=0 WHERE char_name='%s'", + (char*)&P->Buffer[2] )) + return false; + } + break; + case 0x01://Delete + { + DeleteTime = GetServerTime( ) + Config.DeleteTime; + if(!DB->QExecute(" UPDATE characters SET deletetime=%i WHERE char_name='%s'", + DeleteTime, (char*)&P->Buffer[2] )) + return false; + + } + break; + } + BEGINPACKET( pak, 0x714 ); + if(DeleteTime > 0 ) + { + ADDDWORD ( pak, Config.DeleteTime ); + } + else + { + ADDDWORD ( pak, 0x00000000 ); + } + ADDSTRING ( pak, (char*)&P->Buffer[2] ); + ADDBYTE ( pak, 0x00 ); + thisclient->SendPacket( &pak ); + return true; +} + +bool CCharServer::pakUpdateLevel( CCharClient* thisclient, CPacket* P ) +{ + if(!thisclient->isLoggedIn) return false; + WORD charid = GETWORD((*P),0); + WORD level = GETWORD((*P),2); + for(UINT i=0;icharid==charid) + { + player->level = level; + break; + } + } + return true; +} + +// Disconnect client from char/world server +bool CCharServer::pakLoginDSClient( CCharClient* thisclient, CPacket* P ) +{ + + if(!thisclient->isLoggedIn) return false; + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 1: + { + unsigned int userid = GETDWORD ((*P), 1); + CCharClient* otherclient = GetClientByUserID( userid ); + if(otherclient==NULL) + { + Log( MSG_WARNING, "Userid '%u' is not online", userid ); + return true; + } + otherclient->isLoggedIn = false; + otherclient->isActive = false; + BEGINPACKET( pak, 0x502 ); + ADDBYTE ( pak, 1 ); + ADDDWORD ( pak, userid ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( otherclient->channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + } + break; + } + return true; +} diff --git a/Src/Char Server/charserver.cpp b/Src/Char Server/charserver.cpp new file mode 100644 index 0000000..5bd5ee2 --- /dev/null +++ b/Src/Char Server/charserver.cpp @@ -0,0 +1,204 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +// Constructor +CCharServer::CCharServer( string fn ) +{ + LoadConfigurations( (char*)fn.c_str() ); + GServer = this; +} + +// Deconstructor +CCharServer::~CCharServer( ) +{ +} + +// Return a new clientsocket structure +CCharClient* CCharServer::CreateClientSocket( ) +{ + CCharClient* thisclient = new (nothrow) CCharClient( ); + return thisclient; +} + +//loadserver encryption +void CCharServer::LoadEncryption( ) +{ + GenerateCryptTables( CryptTable, 0x87654321 ); +} + +// When Server is ready +bool CCharServer::OnServerReady( ) +{ + DB->QExecute( "DELETE FROM channels WHERE id=%u", Config.ServerID); + if(!DB->QExecute("INSERT INTO channels (id,name,host,port,connected,maxconnections,owner) VALUES (%i,'%s','%s',%i,0,%i,%i)", + Config.ServerID, Config.ServerName, Config.CharIP, Config.CharPort, Config.MaxConnections, Config.ParentID)) + { + Log(MSG_WARNING, "Error accessing to database, the other server will not connect to CharServer" ); + } + + MYSQL_RES *result; + MYSQL_ROW row; + // Load all our clans + result = DB->QStore("SELECT id,logo,back,name,cp,grade,slogan,news FROM list_clan"); + if(result==NULL) return false; + while(row = mysql_fetch_row(result)) + { + CClans* thisclan = new CClans; + assert(thisclan); // maybe remove + thisclan->id = atoi(row[0]); + thisclan->logo = atoi(row[1]); + thisclan->back = atoi(row[2]); + strcpy(thisclan->name,row[3]); + thisclan->cp = atoi(row[4]); + thisclan->grade = atoi(row[5]); + strcpy(thisclan->slogan,row[6]); + strcpy(thisclan->news,row[7]); + ClanList.push_back( thisclan ); + } + DB->QFree( ); + // Load Clans + for(UINT i=0;iQStore("SELECT id,char_name,clan_rank FROM characters WHERE clanid=%i order by clan_rank",Clan->id); + if(result==NULL) return false; + while(row = mysql_fetch_row(result)) + { + CClanMembers* newmember = new CClanMembers; + assert(newmember); + newmember->id = atoi(row[0]); + strcpy(newmember->name,row[1]); + newmember->clan_rank = atoi(row[2]); + Clan->ClanMembers.push_back( newmember ); + } + DB->QFree( ); + } + Log( MSG_INFO, "Clans Information Loaded" ); + return true; +} + +// When a a client is disconnected +void CCharServer::OnClientDisconnect( CClientSocket* thisclient ) +{ + CCharClient* thisclientwc = (CCharClient*)thisclient; + DB->QExecute( "UPDATE accounts SET online=0 WHERE username='%s'", thisclientwc->username ); + if(!thisclientwc->logout) + { + // Send logout message to friends + for(UINT i=0;iFriendList.size( );i++) + { + CFriendList* Friend = thisclientwc->FriendList.at( i );; + CCharClient* otherclient = GetClientByID( Friend->id ); + if(otherclient!=NULL) + { + ChangeMessengerStatus ( thisclientwc, otherclient, 0x08); + } + } + //set logout messga to clan + CClans* thisclan = GetClanByID(thisclientwc->clanid); + if(thisclan==NULL) + return; + for(UINT i=0;iClanMembers.size( );i++) + { + CClanMembers* ClanMember = thisclan->ClanMembers.at( i ); + CCharClient* otherclient = GetClientByID( ClanMember->id ); + if(otherclient!=NULL) + ChangeClanStatus (thisclientwc, otherclient, 0xff); + } + } +} + +// Deletes an old clientsocket structure +void CCharServer::DeleteClientSocket( CClientSocket* thisclient ) +{ + if (thisclient->isserver) + Log( MSG_INFO, "Server disconnected" ); + else + Log( MSG_INFO, "User disconnected" ); + delete (CCharClient*)thisclient; + +} + +// Load Server configuration +void CCharServer::LoadConfigurations( const char* file ) +{ + //Database + Config.SQLServer.pcServer = ConfigGetString ( file, "mysql_host", "localhost" ); + Config.SQLServer.pcDatabase = ConfigGetString ( file, "mysql_database", "roseon_beta" ); + Config.SQLServer.pcUserName = ConfigGetString ( file, "mysql_user", "root" ); + Config.SQLServer.pcPassword = ConfigGetString ( file, "mysql_pass", "" ); + Config.SQLServer.pcPort = ConfigGetInt ( file, "mysql_port", 3306 ); + //Server + Config.ServerID = ConfigGetInt ( file, "serverid", 1 ); + Config.ServerType = 1; // Char always = 1 + Config.CharIP = ConfigGetString ( file, "serverip", "127.0.0.1" ); + Config.CharPort = ConfigGetInt ( file, "serverport", 29100 ); + Config.ParentID = ConfigGetInt ( file, "parentid", 0 ); + Config.ServerName = ConfigGetString ( file, "servername", "Server" ); + Config.CharsPort = ConfigGetInt ( file, "CharsPort", 29110 ); + //Char + Config.usethreads = ConfigGetInt ( file, "usethreads", 0 )==0?false:true; + Config.DeleteTime = ConfigGetInt ( file, "deletetime", 36000 ); + Config.MinimumAccessLevel = ConfigGetInt ( file, "accesslevel", 100 ); + //Password + Config.LoginPass = ConfigGetInt ( file, "loginpass", 123456 ); + Config.CharPass = ConfigGetInt ( file, "charpass", 123456 ); + Config.WorldPass = ConfigGetInt ( file, "worldpass", 123456 ); +} + +// Incoming packet +bool CCharServer::OnReceivePacket( CClientSocket* thisclient, CPacket *P ) +{ + Log(MSG_INFO,"Packet received at OnReceivePacket"); + switch( P->Command ) + { + + case 0x0500: return pakWSReady ( (CCharClient*)thisclient, P ); + case 0x0501: return pakLoginConnected ( (CCharClient*)thisclient, P ); + case 0x0502: return pakLoginDSClient ( (CCharClient*)thisclient, P ); + case 0x0505: return pakWSCharSelect ( (CCharClient*)thisclient, P ); + case 0x070b: return pakDoIdentify ( (CCharClient*)thisclient, P ); + case 0x0712: return pakGetCharacters ( (CCharClient*)thisclient, P ); + case 0x0713: return pakCreateChar ( (CCharClient*)thisclient, P ); + case 0x0714: return pakDeleteChar ( (CCharClient*)thisclient, P ); + case 0x0715: return pakRequestWorld ( (CCharClient*)thisclient, P ); + case 0x0787: return pakClanChat ( (CCharClient*)thisclient, P ); + case 0x07e0: return pakClanManager ( (CCharClient*)thisclient, P ); + case 0x07e1: return pakMessengerManager ( (CCharClient*)thisclient, P ); + case 0x07e2: return pakMessengerChat ( (CCharClient*)thisclient, P ); + case 0x07e3: return pakChatrooms ( (CCharClient*)thisclient, P ); + case 0x07e5: return pak7e5 ( (CCharClient*)thisclient, P ); + case 0x07e6: return pakUploadCM ( (CCharClient*)thisclient, P ); + case 0x07e7: return pakDownloadCM ( (CCharClient*)thisclient, P ); + case 0x07e8: return pakClanIconTime ( (CCharClient*)thisclient, P ); + case 0x079e: return pakUpdateLevel ( (CCharClient*)thisclient, P ); + default: + Log( MSG_WARNING, "(SID:%i) Received unknown packet. Command:%04x Size:%04x", thisclient->sock, P->Command, P->Size ); + break; + } + return true; +} + +bool CCharServer::Ping( ) +{ + return DB->Ping( ); +} diff --git a/Src/Char Server/charserver.h b/Src/Char Server/charserver.h new file mode 100644 index 0000000..40726c3 --- /dev/null +++ b/Src/Char Server/charserver.h @@ -0,0 +1,115 @@ +/* + Open Source Rose Online Team - http://osroseon.to.md/ + note: the Server is develop with erose source server + eich source +*/ +#ifndef __ROSE_SERVERS__ +#define __ROSE_SERVERS__ + +#include +#include "../common/sockets.h" +#include "datatypes.h" +#define ClearItem(i) { i.durability=0; i.itemnum=0; i.itemtype=0; i.lifespan=0; i.refine=0; } +using namespace std; + +// Clent class +class CCharClient : public CClientSocket +{ + public: + CCharClient ( ); // constructor + ~CCharClient ( ); // destructor + + // User information + UINT userid; + bool platinum; + char username[17]; + char password[33]; + UINT accesslevel; + UINT channel; + + // Character information + char charname[17]; + UINT clanid; + int clan_rank; + UINT level; + UINT job; + UINT charid; + + bool isLoggedIn; + bool returnedfromWS; + bool logout; + vector FriendList; +}; + +// Server class +class CCharServer : public CServerSocket +{ + public: + CCharServer ( string ); + ~CCharServer( ); + CCharClient* CreateClientSocket( ); + void DeleteClientSocket( CClientSocket* thisclient ); + void LoadEncryption( ); + void OnClientDisconnect( CClientSocket* thisclient ); + bool OnServerReady( ); + void LoadConfigurations( const char* ); + bool OnReceivePacket( CClientSocket* thisclient, CPacket* P ); + bool Ping( ); + + // Extra functions + CChanels* GetChannelByID( UINT id ); + CCharClient* GetClientByUserID( UINT id ); + CCharClient* GetClientByID( UINT id ); + CCharClient* GetClientByName( char *name ); + CCharClient* GetClientByUserName( char *username ); + CClans* GetClanByID( int id ); + unsigned long int GetServerTime( ); + + // Community functions + bool ChangeMessengerStatus (CCharClient* thisclient, CCharClient* otherclient, int status); + bool pakMessengerManager ( CCharClient* thisclient, CPacket* P ); + bool pakMessengerChat ( CCharClient* thisclient, CPacket* P ); + + // Clan Functions + bool SendClanInfo (CCharClient* thisclient); + bool ChangeClanStatus (CCharClient* thisclient, CCharClient* otherclient, int channel); + bool SendToClanMembers( int clanid,CPacket* pak ); + bool UpdateClanWindow( int clanid ); + bool pakClanMembers ( CCharClient* thisclient ); + bool pakClanManager ( CCharClient* thisclient, CPacket* P ); + bool pakClanChat ( CCharClient* thisclient, CPacket* P ); + bool ClanLogout ( CCharClient* thisclient ); + bool pakUploadCM ( CCharClient* thisclient, CPacket* P ); + bool pakDownloadCM( CCharClient* thisclient, CPacket* P ); + bool pakClanIconTime( CCharClient* thisclient, CPacket* P ); + + // General Packets + bool pakUpdateLevel( CCharClient* thisclient, CPacket* P ); + bool pakDoIdentify( CCharClient* thisclient, CPacket* P ); + bool pakGetCharacters( CCharClient* thisclient, CPacket* P ); + bool pakRequestWorld( CCharClient* thisclient, CPacket* P ); + bool pakCreateChar( CCharClient* thisclient, CPacket* P ); + bool pakDeleteChar( CCharClient* thisclient, CPacket* P ); + bool pakWSReady ( CCharClient* thisclient, CPacket* P ); + bool pakWSCharSelect ( CCharClient* thisclient, CPacket* P ); + bool pakLoginConnected ( CCharClient* thisclient, CPacket* P ); + bool pakLoginDSClient( CCharClient* thisclient, CPacket* P ); + bool pak7e5 ( CCharClient* thisclient, CPacket* P ); + bool pakChatrooms ( CCharClient* thisclient, CPacket* P ); + + // Variables + string filename; + vector ClanList; + vector ChannelList; + + //Login + SOCKET lsock; // Socket to Login + char* lct; // Encryption table for login server + CDatabase* DB; +}; +void StartSignal( ); +void StopSignal( ); +void HandleSignal( int num ); +extern class CCharServer* GServer; +#endif + + diff --git a/Src/Char Server/clan.cpp b/Src/Char Server/clan.cpp new file mode 100644 index 0000000..51a3217 --- /dev/null +++ b/Src/Char Server/clan.cpp @@ -0,0 +1,1076 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "CharServer.h" + +// Send Clan information +bool CCharServer::SendClanInfo (CCharClient* thisclient) +{ + if( thisclient->clanid > 0 ) + { + CClans *thisclan = GetClanByID(thisclient->clanid); + if(thisclan!=NULL) + { + BEGINPACKET( pak, 0x7e0); + ADDBYTE ( pak, 0x33);//0x33 you have invited to clan + ADDWORD ( pak, thisclan->id);// clan id + ADDBYTE ( pak, 0x00);// + ADDBYTE ( pak, 0x00); + ADDWORD ( pak, thisclan->back);//Clan Background + ADDWORD ( pak, thisclan->logo);//Clan logo + ADDBYTE ( pak, thisclan->grade);//Clan grade + ADDBYTE ( pak, thisclient->clan_rank);// Clan rank (0 = red rokie / 6 = master) + ADDDWORD ( pak, thisclan->cp);//Clan Points + ADDDWORD ( pak, 0x00000064); + ADDDWORD ( pak, 0x00000000); //Clan found + ADDDWORD ( pak, 0x00000000); + ADDBYTE ( pak, 0x01); + for(int i=34;i<156;i++) + ADDBYTE ( pak, 0x00); + ADDWORD ( pak, 0x0000); + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->name);//Clan Name + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->slogan);//Clan slogan + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->news);//Clan news + ADDBYTE ( pak, 0x00); + thisclient->SendPacket(&pak); + + //Put the player online in clan + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* ClanMember = thisclan->ClanMembers.at( i );; + CCharClient* otherclient = GetClientByID( ClanMember->id ); + if(otherclient!=NULL) + ChangeClanStatus (thisclient, otherclient, thisclient->channel);//send channel here + } + } + } + return true; +} + +// Change player status in clan ((channel) = online/ 0xff = offline) +bool CCharServer::ChangeClanStatus (CCharClient* thisclient, CCharClient* otherclient, int channel) +{ + if (thisclient->charid !=otherclient->charid) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x73 );//Change player status + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, channel );//CANAL + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, thisclient->level ); + ADDWORD ( pak, thisclient->job ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + otherclient->SendPacket(&pak); + } + return true; +} + +// Clan chat +bool CCharServer::pakClanChat ( CCharClient* thisclient, CPacket* P ) +{ + if(!thisclient->isLoggedIn) + return false; + char* tmp; + if (P->Buffer[0]=='/') + { + char* command = strtok( (char*)&(*P).Buffer[1] , " "); + if(command==NULL) + return true; + if(strcmp(command, "pak")==0)//read cpacket.txt and send it (for test packets) + { + int comando = 0; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + comando=atoi(tmp); + char buffer; + FILE *packet1 = fopen("cpacket.txt","r"); + BEGINPACKET(pak,comando); + if(packet1==NULL) + { + printf("Error opening packet1.txt for 716!\n"); + return true; + } + while((fscanf(packet1,"%c",&buffer))!=EOF) + ADDBYTE(pak,buffer); + fclose(packet1); + thisclient->SendPacket( &pak ); + return true; + } + } + char* text = new (nothrow) char[P->Size]; + if(text==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( text, &P->Buffer, P->Size ); + CClans* thisclan = (CClans*) GetClanByID ( thisclient->clanid ); + if(thisclan!=NULL) + { + BEGINPACKET( pak, 0x787 ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + ADDSTRING ( pak, text ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclan->id,&pak); + } + delete []text; + return true; +} + +// Clan manager for all the clan functions +bool CCharServer::pakClanManager ( CCharClient* thisclient, CPacket* P ) +{ + if(!thisclient->isLoggedIn) return false; + MYSQL_RES *result; + MYSQL_ROW row; + BYTE action = GETBYTE ((*P),0); + switch (action) + { + case 0x01: + break; + case 0x02://invite clan + { + char* nick = new (nothrow) char[P->Size]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size ); + if(strcmp(nick,thisclient->charname)==0) + return true; + CCharClient* otherclient = (CCharClient*) GetClientByName( nick ); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x0b );//invite action + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x0 ); + otherclient->SendPacket( &pak ); + } + delete []nick; + } + break; + case 0x03://Kick from clan + { + char* nick = new (nothrow) char[P->Size]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size ); + CClans* thisclan = GetClanByID(thisclient->clanid); + if(thisclan==NULL) + { + delete []nick; + return true; + } + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x81 );//xxx have kicket to yyyy + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00); + SendToClanMembers(thisclient->clanid,&pak); + for(UINT i=0;iClanMembers.size( );i++) + { + CClanMembers* ClanMember = thisclan->ClanMembers.at( i ); + if(strcmp(ClanMember->name,nick)==0) + { + thisclan->ClanMembers.erase( thisclan->ClanMembers.begin() + i ); + break; + } + } + + CCharClient* otherclient = (CCharClient*) GetClientByName( nick ); + if( otherclient!=NULL ) + { + otherclient->clanid = 0; + otherclient->clan_rank=1; + RESETPACKET( pak, 0x7e1 );//Update world clan information + ADDBYTE ( pak, 0xfb );//action (kick) + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( otherclient->channel ); + if(thischannel!=NULL) + { + send( thischannel->sock, (char*)&pak, pak.Size, 0 ); + } + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + pakClanMembers( otherclient ); + } + if(!DB->QExecute("UPDATE characters SET clanid=0 AND clan_rank=1 WHERE char_name='%s'",nick)) + { + delete []nick; + return false; + } + delete []nick; + } + break; + case 0x04://up rank + { + bool online = true; + char* nick = new (nothrow) char[P->Size-7]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size-7 ); + int clan_rank=0; + int level=0; + int job=0; + int channel=0xff; + if(strcmp(nick,thisclient->charname)==0) + { + delete []nick; + return true; + } + CCharClient* otherclient = GetClientByName ( nick ); + if(otherclient!=NULL) + { + clan_rank = otherclient->clan_rank; + if(otherclient->clan_rank<5) + { + otherclient->clan_rank++; + } + else + { + delete []nick; + return true; + } + CClans* thisclan = GetClanByID(otherclient->clanid); + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at(i); + if(thismember!=0) + { + if (thismember->id==otherclient->charid) + { + thismember->clan_rank=otherclient->clan_rank; + } + } + } + job = otherclient->job; + level = otherclient->level; + channel=otherclient->channel; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x83 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, otherclient->clan_rank ); + otherclient->SendPacket(&pak); + } + else + { + online=false; + result = DB->QStore("SELECT clan_rank,classid,level FROM characters WHERE char_name='%s'",nick); + if(result==NULL) + { + delete []nick; + DB->QFree( ); + return false; + } + if(mysql_num_rows(result)!=1) + { + Log(MSG_WARNING, "Invalid charname: %s" , nick ); + delete []nick; + DB->QFree( ); + return false; + } + row = mysql_fetch_row( result ); + clan_rank=atoi(row[0]); + job = atoi(row[1]); + level = atoi(row[2]); + DB->QFree( ); + } + if(clan_rank<5) + { + clan_rank++; + } + if(!DB->QExecute("UPDATE characters SET clan_rank=%i WHERE char_name='%s'",clan_rank,nick)) + { + delete []nick; + return false; + } + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x75 ); + ADDBYTE ( pak, clan_rank ); + ADDBYTE ( pak, channel );//channel + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, level ); // level + ADDWORD ( pak, job ); // job + ADDSTRING ( pak, nick ); // Nick + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + if (online==true) + { + RESETPACKET( pak, 0x7e1 );//update world information + ADDBYTE ( pak, 0xfc ); + ADDBYTE ( pak, clan_rank ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + } + delete []nick; + } + break; + case 0x05://down rank + { + bool online = true; + char* nick = new (nothrow) char[P->Size-7]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size-7 ); + int clan_rank=0; + int level=0; + int job=0; + int channel=0xff; + if(strcmp(nick,thisclient->charname)==0) + { + delete []nick; + return true; + } + CCharClient* otherclient = GetClientByName ( nick ); + if(otherclient!=NULL) + { + clan_rank = otherclient->clan_rank; + if(otherclient->clan_rank>1) + { + otherclient->clan_rank--; + } + else + { + delete []nick; + return true; + } + CClans* thisclan = GetClanByID(otherclient->clanid); + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at(i); + if(thismember!=0) + { + if (thismember->id==otherclient->charid) + { + thismember->clan_rank=otherclient->clan_rank; + } + } + } + job = otherclient->job; + level = otherclient->level; + channel=otherclient->channel; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x83 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, otherclient->clan_rank ); + otherclient->SendPacket(&pak); + } + else + { + online=false; + result = DB->QStore("SELECT clan_rank,classid,level FROM characters WHERE char_name='%s'",nick); + if(result==NULL) + { + delete []nick; + DB->QFree( ); + return false; + } + if(mysql_num_rows(result)!=1) + { + Log(MSG_WARNING, "Invalid charname: %s" , nick ); + delete []nick; + DB->QFree( ); + return false; + } + row = mysql_fetch_row( result ); + clan_rank=atoi(row[0]); + job = atoi(row[1]); + level = atoi(row[2]); + DB->QFree( ); + } + if(clan_rank>1) + { + clan_rank--; + } + if(!DB->QExecute("UPDATE characters SET clan_rank=%i WHERE char_name='%s'",clan_rank,nick)) + { + delete []nick; + return false; + } + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x75 ); + ADDBYTE ( pak, clan_rank ); + ADDBYTE ( pak, channel );//channel + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, level ); // level + ADDWORD ( pak, job ); // job + ADDSTRING ( pak, nick ); // Nick + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + if (online==true) + { + RESETPACKET( pak, 0x7e1 );//update world information + ADDBYTE ( pak, 0xfc ); + ADDBYTE ( pak, clan_rank ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + } + delete []nick; + } + break; + case 0x06://Change news + { + if(thisclient->clan_rank<5) + return true; + char* news = new (nothrow) char[P->Size-7]; + if(news==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( news, &P->Buffer[1], P->Size-7 ); + CClans* thisclan = GetClanByID(thisclient->clanid); + if(thisclan!=NULL) + { + strcpy(thisclan->news,news); + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x34 ); + ADDSTRING ( pak, thisclan->news ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclan->id,&pak); + if(!DB->QExecute("UPDATE list_clan SET news='%s' WHERE id=%i", thisclan->news, thisclan->id)) + return false; + } + delete []news; + } + break; + case 0x07://Leave Clan + { + if( thisclient->clan_rank == 6 ) + return true; + CClans* thisclan = GetClanByID( thisclient->clanid ); + if(thisclan==NULL) + return true; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE( pak, 0x82 ); + ADDSTRING( pak, thisclient->charname ); + ADDBYTE( pak, 0x00); + SendToClanMembers(thisclient->clanid,&pak); + + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* ClanMember = thisclan->ClanMembers.at( i ); + if(strcmp(ClanMember->name,thisclient->charname)==0) + { + thisclan->ClanMembers.erase( thisclan->ClanMembers.begin()+i ); + delete ClanMember; + break; + } + } + thisclient->clanid = 0; + thisclient->clan_rank=1; + if(!DB->QExecute("UPDATE characters SET clanid=0 , clan_rank=0 WHERE char_name='%s'",thisclient->charname)) + return false; + RESETPACKET( pak, 0x7e1 );//Update world clan information + ADDBYTE ( pak, 0xfb );//action (leave) + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( thisclient->channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + } + break; + case 0x08: + { + pakClanMembers ( thisclient ); + } + break; + case 0x09://Give Master + { + int clan_rank=0; + int level=0; + int job=0; + int channel=0xff; + char* nick = new (nothrow) char[P->Size-7]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size-7 ); + if(strcmp(nick,thisclient->charname)==0) + { + delete []nick; + return true; + } + //Convert to master + CCharClient* otherclient = GetClientByName ( nick ); + if(otherclient!=NULL) + { + clan_rank = 6; + otherclient->clan_rank = clan_rank; + job = otherclient->job; + level = otherclient->level; + channel = otherclient->channel; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x83 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, otherclient->clan_rank ); + otherclient->SendPacket(&pak); + } + else + { // 0 1 + result = DB->QStore("SELECT classid,level FROM characters where char_name='%s'",nick); + if(result==NULL) + { + delete []nick; + return false; + } + if(mysql_num_rows(result)!=1) + { + DB->QFree( ); + Log(MSG_WARNING, "Invalid charname: %s" , nick ); + delete []nick; + return false; + } + row = mysql_fetch_row( result ); + job = atoi(row[0]); + level = atoi(row[1]); + DB->QFree( ); + } + if(!DB->QExecute("UPDATE characters SET clan_rank=%i WHERE char_name='%s'",clan_rank,nick)) + { + delete []nick; + return false; + } + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x75 ); + ADDBYTE ( pak, clan_rank ); + ADDBYTE ( pak, channel );//canal + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, level ); //level? + ADDWORD ( pak, job ); // job? + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + + RESETPACKET( pak, 0x7e1 );//update world information + ADDBYTE ( pak, 0xfc ); + ADDBYTE ( pak, clan_rank ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + CChanels* thischannel = GetChannelByID( channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + + //Convert to commander + clan_rank = 4; + thisclient->clan_rank=clan_rank; + job = thisclient->job; + level = thisclient->level; + channel = thisclient->channel; + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x83 ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, thisclient->clan_rank ); + thisclient->SendPacket(&pak); + + if(!DB->QExecute("UPDATE characters SET clan_rank=%i WHERE char_name='%s'",clan_rank,thisclient->charname)) + { + delete []nick; + return false; + } + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x75 ); + ADDBYTE ( pak, clan_rank ); + ADDBYTE ( pak, channel );//canal + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, level ); //level? + ADDWORD ( pak, job ); // job? + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + + RESETPACKET( pak, 0x7e1 );//update world information + ADDBYTE ( pak, 0xfc ); + ADDBYTE ( pak, clan_rank ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + thischannel = GetChannelByID( channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + delete []nick; + } + break; + case 0x0a://disorganize clan + { + if(thisclient->clan_rank<6) + return true; + unsigned int clanid = thisclient->clanid; + CClans* thisclan = GetClanByID(thisclient->clanid); + if(thisclan==NULL) + return true; + if(thisclan->ClanMembers.size()>1)//Can't be deleted if there are members + return true; + if(!DB->QExecute("UPDATE characters SET clanid=0 AND clan_rank=1 WHERE clanid=%i",thisclient->clanid)) + return false; + if(!DB->QExecute("DELETE FROM list_clan WHERE id=%i",thisclient->clanid)) + return false; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x51 ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid, &pak); + thisclan->ClanMembers.clear( ); + //delete from clan list + for(UINT i=0;icharid ); + CChanels* thischannel = GetChannelByID( thisclient->channel ); + if(thischannel!=NULL){ + send( thischannel->sock, (char*)&pak, pak.Size, 0 );} + else { Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel);} + thisclient->clanid = 0; + thisclient->clan_rank = 1; + //send( wct + } + break; + case 0x0c://invitation accepted + { + char* nick = new (nothrow) char[P->Size-7]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size-7 ); + CCharClient* otherclient = (CCharClient*) GetClientByName(nick ); + if(otherclient!=NULL) + { + thisclient->clanid = otherclient->clanid; + thisclient->clan_rank=1; + if(!DB->QExecute("UPDATE characters SET clanid=%i , clan_rank=1 WHERE char_name='%s'",thisclient->clanid,thisclient->charname)) + { + delete []nick; + return false; + } + CClans* thisclan = GetClanByID(thisclient->clanid); + if(thisclan==NULL) + { + delete []nick; + return true; + } + CClanMembers* newmember = new (nothrow) CClanMembers; + if(newmember==NULL) + { + Log( MSG_WARNING, "Error allocing memory" ); + delete []nick; + return false; + } + newmember->id = thisclient->charid; + strcpy(newmember->name,thisclient->charname); + newmember->clan_rank = 1; + thisclan->ClanMembers.push_back( newmember ); + } + else + { + delete []nick; + return true; + } + SendClanInfo(thisclient); + pakClanMembers(thisclient); + BEGINPACKET( pak, 0x7e0 );//xxx have invited to xxx + ADDBYTE( pak, 0x61 ); + ADDSTRING( pak, thisclient->charname ); + ADDBYTE( pak, 0x00); + ADDSTRING( pak, otherclient->charname ); + ADDBYTE( pak, 0x00); + SendToClanMembers(thisclient->clanid,&pak); + + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x84 ); + ADDWORD ( pak, thisclient->level ); + ADDWORD ( pak, thisclient->job ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + + RESETPACKET( pak, 0x7e1 );//update clan info in world + ADDBYTE( pak, 0xfa ); + ADDWORD( pak, thisclient->charid ); + ADDWORD( pak, thisclient->clanid ); + CChanels* thischannel = GetChannelByID( thisclient->channel ); + if(thischannel!=NULL) + { + send( thischannel->sock, (char*)&pak, pak.Size, 0 ); + } + else + { + Log(MSG_ERROR, "Check serverid in worldserver.conf it should be the same as ID in channels in the mysql database %i",thisclient->channel); + } + CChanels* otherchannel = GetChannelByID( otherclient->channel ); + if(otherchannel!=NULL && thischannel!=otherchannel) + { + send( thischannel->sock, (char*)&pak, pak.Size, 0 ); + } + delete []nick; + } + break; + case 0x0d://invitation no accepted + { + char* nick = new (nothrow) char[P->Size-7]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size-7 ); + CCharClient* otherclient = (CCharClient*) GetClientByName(nick ); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x0d ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + otherclient->SendPacket(&pak); + } + delete []nick; + } + break; + case 0x0f:// Update Clan window with Level and Job + { + CClans* thisclan = (CClans*) GetClanByID( thisclient->clanid ); + if(thisclan!=NULL) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x84 ); + ADDWORD ( pak, GETWORD ((*P),1) ); + ADDWORD ( pak, GETWORD ((*P),3) ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + } + } + break; + case 0x5C://message from worldserver to load the new clan information + { + UINT clanid = GETWORD((*P),1); + UINT clientrank = GETWORD((*P),3); + + CClans* thisclan = (CClans*) GetClanByID( clanid ); + if(thisclan==NULL) + return true; + thisclan->cp=GETDWORD ((*P),5); + + BEGINPACKET( pak, 0x7e0); + ADDBYTE ( pak, 0x71); + ADDWORD ( pak, clanid); + ADDWORD ( pak, 0x0000);// + ADDWORD ( pak, thisclan->back );//clan background + ADDWORD ( pak, thisclan->logo );//clanlogo + ADDBYTE ( pak, thisclan->grade);//clan grade + ADDBYTE ( pak, 0xCC); + ADDDWORD ( pak, thisclan->cp ); + ADDDWORD ( pak, 0x00000064 ); + ADDDWORD ( pak, 0x00000000 ); + ADDDWORD ( pak, 0x00000000 ); + ADDWORD ( pak, 0xCCCC ); + ADDWORD ( pak, 0xCCCC ); + ADDWORD ( pak, 0xCCCC ); + for(int i=0;i<119;i++) + ADDBYTE ( pak, 0x00); + SendToClanMembers(clanid,&pak); + UpdateClanWindow(clanid); + } + break; + case 0xfa://message from worldserver to load the new clan information + { + int id = GETWORD((*P),1); // clanid + int charid = GETWORD((*P),3); // charid + int clientid = GETWORD((*P),5); // clientid + CCharClient* otherclient = (CCharClient*) GetClientByID ( charid ); + if(otherclient==NULL) + return true; + // Load all our clan information + // 0 1 2 3 4 5 6 7 + result = DB->QStore("SELECT id,logo,back,name,cp,grade,slogan,news FROM list_clan where id=%i",id); + if(result == NULL) return false; + if(mysql_num_rows(result)!=1) + { + DB->QFree( ); + Log(MSG_WARNING, "Invalid clan: %i" , id ); + return false; + } + row = mysql_fetch_row(result); + //clan info + CClans* newclan = new CClans; + assert(newclan); + newclan->id = atoi(row[0]); + newclan->logo = atoi(row[1]); + newclan->back = atoi(row[2]); + strncpy_s(newclan->name,row[3],16); + newclan->cp = atoi(row[4]); + newclan->grade = atoi(row[5]); + strncpy_s(newclan->slogan,row[6],29); + strcpy(newclan->news,row[7]); + ClanList.push_back( newclan ); + DB->QFree( ); + //member info + CClanMembers* newmember = new CClanMembers; + newmember->id = otherclient->charid; + strcpy(newmember->name,otherclient->charname); + newmember->clan_rank = 6; + newclan->ClanMembers.push_back( newmember ); + + //DB->QFree( ); + otherclient->clanid = id; + otherclient->clan_rank = 6; + //Send new clan information + BEGINPACKET( pak, 0x7e0); + ADDBYTE ( pak, 0x30); + ADDWORD ( pak, clientid); + ADDWORD ( pak, 0x0100);// + ADDWORD ( pak, newclan->back );//clan background + ADDWORD ( pak, newclan->logo );//clanlogo + ADDBYTE ( pak, newclan->grade);//clan grade + ADDBYTE ( pak, otherclient->clan_rank);// clan rank + ADDDWORD ( pak, newclan->cp ); + ADDDWORD ( pak, 0x00000000 ); // Unk + ADDDWORD ( pak, 0x00000000 ); // Clan Funds + ADDDWORD ( pak, 0x00000000 ); // Unk + ADDWORD ( pak, 0x0000 ); // Unk + ADDDWORD ( pak, 0x00000000 ); // Contribution + for(int i=0;i<120;i++) + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, newclan->name);//clan name + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, newclan->slogan);//clan slogan + ADDBYTE ( pak, 0x00); + + otherclient->SendPacket(&pak); + } + break; + case 0xfb: + { + UINT clanid = GETWORD((*P),1); + UINT grade = GETWORD((*P),3); + CClans* thisclan = (CClans*) GetClanByID( clanid ); + if(thisclan!=NULL) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x84 ); + ADDWORD ( pak, GETWORD ((*P),1) ); + ADDWORD ( pak, GETWORD ((*P),3) ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + SendToClanMembers(thisclient->clanid,&pak); + } + } + default: + Log( MSG_INFO, "Clan action unknow %i ", action); + break; + } + return true; +} + +// Send clan members +bool CCharServer::pakClanMembers ( CCharClient* thisclient ) +{ + CClans* thisclan = (CClans*) GetClanByID( thisclient->clanid ); + if(thisclan!=NULL) + { + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x72 );//Send clan members + for(UINT i=0;iClanMembers.size( );i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at( i ); + if(thismember==NULL) + continue; + CCharClient* otherclient = (CCharClient*) GetClientByID (thismember->id); + if(otherclient!=NULL) + { + ADDBYTE ( pak, thismember->clan_rank ); //clan rank + ADDBYTE ( pak, otherclient->channel ); //channel (0x01 = channel 1) + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, otherclient->level );// Level + ADDWORD ( pak, otherclient->job );// Job + } + else + { + ADDBYTE ( pak, thismember->clan_rank); //clan rank + ADDBYTE ( pak, 0xff ); //channel (0xff = offline) + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 );// Level + ADDWORD ( pak, 0x0000 );// Job + } + ADDSTRING ( pak, thismember->name ); + ADDBYTE ( pak, 0x00 ); + } + thisclient->SendPacketCpy( &pak ); + } + return true; +} + +// Logout from clan +bool CCharServer::ClanLogout ( CCharClient* thisclient ) +{ + CClans* thisclan = (CClans*) GetClanByID(thisclient->clanid); + if(thisclan!=NULL) + { + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at( i ); + CCharClient* otherclient = (CCharClient*) GetClientByID ( thismember->id ); + if(otherclient!=NULL) + { + ChangeClanStatus (thisclient, otherclient, 0xff);//send channel here + } + } + } + return true; +} + +// upload clanmark +bool CCharServer::pakUploadCM ( CCharClient* thisclient, CPacket* P ) +{ + CClans* thisclan = GetClanByID( thisclient->clanid ); + if(thisclan == NULL) return true; + FILE* fh = fopen( "clanmark/clanmark.cnt", "r+b" ); + if(fh == NULL) + { + Log( MSG_WARNING, "Error opening clanmark counter file" ); + return true; + } + rewind(fh); + unsigned int cmid = 0;// this will be our clanmark id + unsigned int tcmid = 0;// this will be to update the id + fread( &cmid, 1, 4, fh ); + tcmid = cmid+1; + rewind(fh); + fwrite( &tcmid, 1, 4, fh ); + fclose(fh); + thisclan->logo = GETWORD((*P), 0 );//cmid; + thisclan->back = 0; + char filename[30]; + sprintf( filename, "clanmark/%u.cm", thisclan->logo ); + fh = fopen( filename, "w+b" ); + if(fh == NULL) + { + Log( MSG_WARNING, "Error creating clanmark file" ); + return true; + } + rewind(fh); + for(unsigned int i=0;iSize-6;i++) + fwrite( &P->Buffer[i], 1, 1, fh ); + fclose(fh); + DB->QExecute("UPDATE list_clan SET logo=%i,back=0 WHERE id=%i",thisclan->logo, thisclient->clanid ); + BEGINPACKET( pak, 0x7e1 );//update clan info in world + ADDBYTE ( pak, 0xff ); + ADDWORD ( pak, thisclient->clanid ); + ADDDWORD ( pak, cmid ); + for(int i=0;isock, (char*)&pak, pak.Size, 0 ); + return true; +} + +bool CCharServer::pakDownloadCM ( CCharClient* thisclient, CPacket* P ) +{ + unsigned int clanid = GETDWORD((*P), 0 ); + MYSQL_RES* result = DB->QStore("SELECT logo FROM list_clan WHERE id=%i",clanid ); + if(result == NULL) + return true; + if(mysql_num_rows(result)!=1) + return true; + MYSQL_ROW row = mysql_fetch_row(result); + unsigned int cmid = atoi(row[0]); + DB->QFree( ); + char filename[30]; + sprintf( filename, "clanmark/%u.cm", cmid ); + FILE* fh = fopen( filename, "rb" ); + if(fh == NULL) + { + Log( MSG_WARNING, "Invalid clanmark ID %i at filepath $s", cmid, filename ); + return true; + } + CClans* thisclan = GetClanByID(thisclient->clanid); + BEGINPACKET( pak, 0x7e7 ); + ADDDWORD ( pak, clanid ); + while(!feof(fh)) + { + unsigned char charvalue = '\0'; + fread( &charvalue, 1 , 1, fh ); + ADDBYTE( pak, charvalue ); + } + ADDWORD( pak,0); + thisclient->SendPacket( &pak ); + fclose(fh); + return true; +} +bool CCharServer::pakClanIconTime ( CCharClient* thisclient, CPacket* P ) //FORMAT UNKNOWN +{ + BEGINPACKET( pak, 0x7e8 ); + ADDWORD ( pak, 0x07d8); //clanmark change year + ADDBYTE ( pak, 0x01 ); //month + ADDBYTE ( pak, 0x0e ); //day + ADDBYTE ( pak, 0x12 ); //hour + ADDBYTE ( pak, 0x37 ); //minutes + ADDBYTE ( pak, 0x00 ); // ?? seconds? + thisclient->SendPacket( &pak ); + return true; +} diff --git a/Src/Char Server/community.cpp b/Src/Char Server/community.cpp new file mode 100644 index 0000000..5e07a10 --- /dev/null +++ b/Src/Char Server/community.cpp @@ -0,0 +1,215 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +// Change messenger status 07 - online | 08 - offline +bool CCharServer::ChangeMessengerStatus (CCharClient* thisclient, CCharClient* otherclient, int status) +{ + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x08 ); + ADDWORD ( pak, thisclient->charid ); + ADDWORD ( pak, 0x0000 ); + ADDBYTE ( pak, status ); + otherclient->SendPacket(&pak); + return true; +} + +// Messenger actions (add/remove/invite) +bool CCharServer::pakMessengerManager ( CCharClient* thisclient, CPacket* P ) +{ + BYTE action = GETBYTE((*P),0); + switch (action) + { + case 0x01://wanna be my friend? + { + char* nick = new (nothrow) char[P->Size]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 1" ); + return false; + } + memcpy( nick, &P->Buffer[1], P->Size ); + CCharClient* otherclient = (CCharClient*) GetClientByName (nick); + if(otherclient!=NULL) + {//Send friend invitation (check this one) + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x01 ); + ADDWORD ( pak, 0x0000 ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00 ); + otherclient->SendPacket(&pak); + } + else + {//This charname doesnt exist + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x04 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + thisclient->SendPacket(&pak); + } + delete []nick; + } + break; + case 0x02://yes i want + { + char* nick = new (nothrow) char[P->Size]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 2" ); + return false; + } + memcpy( nick, &P->Buffer[3], P->Size ); + CCharClient* otherclient = (CCharClient*) GetClientByName (nick); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x02 ); + ADDWORD ( pak, thisclient->charid ); + ADDBYTE ( pak, 0x00 ); + ADDWORD ( pak, 0x0000 ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00); + otherclient->SendPacket(&pak); + //Add friend to my friend list(sql) + if(!DB->QExecute("INSERT INTO list_friend (id,idfriend,namefriend) VALUES (%i,%i,'%s')", + otherclient->charid,thisclient->charid,thisclient->charname)) + return false; + CFriendList * newfriend1 = new (nothrow) CFriendList; + if(newfriend1==NULL) + return false; + newfriend1->id = otherclient->charid; //friendid + strcpy(newfriend1->name, otherclient->charname); //friend name + thisclient->FriendList.push_back( newfriend1 ); + RESETPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x02 ); + ADDWORD ( pak, otherclient->charid ); + ADDBYTE ( pak, 0x00 ); + ADDWORD ( pak, 0x0000 ); + ADDSTRING ( pak, otherclient->charname ); + ADDBYTE ( pak, 0x00); + thisclient->SendPacket(&pak); + //Add me to his friend list (sql) + if(!DB->QExecute("INSERT INTO list_friend (id,idfriend,namefriend) VALUES (%i,%i,'%s')", + thisclient->charid,otherclient->charid,otherclient->charname)) + return false; + CFriendList * newfriend2 = new (nothrow) CFriendList; + if(newfriend2==NULL) + return false; + newfriend2->id = thisclient->charid; //friendid + strcpy(newfriend2->name, thisclient->charname); //friend name + otherclient->FriendList.push_back( newfriend2 ); + } + else//not founded + { + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x04 ); + ADDWORD ( pak, 0x0000 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + thisclient->SendPacket(&pak); + } + delete []nick; + } + break; + case 0x03://no, i dont want + { + char* nick = new (nothrow) char[P->Size]; + if(nick==NULL) + { + Log(MSG_ERROR, "Error allocing memory: pakMessengerManager 3" ); + return false; + } + memcpy( nick, &P->Buffer[3], P->Size ); + CCharClient* otherclient = (CCharClient*) GetClientByName (nick); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x03 ); + ADDSTRING ( pak, thisclient->charname ); + ADDBYTE ( pak, 0x00); + otherclient->SendPacket(&pak); + } + else + { + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0x04 ); + ADDWORD ( pak, 0x0000 ); + ADDSTRING ( pak, nick ); + ADDBYTE ( pak, 0x00 ); + thisclient->SendPacket(&pak); + } + } + break; + case 0x05://freakin later + { + WORD id = GETWORD ((*P),1); + if(!DB->QExecute("DELETE FROM list_friend WHERE id=%i and idfriend=%i", + thisclient->charid,id)) + return false; + CCharClient* otherclient = (CCharClient*) GetClientByID(id); + if(otherclient!=NULL) + { + ChangeMessengerStatus ( thisclient, otherclient, 0x08); + } + } + break; + case 0xfa://messenger logout + { + WORD id = GETWORD ((*P),1); + CCharClient* ctherclient = (CCharClient*) GetClientByID(id); + if(ctherclient==NULL) + return true; + ctherclient->logout = true; + for(UINT i=0;iFriendList.size();i++) + { + CFriendList* thisfriend = ctherclient->FriendList.at( i ); + CCharClient* otherclient = (CCharClient*) GetClientByID( thisfriend->id ); + if(otherclient!=NULL) + { + ChangeMessengerStatus ( ctherclient, otherclient, 0x08); + } + } + //Logout in clan + ClanLogout ( ctherclient ); + } + break; + default: + Log( MSG_INFO, "Friend action unknown: %i", action ); + break; + } + return true; +} + +// Messenger Chat +bool CCharServer::pakMessengerChat ( CCharClient* thisclient, CPacket* P ) +{ + WORD id = GETWORD((*P),0); + CCharClient* otherclient = (CCharClient*) GetClientByID(id); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x7e2 ); + ADDWORD ( pak, thisclient->charid ); + for (int i=2;i<(P->Size-6);i++) + ADDBYTE ( pak, GETBYTE((*P),i )); + otherclient->SendPacket(&pak); + } + return true; +} diff --git a/Src/Char Server/datatypes.h b/Src/Char Server/datatypes.h new file mode 100644 index 0000000..61ed83e --- /dev/null +++ b/Src/Char Server/datatypes.h @@ -0,0 +1,68 @@ +/* + Open Source Rose Online Team - http://osroseon.to.md/ + note: the Server is develop with erose source server + eich source +*/ +#ifndef __ROSE_DATATYPES__ +#define __ROSE_DATATYPES__ +#include "../common/sockets.h" +// List of clan members +struct CClanMembers +{ + UINT id; + char name[50]; + int clan_rank; +}; + +// An item (for figuring out what a user is wearing) +struct CItem { + UINT owner; + UINT itemnum; + UINT itemtype; + UINT refine; + UINT durability; + UINT lifespan; +}; + +// A single client character +struct CCharacter { + char char_name[17]; + UINT level; + UINT face; + UINT hairStyle; + int sex; + UINT classid; + UINT id; + unsigned long int DeleteTime; +}; + +// List of friends +struct CFriendList +{ + int id; + char name[17]; +}; + +// List of clan +struct CClans +{ + int id; + int logo; + int back; + int grade; + int cp; + char name[17]; + char slogan[30]; + char news[260]; + vector ClanMembers; +}; + +// Channel list +struct CChanels +{ + UINT id; + char* ip; + UINT port; + SOCKET sock; +}; +#endif + diff --git a/Src/Char Server/extrafunctions.cpp b/Src/Char Server/extrafunctions.cpp new file mode 100644 index 0000000..a406d7b --- /dev/null +++ b/Src/Char Server/extrafunctions.cpp @@ -0,0 +1,184 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +// Search client by userid +CCharClient* CCharServer::GetClientByUserID( UINT userid ) +{ + for(UINT i=0;iuserid==userid) + { + return client; + } + } + return NULL; +} + +// Search the client by char id +CCharClient* CCharServer::GetClientByID( UINT charid ) +{ + for(UINT i=0;icharid==charid) + { + return client; + } + } + return NULL; +} + +// Search the client by char name +CCharClient* CCharServer::GetClientByName( char *name ) +{ + try + { + for(UINT i=0;icharname, name)==0) + { + return client; + } + } + } + catch(...) + { + return NULL; + } + return NULL; +} + +// Search the client by char name +CCharClient* CCharServer::GetClientByUserName( char *username ) +{ + for(UINT i=0;iusername, username)==0) + { + return client; + } + } + return NULL; +} + +// Search the clan by clan id +CClans* CCharServer::GetClanByID( int id ) +{ + for(UINT i=0;iid==id) + return thisclan; + } + return NULL; +} + +// Send packet to all the clan members +bool CCharServer::SendToClanMembers( int clanid,CPacket* pak ) +{ + CClans* thisclan = GetClanByID(clanid); + if(thisclan==0) + return false; + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at(i); + CCharClient* otherclient = (CCharClient*) GetClientByID (thismember->id); + if(otherclient!=0) + { + otherclient->SendPacketCpy(pak); + } + } + return true; +} + +// Update clan window all members 07 +bool CCharServer::UpdateClanWindow( int clanid ) +{ + CClans* thisclan = GetClanByID(clanid); + if(thisclan==0) + return false; + for(UINT i=0;iClanMembers.size();i++) + { + CClanMembers* thismember = thisclan->ClanMembers.at(i); + CCharClient* otherclient = (CCharClient*) GetClientByID (thismember->id); + if(otherclient!=0) + { + BEGINPACKET( pak, 0x7e0); + ADDBYTE ( pak, 0x33); + ADDWORD ( pak, clanid); + ADDWORD ( pak, 0x0000); + ADDWORD ( pak, thisclan->back );//clan background + ADDWORD ( pak, thisclan->logo );//clanlogo + ADDBYTE ( pak, thisclan->grade);//clan grade + ADDBYTE ( pak, otherclient->clan_rank); + ADDDWORD ( pak, thisclan->cp ); + ADDDWORD ( pak, 0x00000064 ); + ADDDWORD ( pak, 0x00000000 ); + ADDDWORD ( pak, 0x00000000 ); + ADDWORD ( pak, 0x0003 );//????????? + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + for(int i=0;i<119;i++) + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->name);//clan name + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->slogan);//Clan slogan + ADDBYTE ( pak, 0x00); + ADDSTRING ( pak, thisclan->news);//Clan news + ADDBYTE ( pak, 0x00); + otherclient->SendPacket(&pak); + } + } + return true; +} + +// Get channel by id +CChanels* CCharServer::GetChannelByID( unsigned int id ) +{ + for(unsigned int i=0;iid==id) + return thischannel; + } + return NULL; +} + +// Returns the current time/date +unsigned long int CCharServer::GetServerTime( ) +{ + // Get time/date and write it to the beginning of the row + time_t rawtime; // For time + struct tm* timeinfo; // " + time ( &rawtime ); + timeinfo = localtime( &rawtime ); + unsigned long int uCurTime = 0; + uCurTime += (timeinfo->tm_sec * 1 ); + uCurTime += (timeinfo->tm_min * 60 ); + uCurTime += (timeinfo->tm_hour * 3600 ); + uCurTime += (timeinfo->tm_yday * 86400 ); + uCurTime += ((timeinfo->tm_year-2000) * 86400*366 ); + return uCurTime; +} diff --git a/Src/Char Server/main.cpp b/Src/Char Server/main.cpp new file mode 100644 index 0000000..18169fe --- /dev/null +++ b/Src/Char Server/main.cpp @@ -0,0 +1,67 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" + +unsigned char LOG_THISSERVER = 0; +class CCharServer* GServer; + +// Main function +int main(int argc, char *argv[]) +{ + LOG_THISSERVER = LOG_CHARACTER_SERVER; + InitWinSocket( ); + string fileconf = "charserver.conf"; + if(argc>1) + { + fileconf = argv[1]; + } + CCharServer *server = new (nothrow) CCharServer( fileconf ); + if(server==NULL) + return -1; + MYSQL mysql; + server->DB = new CDatabase( server->Config.SQLServer.pcServer, + server->Config.SQLServer.pcUserName, + server->Config.SQLServer.pcPassword, + server->Config.SQLServer.pcDatabase, + server->Config.SQLServer.pcPort, &mysql ); + server->port = server->Config.CharPort; + if(server->Config.usethreads) + Log( MSG_INFO, "Using Threads" ); + if(server->DB->Connect( )==0) Log( MSG_INFO, "Connected to MySQL server" ); + else + { + delete server; + #ifdef _WIN32 + system("pause"); + #endif + return -1; + } + server->LoadEncryption (); + // Start server + server->StartServer(); + // Close server + server->DB->Disconnect( ); + delete server; + CloseWinSocket( ); + return EXIT_SUCCESS; +} + +// ----------------------------------------------------------------------------------------- diff --git a/Src/Char Server/signal.cpp b/Src/Char Server/signal.cpp new file mode 100644 index 0000000..cfb1532 --- /dev/null +++ b/Src/Char Server/signal.cpp @@ -0,0 +1,109 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "charserver.h" +/* +/ from signal.h + * NOTE: SIGINT is produced when the user presses Ctrl-C. + * SIGILL has not been tested. + * SIGFPE doesn't seem to work? + * SIGSEGV does not catch writing to a NULL pointer (that shuts down + * your app; can you say "segmentation violation core dump"?). + * SIGTERM comes from what kind of termination request exactly? + * SIGBREAK is indeed produced by pressing Ctrl-Break. + * SIGABRT is produced by calling abort. +*/ + +void StartSignal( ) +{ + signal( SIGINT, HandleSignal ); + signal( SIGILL, HandleSignal ); + signal( SIGFPE, HandleSignal ); + signal( SIGSEGV, HandleSignal ); + signal( SIGTERM, HandleSignal ); + signal( SIGBREAK, HandleSignal ); + signal( SIGABRT, HandleSignal ); +} + +void StopSignal( ) +{ + signal( SIGINT, 0 ); + signal( SIGILL, 0 ); + signal( SIGFPE, 0 ); + signal( SIGSEGV, 0 ); + signal( SIGTERM, 0 ); + signal( SIGBREAK, 0 ); + signal( SIGABRT, 0 ); +} + +void HandleSignal( int num ) +{ + switch(num) + { + case SIGINT:/* Interactive attention */ + Log( MSG_ERROR, "Signal received: SIGINT" ); + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + case SIGILL:/* Illegal instruction */ + Log( MSG_ERROR, "Signal received: SIGILL, Server will be closed, Trying to save..." ); + GServer->isActive = false; + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + case SIGFPE:/* Floating point error */ + Log( MSG_ERROR, "Signal received: SIGFPE, Server will be closed, Trying to save..." ); + GServer->isActive = false; + raise(num); + break; + case SIGSEGV:/* Segmentation violation */ + Log( MSG_ERROR, "Signal received: SIGSEGV, Server will be closed, Trying to save..." ); + GServer->isActive = false; + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + + case SIGTERM:/* Termination request */ + Log( MSG_INFO, "Signal received: SIGTERM, Server will be closed" ); + GServer->isActive = false; + break; + #ifdef _WIN32 + case SIGBREAK:/* Control-break */ + Log( MSG_INFO, "Signal received: SIGBREAK, Server will be closed" ); + GServer->isActive = false; + break; + #endif + default: + cout << "Unknown Signal Number: " << num << endl; + raise(num); + } +} diff --git a/Src/Common/SocketBase.cpp b/Src/Common/SocketBase.cpp new file mode 100644 index 0000000..177c712 --- /dev/null +++ b/Src/Common/SocketBase.cpp @@ -0,0 +1,66 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "sockets.h" + +// Constructor +CBaseSocket::CBaseSocket( ) +{ + sock = INVALID_SOCKET; + isActive = false; +} + +// Destructor +CBaseSocket::~CBaseSocket( ) +{ +} + +// Close all open sockets +void CBaseSocket::CloseSocket( void ) +{ + // Leave function, if socket isn't active + if ( !isActive ) return; + // Close socket + close( sock ); + sock = INVALID_SOCKET; + isActive = false; +} + +// Init winsocket, if we are using Windows +bool InitWinSocket ( void ) +{ +#ifdef _WIN32 + WSADATA wsa; + return (WSAStartup(MAKEWORD(2,0),&wsa)==0); +#else + return true; +#endif +} + +// Close winsocket, if we are using Windows +void CloseWinSocket( void ) +{ +#ifdef _WIN32 + WSACleanup( ); +#endif +} + + + diff --git a/Src/Common/SocketClient.cpp b/Src/Common/SocketClient.cpp new file mode 100644 index 0000000..19d823e --- /dev/null +++ b/Src/Common/SocketClient.cpp @@ -0,0 +1,216 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "sockets.h" +#include "seh.hpp" +#include "config.h" + +// Constructor +CClientSocket::CClientSocket( ) +{ + PacketSize = 6; // Starting size + PacketOffset = 0; // No bytes read, yet. +} + +// Destructor +CClientSocket::~CClientSocket( ) +{ + +} + +// Receive this client's socket +bool CClientSocket::ReceiveData( ) +{ + int ReceivedBytes; + short BytesToRead; + + // Calculate bytes to read to get the full packet + BytesToRead = PacketSize - PacketOffset; + // This should never happen, but it is integrated: + if ( BytesToRead > 0x400 - PacketOffset ) return false; + if ( BytesToRead == 0 ) return false; + + // Receive data from client + ReceivedBytes = recv( sock, (char*)&Buffer[ PacketOffset ], BytesToRead, 0 ); + if ( ReceivedBytes <= 0 ) return false; + + // Update pointer + PacketOffset += ReceivedBytes; + + // If the packet is not complete, leave the function + if ( ReceivedBytes != BytesToRead ) return true; + + if ( PacketSize == 6 ) + { + // We received the headerblock +#ifndef USE124 + PacketSize = DecryptBufferHeader( &CryptStatus, CryptTable, Buffer ); +#else + CPacket* header = (CPacket*)&Buffer; + PacketSize = header->Size; +#endif + + // Did we receive an incorrect buffer? + if ( PacketSize < 6 ) + { + Log(MSG_ERROR, "(SID:%i) Client sent incorrect blockheader.", sock ); + return false; + } + + // Is the packet larger than just the header, then continue receiving + if ( PacketSize > 6 ) return true; + } + +#ifndef USE124 + // We received the whole packet - Now we try to decrypt it + if ( !DecryptBufferData( CryptTable, Buffer ) ) + { + Log(MSG_ERROR, "(SID:%i) Client sent illegal block.", sock ); + return false; + } +#else + cryptPacket( (char*)Buffer, this->ct); +#endif + CPacket* pak = (CPacket*)&Buffer; + + //comment to prevent packet logging + FILE *fh = NULL; + fopen_s (&fh, "log/inoutpackets.log", "a+"); + if ( fh != NULL ) { + fprintf( fh, "(SID:%08u) IN %04x: ", sock, pak->Command ); + for ( int i=0; iSize-6; ++i ) fprintf( fh, "%02x ", (unsigned char)pak->Buffer[i] ); + fprintf( fh, "\n" ); + fclose( fh ); + } + + // Handle actions for this packet + if ( !GS->OnReceivePacket( this, pak ) ) + { + //Log(MSG_ERROR, "onrecieve packet returned false"); + return false; + } + + + // Reset values for the next packet + PacketSize = 6; + PacketOffset = 0; + //Log(MSG_DEBUG, "onrecieve packet returned true"); + return true; +} + +// Send a packet to this client +void CClientSocket::SendPacket( CPacket *P ) +{ + //first log it so we don't have to worry about encryption. Comment to prevent logging + bool LogFile = true; + FILE *fh = NULL; + fopen_s (&fh, "log/receivedpackets.log", "a+"); + + if ( fh != NULL && LogFile) + { + fprintf( fh, "(SID:%08u) OUT %04x: ", sock, P->Command ); + for ( int i=0; iSize-6; ++i ) fprintf( fh, "%02x ", (unsigned char)P->Buffer[i] ); + fprintf( fh, "\n" ); + fclose( fh ); + } + + + // :WARNING: IF WE ADD A CRYPT FUNCTION HERE WE MUST COPY THE + // PACKET AND NOT USE THE ORIGINAL, IT WILL FUCK UP + // THE SENDTOALL FUNCTIONS + + unsigned char* Buffer2 = (unsigned char*)P; + unsigned Size = P->Size; +#ifndef USE124 + if(this == NULL) + { + Log(MSG_WARNING,"tried to send a packet to a socket that doesn't exist any more"); + return; + } + //__seh_try + //{ + // EncryptBuffer( CryptTable, Buffer ); + //} + //__seh_except(info, context) + //{ + // Log(MSG_WARNING,"Caught a bad packet %s. Send cancelled", Buffer); + // return; + //} + //__seh_end + EncryptBuffer( CryptTable, Buffer2 ); +#endif + send( sock, (char*)Buffer2, Size, 0 ); + + //record teh packet that we just sent + //CPacket* pak = (CPacket*)&Buffer; + + +} + +//------------------------------------------------------------------------- +// Send a packet to a client without encrypting the source packet +//------------------------------------------------------------------------- +void CClientSocket::SendPacketCpy( CPacket *P ) +{ + CPacket NewPacket; + + memcpy( &NewPacket, P, sizeof( CPacket ) ); + SendPacket( &NewPacket ); +} + +// Handle client socket (threads) +PVOID ClientMainThread( PVOID ClientSocket ) +{ + CClientSocket* thisplayer = (CClientSocket*) ClientSocket; + fd_set fds; + while(thisplayer->isActive) + { + FD_ZERO(&fds); + FD_SET (thisplayer->sock, &fds); + int Select = select( thisplayer->sock+1, &fds, NULL, NULL, NULL ); + if(Select == SOCKET_ERROR) + { + Log( MSG_ERROR,NULL,"Error in Select"); + thisplayer->isActive = false; + } + else + { + if(FD_ISSET( thisplayer->sock, &fds )) + { + if (thisplayer->isserver == true) + { + //Log( MSG_INFO,"ISC PACKET in ClientMainThread"); + thisplayer->ISCThread(); + } + else + if(!thisplayer->ReceiveData( )) + { + thisplayer->isActive = false; + } + } + } + + } + thisplayer->GS->DisconnectClient( thisplayer ); + pthread_exit(NULL); + return 0 ; +} + +// ----------------------------------------------------------------------------------------- diff --git a/Src/Common/SocketServer.cpp b/Src/Common/SocketServer.cpp new file mode 100644 index 0000000..a2acb5c --- /dev/null +++ b/Src/Common/SocketServer.cpp @@ -0,0 +1,549 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "sockets.h" +#include + +// Constructor +CServerSocket::CServerSocket( ) +{ +#ifdef USE124 + csum = new char [0x200]; + csumlen = buildChecksum(csum, "TRose.exe"); + if(csumlen < 0) + std::cout << "Failed to generate checksum, error: " << csumlen << std::endl; + else + std::cout << "Generated checksum" << std::endl; +#endif + ConnectedClients = 0; +} + +// Destructor +CServerSocket::~CServerSocket( ) +{ + +} + +// Start up our server +bool CServerSocket::StartServer( ) +{ + //struct sockaddr_in ain; + int test; + sock = socket( AF_INET, SOCK_STREAM, 0 ); + if (sock == INVALID_SOCKET) + { + Log( MSG_FATALERROR, "Could not create a socket" ); + return false; + } + + int optval = 1; + if(setsockopt(sock, SOL_SOCKET,SO_KEEPALIVE,(const char*)&optval,sizeof(optval))==-1) + { + Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); + } + setsockopt(sock, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + ain.sin_family = AF_INET; + ain.sin_addr.s_addr = INADDR_ANY; + ain.sin_port = htons( port ); + memset(&(ain.sin_zero), '\0', 8); + if ( bind( sock, (const sockaddr*)&ain, sizeof( struct sockaddr ) ) ) + { + + Log( MSG_FATALERROR, "Could not bind socket" ); + std::cout <<"socket " << sock << std::endl; + std::cin >> test; + closesocket( sock ); + sock = INVALID_SOCKET; + return false; + } + if ( listen( sock, SOMAXCONN ) == -1 ) + { + Log( MSG_FATALERROR, "Could not listen on socket" ); + closesocket( sock ); + sock = INVALID_SOCKET; + return false; + } + if (LOG_THISSERVER == LOG_CHARACTER_SERVER) + { + //struct sockaddr_in ain; + sckISC = socket( AF_INET, SOCK_STREAM, 0 ); + if (sckISC == INVALID_SOCKET) { + Log( MSG_ERROR, "Could not create valid ISC socket (WSK2 ERROR: %i)", WSAGetLastError( ) ); + return false; + } + optval = 1; + if(setsockopt(sckISC, SOL_SOCKET,SO_KEEPALIVE,(const char*)&optval,sizeof(optval))==-1) + { + Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); + } + setsockopt(sckISC, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + sain.sin_family = AF_INET; + sain.sin_addr.s_addr = INADDR_ANY; + sain.sin_port = htons( Config.CharsPort);//29110 ); + memset(&(sain.sin_zero), '\0', 8); + if ( bind( sckISC, (const sockaddr*)&sain, sizeof( struct sockaddr ) ) ) + { + Log( MSG_FATALERROR, "Could not bind socket" ); + closesocket( sckISC ); + sckISC = INVALID_SOCKET; + return false; + } + if ( listen( sckISC, SOMAXCONN ) == -1 ) + { + Log( MSG_FATALERROR, "Could not listen on socket" ); + closesocket( sckISC ); + sckISC = INVALID_SOCKET; + return false; + } + Log( MSG_INFO, "opened ISC poort %i",29110 ); + + } + + if (LOG_THISSERVER == LOG_WORLD_SERVER) + { + struct sockaddr_in aina; //if this doesn't work. change back to ain + sckISC = socket( AF_INET, SOCK_STREAM, 0 ); + if (sckISC == INVALID_SOCKET) { + Log( MSG_ERROR, "Could not create valid ISC socket (WSK2 ERROR: %i)", WSAGetLastError( ) ); + return false; + } + aina.sin_family = AF_INET; + aina.sin_addr.s_addr = inet_addr(Config.CharIP);//( "127.0.0.1" ); + aina.sin_port = htons( Config.CharPort);//29110 ); + if ( connect( sckISC, (const sockaddr*)&aina, sizeof( aina ) ) == SOCKET_ERROR ) { + Log( MSG_ERROR, "Could not connect to ISC (WSK2 ERROR: %i)", WSAGetLastError( ) ); + closesocket( sckISC ); + sckISC = INVALID_SOCKET; + return false; + } + + + sckISCII = socket( AF_INET, SOCK_STREAM, 0 ); + if (sckISCII == INVALID_SOCKET) { + Log( MSG_ERROR, "Could not create valid ISC socket (WSK2 ERROR: %i)", WSAGetLastError( ) ); + return false; + } + optval = 1; + if(setsockopt(sckISCII, SOL_SOCKET,SO_KEEPALIVE,(const char*)&optval,sizeof(optval))==-1) + { + Log(MSG_ERROR, "setsockopt:SO_KEEPALIVE" ); + } + setsockopt(sckISCII, IPPROTO_TCP,TCP_NODELAY,(const char*)&optval,sizeof(optval)); + sain.sin_family = AF_INET; + sain.sin_addr.s_addr = INADDR_ANY; + sain.sin_port = htons( Config.WorldsPort );//29210 ); + memset(&(sain.sin_zero), '\0', 8); + if ( bind( sckISCII, (const sockaddr*)&sain, sizeof( struct sockaddr ) ) ) + { + Log( MSG_FATALERROR, "Could not bind socket" ); + closesocket( sckISCII ); + sckISCII = INVALID_SOCKET; + return false; + } + if ( listen( sckISCII, SOMAXCONN ) == -1 ) + { + Log( MSG_FATALERROR, "Could not listen on socket" ); + closesocket( sckISCII ); + sckISCII = INVALID_SOCKET; + return false; + } + Log( MSG_INFO, "opened ISC poort %i",Config.WorldsPort );//29210 ); + } + if (LOG_THISSERVER == LOG_LOGIN_SERVER) + { + struct sockaddr_in aina; //may need to change back to ain + sckISC = socket( AF_INET, SOCK_STREAM, 0 ); + if (sckISC == INVALID_SOCKET) { + Log( MSG_ERROR, "Could not create valid ISC socket (WSK2 ERROR: %i)", WSAGetLastError( ) ); + return false; + } + } + isActive = true; + + if ( !this->OnServerReady( ) ) + { + Log( MSG_FATALERROR, "Server could not start" ); + closesocket( sock ); + sock = INVALID_SOCKET; + isActive = false; + return false; + } + Log( MSG_INFO, "Server started on port %i and is listening.", port ); + //ISCThread( ); + ServerLoop( ); + // Nothing past here is ever really called + OnServerDie( ); + closesocket( sock ); + + return true; +} + +// Raven0123 +void CServerSocket::CryptISCPak( char* pak ) +{ + return; + unsigned paksize = *((unsigned short*)&pak[0]); + for( unsigned short i = 2; i < paksize; i ++ ) + pak[i] ^= 0x81 * i * paksize; +} + +// Raven0123 +//void CServerSocket::ISCThread() +bool CClientSocket::ISCThread() +{ + unsigned char buffer[0x400]; + unsigned recvd = 0; + unsigned bufsize = 0; + unsigned readlen = 6; + bool go = true; + + do { + + recvd = recv( sock, (char*)&buffer[bufsize], readlen-bufsize, 0 ); + if( recvd == 0){ + return false; + } + + if( recvd == SOCKET_ERROR ) { + return false; + } + + bufsize += recvd; + if( bufsize != readlen ) continue;//return;//continue; + if( bufsize == 6 ) { + readlen = *((unsigned short*)&buffer[0]); + if( readlen < 6 ) Log( MSG_ERROR, "Invalid server Packet Header" ); + if( readlen > 6 ) continue;//return;//continue; + } + + GS->OnReceivePacket( this, (CPacket*)&buffer[0] ); + go = false; + + bufsize = 0; + readlen = 6; + } while ( go==true ); + return true; +} + +// Server is started, lets run our loop :D +void CServerSocket::ServerLoop( ) +{ + fd_set fds; + int activity; + maxfd = 0; + sockaddr_in ClientInfo; + SOCKET NewSocket; + timeval timeout; + maxfd = sock; + OnServerStep(); + do + { + if (!Ping( )) + isActive = false; + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + NewSocket = INVALID_SOCKET; + FD_ZERO( &fds ); + if(!Config.usethreads) + FillFDS( &fds ); + FD_SET( sock, &fds ); + activity = select( maxfd+1, &fds, NULL, NULL, &timeout ); + if ( activity == 0 ) + { + // continue; + FD_ZERO( &fds ); + if(!Config.usethreads) + FillFDS( &fds ); + FD_SET( sckISC, &fds ); + activity = select( maxfd+1, &fds, NULL, NULL, &timeout ); + if ( activity == 0 )continue; + if ( activity < 0 && errno != EINTR ) + { + #ifdef _WIN32 + Log( MSG_ERROR, "Select command failed. Error #%i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Select command failed. Error #%i", errno ); + #endif + isActive = false; + } + if ( FD_ISSET( sckISC, &fds ) ) + { + int clientinfolen = sizeof( sockaddr_in ); + #ifdef _WIN32 + NewSocket = accept( sckISC, (sockaddr*)&ClientInfo, (int*)&clientinfolen ); + #else + NewSocket = accept( sckISC, (sockaddr*)&ClientInfo, (socklen_t*)&clientinfolen ); + #endif + // TODO: check if server is full + if (NewSocket != INVALID_SOCKET) + { + AddUser( NewSocket, &ClientInfo, true ); + + } + else + { + #ifdef _WIN32 + Log( MSG_ERROR, "Error accepting socket: %i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Error accepting socket: %i", errno ); + #endif + } + } + } + + if ( activity < 0 && errno != EINTR ) + { + #ifdef _WIN32 + Log( MSG_ERROR, "Select command failed. Error #%i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Select command failed. Error #%i", errno ); + #endif + isActive = false; + } + if ( FD_ISSET( sock, &fds ) ) + { + int clientinfolen = sizeof( sockaddr_in ); + #ifdef _WIN32 + NewSocket = accept( sock, (sockaddr*)&ClientInfo, (int*)&clientinfolen ); + #else + NewSocket = accept( sock, (sockaddr*)&ClientInfo, (socklen_t*)&clientinfolen ); + #endif + // TODO: check if server is full + if (NewSocket != INVALID_SOCKET) + AddUser( NewSocket, &ClientInfo, false ); + else + { + #ifdef _WIN32 + Log( MSG_ERROR, "Error accepting socket: %i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Error accepting socket: %i", errno ); + #endif + } + } + if(!Config.usethreads) + HandleClients( &fds ); + } while( isActive ); +} + +// Fills out an FDS for the server +void CServerSocket::FillFDS( fd_set* fds ) +{ + for(UINT i=0;iisActive) + { + FD_SET( (unsigned)client->sock, fds ); + if(client->sock>maxfd) + maxfd=client->sock; + } + else + { + DisconnectClient( client ); + } + } +} + +// Handle all our clients +void CServerSocket::HandleClients( fd_set* fds ) +{ + for(UINT i=0;iisActive) + continue; + if(FD_ISSET( client->sock, fds )) + { + if (client->isserver == true) + { + //Log( MSG_INFO,"ISC PACKET in HandleClients"); + if(!client->ISCThread()){ + client->isActive = false; + DisconnectClient( client );} + } + else + if(!client->ReceiveData( ) ) + { + client->isActive = false; + DisconnectClient( client ); + } + } + } +} + +// Add a new user to our server +void CServerSocket::AddUser( SOCKET sock, sockaddr_in* ClientInfo, bool server ) +{ + ConnectedClients++; + CClientSocket* thisclient = this->CreateClientSocket( ); + if (thisclient==NULL) + { + closesocket( thisclient->sock ); + if (thisclient!=0) delete thisclient; + thisclient=0; + return; + } +#ifdef USE124 + thisclient->ct = new char[TABLE_SIZE]; + buildCryptTable(thisclient->ct, csum, csumlen); +#endif + thisclient->CryptTable = CryptTable; + thisclient->CryptStatus.CurAddValue = 0; + thisclient->CryptStatus.CurEncryptionValue = CryptTable->EncryptionStartValue; + thisclient->GS = this; + thisclient->sock = sock; + thisclient->isActive = true; + + if (!OnClientConnect( thisclient )) { + closesocket( thisclient->sock ); + if (thisclient!=0) delete thisclient; + thisclient=0; + return; + } + thisclient->ClientIP = ""; + thisclient->ClientIP = inet_ntoa( ClientInfo->sin_addr ); + char *tmp; + memset(&thisclient->ClientSubNet, '\0', 12 ); + sprintf_s(thisclient->ClientSubNet, "%i.%i.%i", + (ClientInfo->sin_addr.s_addr )&0xFF, (ClientInfo->sin_addr.s_addr>>8 )&0xFF, + (ClientInfo->sin_addr.s_addr>>16)&0xFF); + ClientList.push_back( thisclient ); + if(Config.usethreads) + { + pthread_create( &threads[sock], NULL, ClientMainThread, (PVOID)thisclient); + } + memcpy( &thisclient->clientinfo, ClientInfo, sizeof(struct sockaddr_in)); + if(server==true) + { + thisclient->isserver=true; + Log( MSG_INFO, "Server connected from %s", inet_ntoa( ClientInfo->sin_addr ) ); + } + else + { + thisclient->isserver=false; + Log( MSG_INFO, "User connected from %s", inet_ntoa( ClientInfo->sin_addr ) ); + } +} + +// Disconnect our user +void CServerSocket::DisconnectClient( CClientSocket* thisclient ) +{ + ConnectedClients--; + OnClientDisconnect( thisclient ); + closesocket( thisclient->sock ); + thisclient->isActive = false; + thisclient->sock = INVALID_SOCKET; + for(UINT i=0;iisserver) + Log( MSG_INFO, "Server disconnected" ); + else + Log( MSG_INFO, "User disconnected" ); + delete thisclient; +} + +//This function loads the encryption +void CServerSocket::LoadEncryption( ) +{ + GenerateCryptTables( CryptTable, 0x87654321 ); + //port = ConfigGetInt("server.conf", "loginport", 29000); +} + +// This function is called just before proccessing clients +void CServerSocket::OnServerStep( ) +{ +} + +// This function is called just before the server starts +bool CServerSocket::OnServerReady( ) +{ + return true; +} + +// This function is called just before the server dies +void CServerSocket::OnServerDie( ) +{ + // DOESNT WORK - DAMN CONSOLE APPS +} + +// This function is called, if a client receives data +bool CServerSocket::OnReceivePacket( CClientSocket* thisclient, CPacket *P ) +{ + return true; +} + +// This function is called, if a client connects +bool CServerSocket::OnClientConnect( CClientSocket* thisclient ) +{ + return true; +} + +// This function is called, if a client disconnects +void CServerSocket::OnClientDisconnect( CClientSocket* thisclient ) +{ +} + +// Raven0123 +void CServerSocket::SendISCPacket( CPacket* pak ) +{ + send( sckISC, (char*)pak, pak->Size, 0 ); +} + +void CServerSocket::ReceivedISCPacket( CPacket* pak ) +{ + Log( MSG_DEBUG, "GOT ISC PACKET (BASESERVER) - 0x%04x %04x", pak->Command, pak->Size ); +} + +bool CServerSocket::DoSQL(char *Format, ...) { + int retval; + char Buffer[0x1500]; + va_list ap; va_start( ap, Format ); + vsprintf_s( Buffer, Format, ap ); + va_end ( ap ); + retval = mysql_query( mysql, Buffer ); + if( retval != 0 ) Log( MSG_ERROR, "MySQL Query Error '%s'", mysql_error( mysql ) ); + return (retval==0); +} + +bool CServerSocket::Ping( ) +{ + return true; +} diff --git a/Src/Common/config.cpp b/Src/Common/config.cpp new file mode 100644 index 0000000..9b17f15 --- /dev/null +++ b/Src/Common/config.cpp @@ -0,0 +1,114 @@ +/******************************************************************************************* + ___ ___ ___ ___ + ___| _ \/ _ \/ __| __| + / -_) / (_) \__ \ _| + \___|_|_\\___/|___/___| + + eROSE Server Develped by Brett19. + hROSE Team: Brett19 and bdaan + Year: 2006 + Release Date: Summer-Winter 2006 +*******************************************************************************************/ +#include +#include +#include +#include + +// Find the entry +bool ConfigGetEntry( const char *pcFile, const char *pcNameMust, char* &pcResult ) +{ + char pcBuffer[ 512 ]; + char *pcName, *pcPar, *pcPoint; + unsigned len; + + pcResult=0; + + // Open file + FILE* fh = NULL; + fopen_s (&fh, pcFile, "r"); + if ( fh == NULL ) return false; + + do + { + fgets( pcBuffer, 510, fh ); + len = (unsigned)strlen( pcBuffer ); + if ( pcBuffer[ len-1 ] == '\n' ) --len; + pcBuffer[ len ] = 0; + + pcName = pcBuffer; + while ( *pcName==' ' || *pcName=='\t' ) ++pcName; + if ( *pcName==0 || *pcName=='#' ) continue; + pcPoint = pcName; + while ( *pcPoint!=' ' && *pcPoint!='\t' && *pcPoint!='#' && *pcPoint!='=' && *pcPoint!=0 ) ++pcPoint; + if ( *pcPoint==0 || *pcPoint=='#' ) continue; + pcPar = pcPoint; + while ( *pcPar==' ' || *pcPar=='\t' || *pcPar=='=' ) ++pcPar; + *pcPoint=0; + + #ifdef _WIN32 + if ( !_strcmpi( pcName, pcNameMust ) ) + { + pcResult = _strdup( pcPar ); + pcPoint = pcResult + strlen( pcPar ); + if ( *pcPoint==0 ) return true; + --pcPoint; + while( *pcPoint==' ' || *pcPoint=='\t' ) *pcPoint--=0; + return true; + } + + #else + if ( strcasecmp( pcName, pcNameMust )==0 ) + { + pcResult = _strdup( pcPar ); + pcPoint = pcResult + strlen( pcPar ); + if ( *pcPoint==0 ) return true; + --pcPoint; + while( *pcPoint==' ' || *pcPoint=='\t' ) *pcPoint--=0; + return true; + } + #endif + + } while ( !feof( fh ) ); + + // Close file + fclose( fh ); + + return true; +} + +// ----------------------------------------------------------------------------------------- +// Returns a text +// ----------------------------------------------------------------------------------------- +char *ConfigGetString( const char *pcFile, const char *pcName, const char *pcDefault ) +{ + char *pcRet, *pcRetReal, *pcRetOld; + if ( !ConfigGetEntry( pcFile, pcName, pcRet ) ) return _strdup( pcDefault ); + if (pcRet==0) return _strdup( pcDefault ); + pcRetOld = pcRet; + if ( *pcRet=='"' ) + { + ++pcRet; + pcRet[ strlen(pcRet) - 1 ] = 0; + } + + pcRetReal = _strdup( pcRet ); + free( pcRetOld ); + + return pcRetReal; +} + +// Returns a number +unsigned ConfigGetInt( const char *pcFile, const char *pcName, unsigned uDefault ) +{ + char *pcRet; + unsigned uRet; + if ( !ConfigGetEntry( pcFile, pcName, pcRet ) ) return uDefault; + if (pcRet==0) return uDefault; + + uRet = atoi( pcRet ); + free( pcRet ); + + return uRet; +} + +// ----------------------------------------------------------------------------------------- diff --git a/Src/Common/config.h b/Src/Common/config.h new file mode 100644 index 0000000..8b5a76c --- /dev/null +++ b/Src/Common/config.h @@ -0,0 +1,24 @@ +/******************************************************************************************* + ___ ___ ___ ___ + ___| _ \/ _ \/ __| __| + / -_) / (_) \__ \ _| + \___|_|_\\___/|___/___| + + eROSE Server Develped by Brett19. + hROSE Team: Brett19 and bdaan + Year: 2006 + Release Date: Summer-Winter 2006 +*******************************************************************************************/ +#ifndef __ROSE_CONFIG_FUNCTIONS__ +#define __ROSE_CONFIG_FUNCTIONS__ + +// ----------------------------------------------------------------------------------------- +// Config load functions +// ----------------------------------------------------------------------------------------- +char *ConfigGetString( const char *pcFile, const char *pcName, const char *pcDefault ); +unsigned ConfigGetInt( const char *pcFile, const char *pcName, unsigned uDefault ); + +// ----------------------------------------------------------------------------------------- + +#endif + diff --git a/Src/Common/database/database.cpp b/Src/Common/database/database.cpp new file mode 100644 index 0000000..831e131 --- /dev/null +++ b/Src/Common/database/database.cpp @@ -0,0 +1,188 @@ +#include "database.h" +#include "../log.h" + +// constructor +CDatabase::CDatabase( char* server , char* username, char* password, char* database, unsigned int port , MYSQL* mysql) +{ + Server = server; + Username = username; + Password = password; + Database = database; + Port = port; + Mysql = mysql; + LastPing = time( NULL ); + SQLMutex = PTHREAD_MUTEX_INITIALIZER; + mysql_init( Mysql ); +} + +// deconstructor +CDatabase::~CDatabase( ) +{ +} + +// disconnect from mysql +void CDatabase::Disconnect( ) +{ + mysql_close( Mysql ); +} + +int CDatabase::Connect( ) +{ + Log( MSG_INFO, "Connecting to MySQL" ); + if(!mysql_real_connect( Mysql, Server, Username, Password, Database,Port, NULL, 0 )) + { + Log( MSG_FATALERROR, "Error connecting to MySQL server: %s\n", mysql_error( Mysql ) ); + return -1; + } + // Get timeout so we know when to ping - Drakia + MYSQL_RES *myresult = QStore("SELECT @@wait_timeout"); + MYSQL_ROW row = mysql_fetch_row( myresult ); + Timeout = atoi(row[0]) - 60; // Set the timeout to 1 minute before the connection will timeout + QFree( ); + Log( MSG_INFO, "MySQL Ping Timeout: %d seconds", Timeout ); + return 0; +} + +// reconnect to mysql +int CDatabase::Reconnect( ) +{ + Log( MSG_INFO, "Reconnecting to Mysql" ); + if(!mysql_real_connect( Mysql, Server, Username, Password, Database,Port, NULL, 0 )) + { + Log( MSG_FATALERROR, "Error reconnecting to MySQL server: %s\n", mysql_error( Mysql ) ); + return -1; + } + return 0; +} + +// execute query +bool CDatabase::QExecute( const char *Format,... ) +{ + bool Qfail = true; + char query[80000]; + va_list ap; + va_start( ap, Format ); + vsprintf_s (query, Format, ap); + va_end ( ap ); + Log( MSG_QUERY, query ); + pthread_mutex_lock( &SQLMutex ); + while(Qfail) + { + if(mysql_query( Mysql, query )!=0) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + if(Reconnect( )==-1) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + pthread_mutex_unlock( &SQLMutex ); + return false; + } + else Qfail = false; + } + else Qfail = false; + } + pthread_mutex_unlock( &SQLMutex ); + return true; +} + +MYSQL_RES* CDatabase::QStore( const char *Format, ...) +{ + bool Qfail = true; + char query[80000]; + va_list ap; + va_start( ap, Format ); + vsprintf_s( query, Format, ap ); + va_end ( ap ); + result = NULL; + Log( MSG_QUERY, query ); + //Log( MSG_INFO, query ); + //Log( MSG_INFO, "Locking pthread mutex" ); + pthread_mutex_lock( &SQLMutex ); + //Log( MSG_INFO, "Locked pthread mutex" ); + while(Qfail) + { + if(mysql_query( Mysql, query )!=0) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + if(Reconnect( )==-1) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + pthread_mutex_unlock( &SQLMutex ); + return NULL; + } + else Qfail = false; + } + else Qfail = false; + // Log( MSG_INFO, "mysql answer: %s", mysql_error( Mysql ) ); + } + //Log( MSG_INFO, "reading mysql result" ); + result = mysql_store_result( Mysql ); + //Log( MSG_INFO, "returning mysql result" ); + return result; +} + +MYSQL_RES* CDatabase::QUse( const char *Format, ...) +{ + bool Qfail = true; + char query[1024]; + va_list ap; + va_start( ap, Format ); + vsprintf_s( query, Format, ap ); + va_end ( ap ); + result = NULL; + Log( MSG_QUERY, query ); + pthread_mutex_lock( &SQLMutex ); + while(Qfail) + { + if(mysql_query( Mysql, query )!=0) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + if(Reconnect( )==-1) + { + Log( MSG_FATALERROR, "Could not execute query: %s", mysql_error( Mysql ) ); + pthread_mutex_unlock( &SQLMutex ); + return NULL; + } + else Qfail = false; + } + else Qfail = false; + } + result = mysql_use_result( Mysql ); + return result; +} + +bool CDatabase::DoSQL(const char *Format, ...) { + int retval; + char Buffer[1024]; + va_list ap; va_start( ap, Format ); + vsprintf_s( Buffer, Format, ap ); + va_end ( ap ); + retval = mysql_query( Mysql, Buffer ); + if( retval!=0 ) Log( MSG_ERROR, "MySQL Query Error '%s'", mysql_error( Mysql ) ); + return (retval==0); +} + +void CDatabase::QFree( ) +{ + mysql_free_result( result ); + pthread_mutex_unlock( &SQLMutex ); +} + +bool CDatabase::Ping( ) +{ + if ( time( NULL ) > ( LastPing + Timeout ) ) { + Log(MSG_INFO, "MySql Ping"); + LastPing = time( NULL ); + MYSQL_RES *res = QStore("SELECT @@wait_timeout"); + if (res == NULL) { + Log( MSG_ERROR, "MySQL Ping Failed, Attempting reconnect" ); + QFree( ); + return !Reconnect( ); + } + MYSQL_ROW row = mysql_fetch_row(res); + Timeout = atoi(row[0]) - 60; + QFree( ); + Log( MSG_INFO, "MySql Pong, new Timeout: %d seconds", Timeout); + } + return true; +} diff --git a/Src/Common/database/database.h b/Src/Common/database/database.h new file mode 100644 index 0000000..ca0ec84 --- /dev/null +++ b/Src/Common/database/database.h @@ -0,0 +1,33 @@ +#ifndef _ROSE_DATABASE_ +#define _ROSE_DATABASE_ +#include "../sockets.h" + +class CDatabase +{ + public: + CDatabase( char* , char*, char*, char*, unsigned int, MYSQL* ); + ~CDatabase( ); + + char* Server; + char* Username; + char* Password; + char* Database; + unsigned int Port; + unsigned int Timeout; + time_t LastPing; + int Connect( ); + int Reconnect( ); + void Disconnect( ); + bool QExecute( const char *format,... ); + MYSQL_RES* QUse( const char *format,... ); + MYSQL_RES* QStore( const char *format,... ); + bool DoSQL(const char *Format, ...); + void QFree( ); + bool Ping( ); + + MYSQL* Mysql; + MYSQL_RES* result; + pthread_mutex_t SQLMutex; +}; + +#endif diff --git a/Src/Common/globals.cpp b/Src/Common/globals.cpp new file mode 100644 index 0000000..0f449cb --- /dev/null +++ b/Src/Common/globals.cpp @@ -0,0 +1,91 @@ +#include "sockets.h" + +CPacket::CPacket( unsigned short mycommand, unsigned short mysize, unsigned short myunused ) +{ + Command = mycommand; + Size = mysize; + Unused = myunused; +} +CPacket::~CPacket( ) { } + +void CPacket::StartPacket( unsigned short mycommand, unsigned short mysize, unsigned short myunused ) +{ + Command = mycommand; + Size = mysize; + Unused = myunused; +} +void CPacket::AddByte( unsigned char value ) +{ + Buffer[Size-6] = value; + Size += 1; +} +void CPacket::AddWord( unsigned short value ) +{ + *((unsigned short*)&Buffer[Size-6]) = value; + Size += 2; +} +void CPacket::AddDWord( unsigned value ) +{ + *((unsigned*)&Buffer[Size-6]) = value; + Size += 4; +} +void CPacket::AddQWord( unsigned long long value ) +{ + *((unsigned long long*)&Buffer[Size-6]) = value; + Size += 8; +} +void CPacket::AddFloat( float value ) +{ + *((float*)&Buffer[Size-6]) = value; + Size += 4; +} +void CPacket::AddString( char* value ) +{ + for(unsigned char c=0; c<(unsigned)strlen(value); c++) + AddByte( value[c] ); +} +void CPacket::SetByte( unsigned short pos, unsigned char value ) +{ + Buffer[pos] = value; +} +void CPacket::SetWord( unsigned short pos, unsigned short value ) +{ + *((unsigned short*)&Buffer[pos]) = value; +} +void CPacket::SetDWord( unsigned short pos, unsigned value ) +{ + *((unsigned*)&Buffer[pos]) = value; +} +void CPacket::SetQWord( unsigned short pos, unsigned long long value ) +{ + *((unsigned long long*)&Buffer[pos]) = value; +} +void CPacket::SetFloat( unsigned short pos, float value ) +{ + *((float*)&Buffer[pos]) = value; +} +unsigned char CPacket::GetByte( unsigned short pos ) +{ + return Buffer[pos]; +} +unsigned short CPacket::GetWord( unsigned short pos ) +{ + return *((unsigned short*)&Buffer[pos]); +} +unsigned CPacket::GetDWord( unsigned short pos ) +{ + return *((unsigned*)&Buffer[pos]); +} +unsigned long long CPacket::GetQWord( unsigned short pos ) +{ + return *((unsigned long long*)&Buffer[pos]); +} +float CPacket::GetFloat( unsigned short pos ) +{ + return *((float*)&Buffer[pos]); +} + +char* CPacket::GetString( unsigned short pos ) +{ + return (char*)&Buffer[pos]; +} diff --git a/Src/Common/log.cpp b/Src/Common/log.cpp new file mode 100644 index 0000000..184aea6 --- /dev/null +++ b/Src/Common/log.cpp @@ -0,0 +1,167 @@ +#ifdef _WIN32 + #include + #include + #include +#endif +#include +#include +#include +#include "log.h" + +// Basic colors +typedef enum +{ + BLACK, + BLUE, + GREEN, + CYAN, + RED, + MAGENTA, + BROWN, + LIGHTGRAY, + DARKGRAY, + LIGHTBLUE, + LIGHTGREEN, + LIGHTCYAN, + LIGHTRED, + LIGHTMAGENTA, + YELLOW, + WHITE +} COLORS; + +// Our base colors +static int __BACKGROUND = BLACK; +static int __FOREGROUND = LIGHTGRAY; + +// Change console text color +void textcolor(int color) +{ + #ifdef _WIN32 + __FOREGROUND = color; + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color + (__BACKGROUND << 4)); + #else + + #endif +} + +// This function logs based on flags +void Log( enum msg_type flag, const char *Format, ... ) +{ + va_list ap; // For arguments + va_start( ap, Format ); + + // Timestamp + time_t rtime; + time (&rtime); + char timestamp[26]; + ctime_s (timestamp, sizeof (timestamp), &rtime); + timestamp[strlen (timestamp) - 1] = ' '; + + switch (flag) { + case MSG_NONE: // direct printf replacement + textcolor(WHITE); + vprintf( Format, ap ); + break; + case MSG_STATUS: + textcolor(GREEN); + printf("[STATUS]: "); + break; + case MSG_SQL: + textcolor(CYAN); + printf("[SQL]: "); + break; + case MSG_INFO: + textcolor(LIGHTGREEN); + printf("[INFO]: "); + break; + case MSG_NOTICE: + textcolor(LIGHTCYAN); + printf("[NOTICE]: "); + break; + case MSG_WARNING: + textcolor(YELLOW); + printf("[WARNING]: "); + break; + case MSG_DEBUG: + textcolor(LIGHTBLUE); + printf("[DEBUG]: "); + break; + case MSG_ERROR: + textcolor(RED); + printf("[ERROR]: "); + break; + case MSG_FATALERROR: + textcolor(LIGHTRED); + printf("[FATAL ERROR]: "); + break; + case MSG_HACK: + textcolor(LIGHTRED); + printf("[HACK]: "); + break; + case MSG_LOAD: + textcolor(BROWN); + printf("[LOADING]: "); + break; + case MSG_GMACTION: + textcolor(MAGENTA); + printf("[GM ACTION]: "); + break; + case MSG_START: + textcolor(MAGENTA); + vprintf( Format, ap ); + printf( "\r\n" ); + break; + //case MSG_SDEBUG: + // textcolor(MAGENTA); + // printf("[SDEBUG]: "); + // break; + //case MSG_QUERY: + // textcolor(LIGHTBLUE); + // printf("[QUERY]: "); + // break; + //default: + // textcolor(MAGENTA); + // printf("[UNKNOWN CASE]: "); + // break; + } + textcolor(LIGHTGRAY); + if(flag!=MSG_QUERY) + { + printf( "%s- ", timestamp ); + vprintf( Format, ap ); + printf( (flag==MSG_LOAD) ? "\r" : "\n" ); + } + FILE *fh = NULL; + switch(LOG_THISSERVER) + { + case LOG_LOGIN_SERVER: + fopen_s (&fh, LOG_DIRECTORY LOG_FILENAME_LOGINFILE, "a+"); + break; + case LOG_CHARACTER_SERVER: + fopen_s (&fh, LOG_DIRECTORY LOG_FILENAME_CHARFILE, "a+"); + break; + case LOG_WORLD_SERVER: + fopen_s (&fh, LOG_DIRECTORY LOG_FILENAME_WORLDFILE, "a+"); + break; + case LOG_SAME_FILE: + fopen_s (&fh, LOG_DIRECTORY LOG_DEFAULT_FILE, "a+"); + break; + default: + break; + } + if(flag == MSG_QUERY) + { + if(fh != NULL) + fclose(fh); + fopen_s (&fh, LOG_DIRECTORY "queries.txt", "a+"); + } + if ( fh != NULL ) + { + fprintf( fh, "%s- ", timestamp ); + vfprintf( fh, Format, ap ); + fputc( '\n', fh ); + fclose( fh ); + } + + va_end ( ap ); +} diff --git a/Src/Common/log.h b/Src/Common/log.h new file mode 100644 index 0000000..8d47887 --- /dev/null +++ b/Src/Common/log.h @@ -0,0 +1,58 @@ +/* + Open Source Rose Online Team - http://osroseon.to.md/ + note: the Server is develop with erose source server + eich source +*/ +#ifndef __ROSE_LOGGING_FUNCTIONS__ +#define __ROSE_LOGGING_FUNCTIONS__ + +// Log path +#ifdef _WIN32 +# define LOG_DIRECTORY "log/" +#endif + +#ifdef __unix__ +# define LOG_DIRECTORY "/var/log/roseserver/" +#endif + +#define LOG_LOGIN_SERVER 0x01 +#define LOG_CHARACTER_SERVER 0x02 +#define LOG_WORLD_SERVER 0x03 +#define LOG_SAME_FILE 0x04 + +// Log File names +extern unsigned char LOG_THISSERVER; +#define LOG_FILENAME_LOGINFILE "loginserver.log" +#define LOG_FILENAME_CHARFILE "charserver.log" +#define LOG_FILENAME_WORLDFILE "worldserver.log" +#define LOG_DEFAULT_FILE "server.log" +#define LOG_FILENAME_ERRORFILE "errors.log" + +#define LOG_LOGINPACKETS "loginpacket.log" +#define LOG_CHARPACKETS "charpackets.log" +#define LOG_WORLDPACKETS "worldpackets.log" +#define LOG_DEFAULTPACKETS "packets.log" + +// Error Types +enum msg_type { + MSG_NONE, + MSG_STATUS, + MSG_SQL, + MSG_INFO, + MSG_NOTICE, + MSG_WARNING, + MSG_DEBUG, + MSG_ERROR, + MSG_FATALERROR, + MSG_HACK, + MSG_LOAD, + MSG_SDEBUG, + MSG_GMACTION, + MSG_START, + MSG_QUERY, + MSG_QUESTDEBUG +}; + +// Logging function +void Log( msg_type flag, const char* Format, ... ); + +#endif diff --git a/Src/Common/mysql/mysql.h b/Src/Common/mysql/mysql.h new file mode 100644 index 0000000..ab61fe6 --- /dev/null +++ b/Src/Common/mysql/mysql.h @@ -0,0 +1,739 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program 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 + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _mysql_h +#define _mysql_h + +#ifdef __CYGWIN__ /* CYGWIN implements a UNIX API */ +#undef WIN +#undef _WIN +#undef _WIN32 +#undef _WIN64 +#undef __WIN__ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _global_h /* If not standard header */ +#include +#ifdef __LCC__ +#include /* For windows */ +#endif +typedef char my_bool; +#if (defined(_WIN32) || defined(_WIN64)) && !defined(__WIN__) +#define __WIN__ +#endif +#if !defined(__WIN__) +#define STDCALL +#else +#define STDCALL __stdcall +#endif +typedef char * gptr; + +#ifndef my_socket_defined +#ifdef __WIN__ +#define my_socket SOCKET +#else +typedef int my_socket; +#endif /* __WIN__ */ +#endif /* my_socket_defined */ +#endif /* _global_h */ + +#include "mysql_com.h" +#include "mysql_time.h" +#include "mysql_version.h" +#include "typelib.h" + +#include "my_list.h" /* for LISTs used in 'MYSQL' and 'MYSQL_STMT' */ + +extern unsigned int mysql_port; +extern char *mysql_unix_port; + +#define CLIENT_NET_READ_TIMEOUT 365*24*3600 /* Timeout on read */ +#define CLIENT_NET_WRITE_TIMEOUT 365*24*3600 /* Timeout on write */ + +#ifdef __NETWARE__ +#pragma pack(push, 8) /* 8 byte alignment */ +#endif + +#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) +#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) +#define IS_BLOB(n) ((n) & BLOB_FLAG) +#define IS_NUM(t) ((t) <= FIELD_TYPE_INT24 || (t) == FIELD_TYPE_YEAR) +#define IS_NUM_FIELD(f) ((f)->flags & NUM_FLAG) +#define INTERNAL_NUM_FIELD(f) (((f)->type <= FIELD_TYPE_INT24 && ((f)->type != FIELD_TYPE_TIMESTAMP || (f)->length == 14 || (f)->length == 8)) || (f)->type == FIELD_TYPE_YEAR) + + +typedef struct st_mysql_field { + char *name; /* Name of column */ + char *org_name; /* Original column name, if an alias */ + char *table; /* Table of column if column was a field */ + char *org_table; /* Org table name, if table was an alias */ + char *db; /* Database for table */ + char *catalog; /* Catalog for table */ + char *def; /* Default value (set by mysql_list_fields) */ + unsigned long length; /* Width of column (create length) */ + unsigned long max_length; /* Max width for selected set */ + unsigned int name_length; + unsigned int org_name_length; + unsigned int table_length; + unsigned int org_table_length; + unsigned int db_length; + unsigned int catalog_length; + unsigned int def_length; + unsigned int flags; /* Div flags */ + unsigned int decimals; /* Number of decimals in field */ + unsigned int charsetnr; /* Character set */ + enum enum_field_types type; /* Type of field. See mysql_com.h for types */ +} MYSQL_FIELD; + +typedef char **MYSQL_ROW; /* return data as array of strings */ +typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ + +#ifndef _global_h +#if defined(NO_CLIENT_LONG_LONG) +typedef unsigned long my_ulonglong; +#elif defined (__WIN__) +typedef unsigned __int64 my_ulonglong; +#else +typedef unsigned long long my_ulonglong; +#endif +#endif + +#define MYSQL_COUNT_ERROR (~(my_ulonglong) 0) + +typedef struct st_mysql_rows { + struct st_mysql_rows *next; /* list of rows */ + MYSQL_ROW data; + unsigned long length; +} MYSQL_ROWS; + +typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ + +#include "my_alloc.h" + +typedef struct st_mysql_data { + my_ulonglong rows; + unsigned int fields; + MYSQL_ROWS *data; + MEM_ROOT alloc; +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || defined(EMBEDDED_LIBRARY) + MYSQL_ROWS **prev_ptr; +#endif +} MYSQL_DATA; + +enum mysql_option +{ + MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH +}; + +struct st_mysql_options { + unsigned int connect_timeout, read_timeout, write_timeout; + unsigned int port, protocol; + unsigned long client_flag; + char *host,*user,*password,*unix_socket,*db; + struct st_dynamic_array *init_commands; + char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; + char *ssl_key; /* PEM key file */ + char *ssl_cert; /* PEM cert file */ + char *ssl_ca; /* PEM CA file */ + char *ssl_capath; /* PEM directory of CA-s? */ + char *ssl_cipher; /* cipher to use */ + char *shared_memory_base_name; + unsigned long max_allowed_packet; + my_bool use_ssl; /* if to use SSL or not */ + my_bool compress,named_pipe; + /* + On connect, find out the replication role of the server, and + establish connections to all the peers + */ + my_bool rpl_probe; + /* + Each call to mysql_real_query() will parse it to tell if it is a read + or a write, and direct it to the slave or the master + */ + my_bool rpl_parse; + /* + If set, never read from a master, only from slave, when doing + a read that is replication-aware + */ + my_bool no_master_reads; +#if !defined(CHECK_EMBEDDED_DIFFERENCES) || defined(EMBEDDED_LIBRARY) + my_bool separate_thread; +#endif + enum mysql_option methods_to_use; + char *client_ip; + /* Refuse client connecting to server if it uses old (pre-4.1.1) protocol */ + my_bool secure_auth; + + /* function pointers for local infile support */ + int (*local_infile_init)(void **, const char *, void *); + int (*local_infile_read)(void *, char *, unsigned int); + void (*local_infile_end)(void *); + int (*local_infile_error)(void *, char *, unsigned int); + void *local_infile_userdata; +}; + +enum mysql_status +{ + MYSQL_STATUS_READY,MYSQL_STATUS_GET_RESULT,MYSQL_STATUS_USE_RESULT +}; + +enum mysql_protocol_type +{ + MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, + MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY +}; +/* + There are three types of queries - the ones that have to go to + the master, the ones that go to a slave, and the adminstrative + type which must happen on the pivot connectioin +*/ +enum mysql_rpl_type +{ + MYSQL_RPL_MASTER, MYSQL_RPL_SLAVE, MYSQL_RPL_ADMIN +}; + +struct st_mysql_methods; + +typedef struct st_mysql +{ + NET net; /* Communication parameters */ + gptr connector_fd; /* ConnectorFd for SSL */ + char *host,*user,*passwd,*unix_socket,*server_version,*host_info,*info; + char *db; + struct charset_info_st *charset; + MYSQL_FIELD *fields; + MEM_ROOT field_alloc; + my_ulonglong affected_rows; + my_ulonglong insert_id; /* id if insert on table with NEXTNR */ + my_ulonglong extra_info; /* Used by mysqlshow */ + unsigned long thread_id; /* Id for connection in server */ + unsigned long packet_length; + unsigned int port; + unsigned long client_flag,server_capabilities; + unsigned int protocol_version; + unsigned int field_count; + unsigned int server_status; + unsigned int server_language; + unsigned int warning_count; + struct st_mysql_options options; + enum mysql_status status; + my_bool free_me; /* If free in mysql_close */ + my_bool reconnect; /* set to 1 if automatic reconnect */ + + /* session-wide random string */ + char scramble[SCRAMBLE_LENGTH+1]; + + /* + Set if this is the original connection, not a master or a slave we have + added though mysql_rpl_probe() or mysql_set_master()/ mysql_add_slave() + */ + my_bool rpl_pivot; + /* + Pointers to the master, and the next slave connections, points to + itself if lone connection. + */ + struct st_mysql* master, *next_slave; + + struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ + /* needed for send/read/store/use result to work correctly with replication */ + struct st_mysql* last_used_con; + + LIST *stmts; /* list of all statements */ + const struct st_mysql_methods *methods; + void *thd; + /* + Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag + from mysql_stmt_close if close had to cancel result set of this object. + */ + my_bool *unbuffered_fetch_owner; +} MYSQL; + +typedef struct st_mysql_res { + my_ulonglong row_count; + MYSQL_FIELD *fields; + MYSQL_DATA *data; + MYSQL_ROWS *data_cursor; + unsigned long *lengths; /* column lengths of current row */ + MYSQL *handle; /* for unbuffered reads */ + MEM_ROOT field_alloc; + unsigned int field_count, current_field; + MYSQL_ROW row; /* If unbuffered read */ + MYSQL_ROW current_row; /* buffer to current row */ + my_bool eof; /* Used by mysql_fetch_row */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + const struct st_mysql_methods *methods; +} MYSQL_RES; + +#define MAX_MYSQL_MANAGER_ERR 256 +#define MAX_MYSQL_MANAGER_MSG 256 + +#define MANAGER_OK 200 +#define MANAGER_INFO 250 +#define MANAGER_ACCESS 401 +#define MANAGER_CLIENT_ERR 450 +#define MANAGER_INTERNAL_ERR 500 + +#if !defined(MYSQL_SERVER) && !defined(MYSQL_CLIENT) +#define MYSQL_CLIENT +#endif + + +typedef struct st_mysql_manager +{ + NET net; + char *host,*user,*passwd; + unsigned int port; + my_bool free_me; + my_bool eof; + int cmd_status; + int last_errno; + char* net_buf,*net_buf_pos,*net_data_end; + int net_buf_size; + char last_error[MAX_MYSQL_MANAGER_ERR]; +} MYSQL_MANAGER; + +typedef struct st_mysql_parameters +{ + unsigned long *p_max_allowed_packet; + unsigned long *p_net_buffer_length; +} MYSQL_PARAMETERS; + +#if !defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) +#define max_allowed_packet (*mysql_get_parameters()->p_max_allowed_packet) +#define net_buffer_length (*mysql_get_parameters()->p_net_buffer_length) +#endif + +/* + Set up and bring down the server; to ensure that applications will + work when linked against either the standard client library or the + embedded server library, these functions should be called. +*/ +int STDCALL mysql_server_init(int argc, char **argv, char **groups); +void STDCALL mysql_server_end(void); +/* + mysql_server_init/end need to be called when using libmysqld or + libmysqlclient (exactly, mysql_server_init() is called by mysql_init() so + you don't need to call it explicitely; but you need to call + mysql_server_end() to free memory). The names are a bit misleading + (mysql_SERVER* to be used when using libmysqlCLIENT). So we add more general + names which suit well whether you're using libmysqld or libmysqlclient. We + intend to promote these aliases over the mysql_server* ones. +*/ +#define mysql_library_init mysql_server_init +#define mysql_library_end mysql_server_end + +MYSQL_PARAMETERS *STDCALL mysql_get_parameters(void); + +/* + Set up and bring down a thread; these function should be called + for each thread in an application which opens at least one MySQL + connection. All uses of the connection(s) should be between these + function calls. +*/ +my_bool STDCALL mysql_thread_init(void); +void STDCALL mysql_thread_end(void); + +/* + Functions to get information from the MYSQL and MYSQL_RES structures + Should definitely be used if one uses shared libraries. +*/ + +my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res); +unsigned int STDCALL mysql_num_fields(MYSQL_RES *res); +my_bool STDCALL mysql_eof(MYSQL_RES *res); +MYSQL_FIELD *STDCALL mysql_fetch_field_direct(MYSQL_RES *res, + unsigned int fieldnr); +MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res); +MYSQL_ROW_OFFSET STDCALL mysql_row_tell(MYSQL_RES *res); +MYSQL_FIELD_OFFSET STDCALL mysql_field_tell(MYSQL_RES *res); + +unsigned int STDCALL mysql_field_count(MYSQL *mysql); +my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql); +my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql); +unsigned int STDCALL mysql_errno(MYSQL *mysql); +const char * STDCALL mysql_error(MYSQL *mysql); +const char *STDCALL mysql_sqlstate(MYSQL *mysql); +unsigned int STDCALL mysql_warning_count(MYSQL *mysql); +const char * STDCALL mysql_info(MYSQL *mysql); +unsigned long STDCALL mysql_thread_id(MYSQL *mysql); +const char * STDCALL mysql_character_set_name(MYSQL *mysql); +int STDCALL mysql_set_character_set(MYSQL *mysql, char *csname); + +MYSQL * STDCALL mysql_init(MYSQL *mysql); +my_bool STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, + const char *cert, const char *ca, + const char *capath, const char *cipher); +my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, + const char *passwd, const char *db); +MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, + const char *user, + const char *passwd, + const char *db, + unsigned int port, + const char *unix_socket, + unsigned long clientflag); +int STDCALL mysql_select_db(MYSQL *mysql, const char *db); +int STDCALL mysql_query(MYSQL *mysql, const char *q); +int STDCALL mysql_send_query(MYSQL *mysql, const char *q, + unsigned long length); +int STDCALL mysql_real_query(MYSQL *mysql, const char *q, + unsigned long length); +MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql); + +/* perform query on master */ +my_bool STDCALL mysql_master_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_master_send_query(MYSQL *mysql, const char *q, + unsigned long length); +/* perform query on slave */ +my_bool STDCALL mysql_slave_query(MYSQL *mysql, const char *q, + unsigned long length); +my_bool STDCALL mysql_slave_send_query(MYSQL *mysql, const char *q, + unsigned long length); + +/* local infile support */ + +#define LOCAL_INFILE_ERROR_LEN 512 + +void +mysql_set_local_infile_handler(MYSQL *mysql, + int (*local_infile_init)(void **, const char *, + void *), + int (*local_infile_read)(void *, char *, + unsigned int), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char*, + unsigned int), + void *); + +void +mysql_set_local_infile_default(MYSQL *mysql); + + +/* + enable/disable parsing of all queries to decide if they go on master or + slave +*/ +void STDCALL mysql_enable_rpl_parse(MYSQL* mysql); +void STDCALL mysql_disable_rpl_parse(MYSQL* mysql); +/* get the value of the parse flag */ +int STDCALL mysql_rpl_parse_enabled(MYSQL* mysql); + +/* enable/disable reads from master */ +void STDCALL mysql_enable_reads_from_master(MYSQL* mysql); +void STDCALL mysql_disable_reads_from_master(MYSQL* mysql); +/* get the value of the master read flag */ +my_bool STDCALL mysql_reads_from_master_enabled(MYSQL* mysql); + +enum mysql_rpl_type STDCALL mysql_rpl_query_type(const char* q, int len); + +/* discover the master and its slaves */ +my_bool STDCALL mysql_rpl_probe(MYSQL* mysql); + +/* set the master, close/free the old one, if it is not a pivot */ +int STDCALL mysql_set_master(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); +int STDCALL mysql_add_slave(MYSQL* mysql, const char* host, + unsigned int port, + const char* user, + const char* passwd); + +int STDCALL mysql_shutdown(MYSQL *mysql, + enum mysql_enum_shutdown_level + shutdown_level); +int STDCALL mysql_dump_debug_info(MYSQL *mysql); +int STDCALL mysql_refresh(MYSQL *mysql, + unsigned int refresh_options); +int STDCALL mysql_kill(MYSQL *mysql,unsigned long pid); +int STDCALL mysql_set_server_option(MYSQL *mysql, + enum enum_mysql_set_option + option); +int STDCALL mysql_ping(MYSQL *mysql); +const char * STDCALL mysql_stat(MYSQL *mysql); +const char * STDCALL mysql_get_server_info(MYSQL *mysql); +const char * STDCALL mysql_get_client_info(void); +unsigned long STDCALL mysql_get_client_version(void); +const char * STDCALL mysql_get_host_info(MYSQL *mysql); +unsigned long STDCALL mysql_get_server_version(MYSQL *mysql); +unsigned int STDCALL mysql_get_proto_info(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_list_dbs(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_tables(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_processes(MYSQL *mysql); +int STDCALL mysql_options(MYSQL *mysql,enum mysql_option option, + const char *arg); +void STDCALL mysql_free_result(MYSQL_RES *result); +void STDCALL mysql_data_seek(MYSQL_RES *result, + my_ulonglong offset); +MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *result, + MYSQL_ROW_OFFSET offset); +MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *result, + MYSQL_FIELD_OFFSET offset); +MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result); +unsigned long * STDCALL mysql_fetch_lengths(MYSQL_RES *result); +MYSQL_FIELD * STDCALL mysql_fetch_field(MYSQL_RES *result); +MYSQL_RES * STDCALL mysql_list_fields(MYSQL *mysql, const char *table, + const char *wild); +unsigned long STDCALL mysql_escape_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_hex_string(char *to,const char *from, + unsigned long from_length); +unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, + char *to,const char *from, + unsigned long length); +void STDCALL mysql_debug(const char *debug); +char * STDCALL mysql_odbc_escape_string(MYSQL *mysql, + char *to, + unsigned long to_length, + const char *from, + unsigned long from_length, + void *param, + char * + (*extend_buffer) + (void *, char *to, + unsigned long *length)); +void STDCALL myodbc_remove_escape(MYSQL *mysql,char *name); +unsigned int STDCALL mysql_thread_safe(void); +my_bool STDCALL mysql_embedded(void); +MYSQL_MANAGER* STDCALL mysql_manager_init(MYSQL_MANAGER* con); +MYSQL_MANAGER* STDCALL mysql_manager_connect(MYSQL_MANAGER* con, + const char* host, + const char* user, + const char* passwd, + unsigned int port); +void STDCALL mysql_manager_close(MYSQL_MANAGER* con); +int STDCALL mysql_manager_command(MYSQL_MANAGER* con, + const char* cmd, int cmd_len); +int STDCALL mysql_manager_fetch_line(MYSQL_MANAGER* con, + char* res_buf, + int res_buf_size); +my_bool STDCALL mysql_read_query_result(MYSQL *mysql); + + +/* + The following definitions are added for the enhanced + client-server protocol +*/ + +/* statement state */ +enum enum_mysql_stmt_state +{ + MYSQL_STMT_INIT_DONE= 1, MYSQL_STMT_PREPARE_DONE, MYSQL_STMT_EXECUTE_DONE, + MYSQL_STMT_FETCH_DONE +}; + + +/* bind structure */ +typedef struct st_mysql_bind +{ + unsigned long *length; /* output length pointer */ + my_bool *is_null; /* Pointer to null indicator */ + void *buffer; /* buffer to get/put data */ + enum enum_field_types buffer_type; /* buffer type */ + unsigned long buffer_length; /* buffer length, must be set for str/binary */ + + /* Following are for internal use. Set by mysql_stmt_bind_param */ + unsigned char *inter_buffer; /* for the current data position */ + unsigned long offset; /* offset position for char/binary fetch */ + unsigned long internal_length; /* Used if length is 0 */ + unsigned int param_number; /* For null count and error messages */ + unsigned int pack_length; /* Internal length for packed data */ + my_bool is_unsigned; /* set if integer type is unsigned */ + my_bool long_data_used; /* If used with mysql_send_long_data */ + my_bool internal_is_null; /* Used if is_null is 0 */ + void (*store_param_func)(NET *net, struct st_mysql_bind *param); + void (*fetch_result)(struct st_mysql_bind *, unsigned char **row); + void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *, + unsigned char **row); +} MYSQL_BIND; + + +/* statement handler */ +typedef struct st_mysql_stmt +{ + MEM_ROOT mem_root; /* root allocations */ + LIST list; /* list to keep track of all stmts */ + MYSQL *mysql; /* connection handle */ + MYSQL_BIND *params; /* input parameters */ + MYSQL_BIND *bind; /* output parameters */ + MYSQL_FIELD *fields; /* result set metadata */ + MYSQL_DATA result; /* cached result set */ + MYSQL_ROWS *data_cursor; /* current row in cached result */ + /* copy of mysql->affected_rows after statement execution */ + my_ulonglong affected_rows; + my_ulonglong insert_id; /* copy of mysql->insert_id */ + /* + mysql_stmt_fetch() calls this function to fetch one row (it's different + for buffered, unbuffered and cursor fetch). + */ + int (*read_row_func)(struct st_mysql_stmt *stmt, + unsigned char **row); + unsigned long stmt_id; /* Id for prepared statement */ + unsigned int last_errno; /* error code */ + unsigned int param_count; /* input parameter count */ + unsigned int field_count; /* number of columns in result set */ + enum enum_mysql_stmt_state state; /* statement state */ + char last_error[MYSQL_ERRMSG_SIZE]; /* error message */ + char sqlstate[SQLSTATE_LENGTH+1]; + /* Types of input parameters should be sent to server */ + my_bool send_types_to_server; + my_bool bind_param_done; /* input buffers were supplied */ + my_bool bind_result_done; /* output buffers were supplied */ + /* mysql_stmt_close() had to cancel this result */ + my_bool unbuffered_fetch_cancelled; + /* + Is set to true if we need to calculate field->max_length for + metadata fields when doing mysql_stmt_store_result. + */ + my_bool update_max_length; +} MYSQL_STMT; + +enum enum_stmt_attr_type +{ + /* + When doing mysql_stmt_store_result calculate max_length attribute + of statement metadata. This is to be consistent with the old API, + where this was done automatically. + In the new API we do that only by request because it slows down + mysql_stmt_store_result sufficiently. + */ + STMT_ATTR_UPDATE_MAX_LENGTH +}; + + +typedef struct st_mysql_methods +{ + my_bool (*read_query_result)(MYSQL *mysql); + my_bool (*advanced_command)(MYSQL *mysql, + enum enum_server_command command, + const char *header, + unsigned long header_length, + const char *arg, + unsigned long arg_length, + my_bool skip_check); + MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + unsigned int fields); + MYSQL_RES * (*use_result)(MYSQL *mysql); + void (*fetch_lengths)(unsigned long *to, + MYSQL_ROW column, unsigned int field_count); + void (*flush_use_result)(MYSQL *mysql); +#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) + MYSQL_FIELD * (*list_fields)(MYSQL *mysql); + my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); + int (*stmt_execute)(MYSQL_STMT *stmt); + int (*read_binary_rows)(MYSQL_STMT *stmt); + int (*unbuffered_fetch)(MYSQL *mysql, char **row); + void (*free_embedded_thd)(MYSQL *mysql); + const char *(*read_statistics)(MYSQL *mysql); + my_bool (*next_result)(MYSQL *mysql); + int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); +#endif +} MYSQL_METHODS; + + +MYSQL_STMT * STDCALL mysql_stmt_init(MYSQL *mysql); +int STDCALL mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, + unsigned long length); +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt); +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind, + unsigned int column, + unsigned long offset); +int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt); +unsigned long STDCALL mysql_stmt_param_count(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *attr); +my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + void *attr); +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd); +my_bool STDCALL mysql_stmt_close(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_reset(MYSQL_STMT * stmt); +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt); +my_bool STDCALL mysql_stmt_send_long_data(MYSQL_STMT *stmt, + unsigned int param_number, + const char *data, + unsigned long length); +MYSQL_RES *STDCALL mysql_stmt_result_metadata(MYSQL_STMT *stmt); +MYSQL_RES *STDCALL mysql_stmt_param_metadata(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_errno(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_error(MYSQL_STMT * stmt); +const char *STDCALL mysql_stmt_sqlstate(MYSQL_STMT * stmt); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_seek(MYSQL_STMT *stmt, + MYSQL_ROW_OFFSET offset); +MYSQL_ROW_OFFSET STDCALL mysql_stmt_row_tell(MYSQL_STMT *stmt); +void STDCALL mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset); +my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt); +my_ulonglong STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt); +unsigned int STDCALL mysql_stmt_field_count(MYSQL_STMT *stmt); + +my_bool STDCALL mysql_commit(MYSQL * mysql); +my_bool STDCALL mysql_rollback(MYSQL * mysql); +my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode); +my_bool STDCALL mysql_more_results(MYSQL *mysql); +int STDCALL mysql_next_result(MYSQL *mysql); +void STDCALL mysql_close(MYSQL *sock); + + +/* status return codes */ +#define MYSQL_NO_DATA 100 + +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) + +#ifdef USE_OLD_FUNCTIONS +MYSQL * STDCALL mysql_connect(MYSQL *mysql, const char *host, + const char *user, const char *passwd); +int STDCALL mysql_create_db(MYSQL *mysql, const char *DB); +int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) +#endif +#define HAVE_MYSQL_REAL_CONNECT + +/* + The following functions are mainly exported because of mysqlbinlog; + They are not for general usage +*/ + +#define simple_command(mysql, command, arg, length, skip_check) \ + (*(mysql)->methods->advanced_command)(mysql, command, \ + NullS, 0, arg, length, skip_check) +unsigned long net_safe_read(MYSQL* mysql); + +#ifdef __NETWARE__ +#pragma pack(pop) /* restore alignment */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _mysql_h */ diff --git a/Src/Common/rosecrypt.cpp b/Src/Common/rosecrypt.cpp new file mode 100644 index 0000000..46e42d6 --- /dev/null +++ b/Src/Common/rosecrypt.cpp @@ -0,0 +1,530 @@ +#include +#include +#include "rosecrypt.hpp" +#include + +#define LOGINSERVER_CRYPTTABLE_START_VALUE 0x0042D266 + +//------------------------------------------------------------------------- +// This structure saves values while the tables are generated +//------------------------------------------------------------------------- +struct CCryptTableControlStruct +{ + unsigned CountTableMod[4]; + unsigned char CurTableMod ; + unsigned ValueTableMod[4]; +}; + +//------------------------------------------------------------------------ +// This structure is used by the encryption routines +//------------------------------------------------------------------------- +typedef unsigned long long int u64; + +struct CCryptBuffer +{ + u64 AddBufferLen : 11; + u64 Command : 11; + u64 AddTableValue : 11; + u64 EncryptAddValue : 4; + u64 EncryptValue : 3; +}; + +//------------------------------------------------------------------------- +// This table is used to calculate a checksum (size of 256 bytes) +//------------------------------------------------------------------------- +const unsigned char CheckSumTable[] = + "\x00\x5E\xBC\xE2\x61\x3F\xDD\x83\xC2\x9C\x7E\x20\xA3\xFD\x1F\x41" + "\x9D\xC3\x21\x7F\xFC\xA2\x40\x1E\x5F\x01\xE3\xBD\x3E\x60\x82\xDC" + "\x23\x7D\x9F\xC1\x42\x1C\xFE\xA0\xE1\xBF\x5D\x03\x80\xDE\x3C\x62" + "\xBE\xE0\x02\x5C\xDF\x81\x63\x3D\x7C\x22\xC0\x9E\x1D\x43\xA1\xFF" + "\x46\x18\xFA\xA4\x27\x79\x9B\xC5\x84\xDA\x38\x66\xE5\xBB\x59\x07" + "\xDB\x85\x67\x39\xBA\xE4\x06\x58\x19\x47\xA5\xFB\x78\x26\xC4\x9A" + "\x65\x3B\xD9\x87\x04\x5A\xB8\xE6\xA7\xF9\x1B\x45\xC6\x98\x7A\x24" + "\xF8\xA6\x44\x1A\x99\xC7\x25\x7B\x3A\x64\x86\xD8\x5B\x05\xE7\xB9" + "\x8C\xD2\x30\x6E\xED\xB3\x51\x0F\x4E\x10\xF2\xAC\x2F\x71\x93\xCD" + "\x11\x4F\xAD\xF3\x70\x2E\xCC\x92\xD3\x8D\x6F\x31\xB2\xEC\x0E\x50" + "\xAF\xF1\x13\x4D\xCE\x90\x72\x2C\x6D\x33\xD1\x8F\x0C\x52\xB0\xEE" + "\x32\x6C\x8E\xD0\x53\x0D\xEF\xB1\xF0\xAE\x4C\x12\x91\xCF\x2D\x73" + "\xCA\x94\x76\x28\xAB\xF5\x17\x49\x08\x56\xB4\xEA\x69\x37\xD5\x8B" + "\x57\x09\xEB\xB5\x36\x68\x8A\xD4\x95\xCB\x29\x77\xF4\xAA\x48\x16" + "\xE9\xB7\x55\x0B\x88\xD6\x34\x6A\x2B\x75\x97\xC9\x4A\x14\xF6\xA8" + "\x74\x2A\xC8\x96\x15\x4B\xA9\xF7\xB6\xE8\x0A\x54\xD7\x89\x6B\x35"; + +//---------------------------------------------------------------------------- +// Private function, that generates the next value for the tables +//---------------------------------------------------------------------------- +unsigned ctGenerateNextValue( CCryptTable *CryptTables, CCryptTableControlStruct *ControlStruct ) +{ + // Get the last value depending on the current TableMod. + int Value = ControlStruct->ValueTableMod[ ControlStruct->CurTableMod ]; + + // Update generatecount, depending on the current TableMod + ControlStruct->CountTableMod [ ControlStruct->CurTableMod ]; + + // Calculate and return the next value + switch ( ControlStruct->CurTableMod ) + { + case 0: + Value = (int(Value * 0x8088405)) + 1; + ControlStruct->ValueTableMod[ 0 ] = Value; + return int(Value + (Value<0?0xFFFF:0)) >> 0x10; + case 1: + Value = (0x3039 - (int(Value * 0x3E39B193))) & 0x7FFFFFFF; + ControlStruct->ValueTableMod[ 1 ] = Value; + return Value; + case 2: + Value = (int(Value * 0x41C64E6D) + 0x3039); + ControlStruct->ValueTableMod[ 2 ] = Value; + return int(Value + (Value<0?0xFFFF:0)) >> 0x10; + case 3: + Value = (int(Value * 0x343FD) + 0x269EC3) & 0x7FFFFFFF; + ControlStruct->ValueTableMod[ 3 ] = Value; + return int(Value + (Value<0?0xFFFF:0)) >> 0x10; + default: + return 0; + } + + return 0; +} + +//------------------------------------------------------------------------- +// Private functions, that searches a value in an existing table to +// check if this value already exists. +//------------------------------------------------------------------------- +bool ctValueExistsInTable( CCryptTable *CryptTables, unsigned char Table, unsigned Value, unsigned short EntryCount ) +{ + for ( unsigned short i=0; iTables[ Table ][ i ] == Value ) return true; + return false; +} + +//------------------------------------------------------------------------- +// This function generates a new crypt table depending on the ModValue +// parameter. +//------------------------------------------------------------------------- +void GenerateCryptTables( CCryptTable* &CryptTables, unsigned ModValue ) +{ + CCryptTableControlStruct ControlStruct; + unsigned char TableFuncList[ 17 ]; + unsigned char table; + unsigned short entry; + unsigned value; + int ivalue; + + // Allocate memory for structure + CryptTables = (CCryptTable*)malloc( sizeof( CCryptTable ) ); + CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned ) ); + + // Prepare values + ModValue = (int(ModValue * 0x41C64E6D) + 0x3039); + ModValue = int(ModValue + ((int)ModValue<0?0xFFFF:0)) >> 0x10; + + // Startvalue for checksum variable for each user + CryptTables->EncryptionStartValue = ModValue; + + // set startvalues + for ( table=0; table<4; ++table ) + { + ControlStruct.CountTableMod[ table ] = 0; + ControlStruct.ValueTableMod[ table ] = ModValue; + } + + // Generate TableMod list for creation + ControlStruct.CurTableMod = 0; + for ( table=0; table<0x11; ++table ) + { + ivalue = ctGenerateNextValue( CryptTables, &ControlStruct ) & 0x80000003; + if ( ivalue<0 ) ivalue = ( ( ivalue - 1 ) | 0xFFFFFFFC ) + 1; + TableFuncList[ table ] = ( unsigned char )ivalue; + } + + // Create tables + for ( table=0; table<16; ++table ) + { + // Prepare creation loop for current table + CryptTables->Tables[ table ] = (unsigned*)calloc( 0x800, sizeof( unsigned ) ); + ControlStruct.CurTableMod = TableFuncList[ table ] & 3; + + // Generate current table + entry = 0; + do + { + // Generate next value + value = ctGenerateNextValue( CryptTables, &ControlStruct ); + + // Check, if value already exist in table 0 + if ( ctValueExistsInTable( CryptTables, 0, value, entry ) ) continue; + + // Save value to memory + CryptTables->Tables[ table ][ entry ] = value; + + ++entry; + } while ( entry<0x800 ); + } + + // Generate additional table + CryptTables->AddTable = (unsigned short*)calloc( 0x200, sizeof( unsigned short ) ); + ControlStruct.CurTableMod = table = (unsigned char)(TableFuncList[ 0x10 ]) & 3; + entry = 0; + do + { + // Generate next value + value = ctGenerateNextValue( CryptTables, &ControlStruct ) & 0x7FF; + + // Check, if value already exist + for ( ivalue=0; ivalue<=(int)entry; ++ivalue ) + if ( (unsigned)CryptTables->AddTable[ivalue] == value ) { value=0xFFFFFFFF; break; } + + // If value exist, continue + if ( value==0xFFFFFFFF ) continue; + + // Save value + CryptTables->AddTable[ entry++ ] = value; + } while ( entry<0x200 ); +} + +//------------------------------------------------------------------------- +// This function generates a new crypt table used by the Login Server +//------------------------------------------------------------------------- +void GenerateLoginServerCryptTables( CCryptTable* &CryptTables ) +{ + CCryptTableControlStruct ControlStruct; + unsigned char table; + unsigned short entry; + unsigned value; + int ivalue; + + // Allocate memory for structure + CryptTables = (CCryptTable*)malloc( sizeof( CCryptTable ) ); + CryptTables->Tables = (unsigned **)calloc( 16, sizeof( unsigned ) ); + + // Set default startvalues + for ( table=0; table<4; ++table ) + { + ControlStruct.CountTableMod[ table ] = 0; + ControlStruct.ValueTableMod[ table ] = LOGINSERVER_CRYPTTABLE_START_VALUE; + } + + // Another startvalue + CryptTables->EncryptionStartValue = LOGINSERVER_CRYPTTABLE_START_VALUE; + + // Generate default tables + for ( table=0; table<16; ++table ) + { + // Prepare creation loop for current table + ControlStruct.CurTableMod = table & 3; + CryptTables->Tables[ table ] = (unsigned*)calloc( 0x800, sizeof( unsigned ) ); + + entry = 0; + do + { + // Generate next value depend on CurTableMod + value = ctGenerateNextValue( CryptTables, &ControlStruct ); + + // Only take value, if it doesn't exist in table 0 already + if ( ctValueExistsInTable( CryptTables, 0, value, entry ) ) continue; + + // Save value to structure + CryptTables->Tables[ table ][ entry ] = value; + + ++entry; + } while ( entry<0x800 ); + } + + // Create additional table + CryptTables->AddTable = (unsigned short*)calloc( 0x200, sizeof( unsigned short ) ); + entry = 0; + do + { + // Generate next value for table + ControlStruct.CountTableMod[1]; + ivalue = ControlStruct.ValueTableMod[1]; + ivalue *= 0x3E39B193; + value = (0x3039 - (unsigned)ivalue) & 0x7FFFFFFF; + ControlStruct.ValueTableMod[1] = value; + + // Don't save, if value already exist + value &= 0x7FF; + for ( ivalue=0; ivalue<=(int)entry; ++ivalue ) + if ( (unsigned)CryptTables->AddTable[ivalue] == value ) { value=0xFFFFFFFF; break; } + + // If value already exist, continue + if ( value==0xFFFFFFFF ) continue; + + // Save value to structure + CryptTables->AddTable[ entry ] = value; + + ++entry; + } while ( entry<0x200 ); +} + +//------------------------------------------------------------------------- +// This function frees the memory of a crypt table +//------------------------------------------------------------------------- +void FreeCryptTables( CCryptTable* &CryptTables ) +{ + unsigned char c; + + // Free every table + for ( c=0; c<16; ++c ) + free( CryptTables->Tables[ c ] ); + + // Free memory + free( CryptTables->AddTable ); + free( CryptTables->Tables ); + free( CryptTables ); + + CryptTables = NULL; +} + +//------------------------------------------------------------------------- +// This function decrypts a received 6-byte buffer and returns the full +// size of the incoming packet (including this 6-byte header). +// If the buffer is incorrect, this function returns a zero. +//------------------------------------------------------------------------- +int DecryptBufferHeader( CCryptStatus *ri, CCryptTable *CryptTables, unsigned char *Buffer ) +{ + CCryptBuffer TempBuffer; + unsigned BigBuffer; + unsigned char c; + + // Read "AddTableValue" from buffer + BigBuffer = *((unsigned*)Buffer); + TempBuffer.AddTableValue = ( ( Buffer[4] >> 0x03 ) & 0x0007 ) | ( ( BigBuffer ) & 0x0038 ) | \ + ( ( BigBuffer >> 0x03 ) & 0x01C0 ) | ( ( BigBuffer >> 0x11 ) & 0x0600 ); + + // Decrypt header in the buffer + for ( c=0; c<5; ++c ) + Buffer[c] ^= CryptTables->Tables[ c ][ TempBuffer.AddTableValue ]; + + BigBuffer = *((unsigned*)Buffer); + TempBuffer.EncryptValue = ( BigBuffer >> 0x0F ) & 0x0007 ; + TempBuffer.Command = ( ( BigBuffer >> 0x0C ) & 0x0007 ) | ( ( BigBuffer << 0x03 ) & 0x0038 ) | \ + ( ( Buffer[4] << 0x06 ) & 0x01C0 ) | ( ( BigBuffer >> 0x13 ) & 0x0600 ); + TempBuffer.AddBufferLen = ( ( BigBuffer >> 0x06 ) & 0x0007 ) | ( ( BigBuffer >> 0x0F ) & 0x0038 ) | \ + ( ( BigBuffer >> 0x11 ) & 0x01C0 ) | ( ( Buffer[4] << 0x03 ) & 0x0600 ); + TempBuffer.EncryptAddValue = ( ( BigBuffer >> 0x1E ) & 0x0003 ) | ( ( BigBuffer >> 0x13 ) & 0x000C ); + + // Size correct? + if ( TempBuffer.AddBufferLen < 0x06 || TempBuffer.AddBufferLen > 0x400 ) + return 0; + + // Header Checksum correct? ~ not implented yet ~ +/* + if ( ((unsigned)( TempBuffer.AddTableValue + TempBuffer.EncryptAddValue ) & 7 ) != \ + ((unsigned)( TempBuffer.EncryptValue ) & 7 ) ) + return 0; +*/ + + // Save CryptBuffer temporary to buffer for later use + *((unsigned*)Buffer) = *((unsigned *)&TempBuffer) ; + Buffer[4] = ((unsigned char*)&TempBuffer)[4]; + + // Update encryption value + ri->CurEncryptionValue = ( ri->CurEncryptionValue * 0x343FD ) + 0x269EC3; + + // Everything went ok + return TempBuffer.AddBufferLen; +} + +//------------------------------------------------------------------------- +// This function will decrypt the full buffer after it is received. +// If it fails, it will return FALSE. +//------------------------------------------------------------------------- +bool DecryptBufferData( CCryptTable *CryptTables, unsigned char *Buffer ) +{ + CCryptBuffer TempBuffer; + unsigned short c, BufferLen; + unsigned char CheckSum; + + // Read CryptBuffer from received buffer + *((unsigned *)&TempBuffer) = *((unsigned*)Buffer); + ((unsigned char*)&TempBuffer)[4] = Buffer[4]; + + // Calculate CheckSum for bufferheader + for ( CheckSum=0, c=0; c<5; ++c ) + CheckSum = CheckSumTable[ (unsigned char)Buffer[ c ] ^ CheckSum ]; + + // Calculate real buffer length and check it + BufferLen = (unsigned short)(TempBuffer.AddBufferLen - TempBuffer.EncryptValue); + if ( BufferLen < 0x06 ) return false; + + // Decrypt buffer data + if ( BufferLen > 0x06 ) + { + for ( c=6; cTables[ (TempBuffer.EncryptAddValue + c) & 0x000F ] \ + [ (TempBuffer.AddTableValue + c) & 0x07FF ]; + CheckSum = CheckSumTable [ (unsigned char)Buffer[ c ] ^ CheckSum ]; + } + } + + // Check CheckSum + if ( Buffer[5] != CheckSum ) + return false; + + // Complete buffer + *((unsigned short*)(Buffer )) = BufferLen ; + *((unsigned short*)(Buffer+2)) = TempBuffer.Command; + + return true; +} + +//------------------------------------------------------------------------- +// This functions will encrypt a buffer. The buffer must have at least a +// size of 6 bytes. The first two bytes must be the size of the buffer. +//------------------------------------------------------------------------- +void EncryptBuffer( CCryptTable *CryptTables, unsigned char *Buffer ) +{ + CCryptBuffer TempBuffer; + unsigned short c; + unsigned char CheckSum; + unsigned EncryptValue; + + // Generate some random values for encrypting + TempBuffer.AddTableValue = (rand( ) % 0x800); + TempBuffer.EncryptAddValue = (rand( ) % 0x10 ); + TempBuffer.EncryptValue = TempBuffer.AddTableValue + TempBuffer.EncryptAddValue; + + // Fill other fields with values from Buffer + TempBuffer.AddBufferLen = *((unsigned short*)(Buffer )); + TempBuffer.Command = *((unsigned short*)(Buffer+2)); + + // Build new bufferheader + + Buffer[4] = (((unsigned)TempBuffer.AddBufferLen ) & 0x00000007 ) ^ \ + (((unsigned)TempBuffer.Command >> 0x03 ) & 0x000000C0 ); + EncryptValue = (((unsigned)TempBuffer.AddBufferLen >> 0x03 ) & 0x00000007 ); + EncryptValue |= (((unsigned)TempBuffer.AddBufferLen << 0x06 ) & 0x00007000 ); + EncryptValue |= (((unsigned)TempBuffer.AddBufferLen << 0x13 ) & 0x30000000 ); + EncryptValue |= (((unsigned)TempBuffer.Command << 0x17 ) & 0x03800000 ); + EncryptValue |= (((unsigned)TempBuffer.Command << 0x0F ) & 0x001C0000 ); + EncryptValue |= (((unsigned)TempBuffer.Command ) & 0x000001C0 ); + EncryptValue |= (((unsigned)TempBuffer.EncryptValue << 0x09 ) & 0x00000E00 ); + EncryptValue |= (((unsigned)TempBuffer.EncryptAddValue << 0x1A ) & 0x0C000000 ); + EncryptValue |= (((unsigned)TempBuffer.EncryptAddValue << 0x1C ) & 0xC0000000 ); + + *((unsigned*)Buffer) = EncryptValue; + + // Encrypt bufferheader + for ( CheckSum=0, c=0; c<5; ++c ) + { + CheckSum = CheckSumTable [ ((unsigned char*)&TempBuffer)[ c ] ^ CheckSum ]; + Buffer[c] ^= CryptTables->Tables[ c ][ TempBuffer.AddTableValue ]; + } + + + // Encrypt bufferdata if exists + if ( TempBuffer.AddBufferLen > 0x06 ) + { + for ( c=6; cTables[ (TempBuffer.EncryptAddValue + c) & 0x000F ] \ + [ (TempBuffer.AddTableValue + c) & 0x07FF ]; + } + } + Buffer[ 5 ] = CheckSum; + + // Complete bufferheader + EncryptValue = *((unsigned*)Buffer) & 0xFF9C7FC7; + EncryptValue |= (((unsigned)TempBuffer.AddTableValue << 0x03 ) & 0x00000038 ); + EncryptValue |= (((unsigned)TempBuffer.AddTableValue << 0x09 ) & 0x00038000 ); + EncryptValue |= (((unsigned)TempBuffer.AddTableValue << 0x0C ) & 0x00600000 ); + Buffer[ 4 ] = (Buffer[4]&0xC7) | (((unsigned)TempBuffer.AddTableValue ) & 0x38 ); + *((unsigned*)Buffer) = EncryptValue; +} + +//------------------------------------------------------------------------- +bool CryptISCPak( unsigned char* pak ) +{ + unsigned paksize = *((unsigned short*)&pak[0]); + for( unsigned short i = 2; i < paksize; i ++ ) + pak[i] ^= 0x81 * i * paksize; + return true; +} + +#ifdef USE124 +// This encryption code was taken from the dRose source. Not sure who to credit other than them. +// To use 124 you will need the 124 trose.exe in your server directory. +int buildChecksum(char* csum, char* path) +{ + char buf[0x200]; + int bytesread=0; + FILE* fh; + + if (path==0) path = "TRose.exe"; + fh = fopen( path, "rb" ); + if ( fh == NULL ) return -1; + + fseek(fh, -512, SEEK_END); + bytesread = fread(buf, 1, 0x200, fh); + fclose(fh); + + if (bytesread!=0x200) return -2; + + short keylen = buf[0x0f]; + char* key = new char[keylen+1]; + unsigned short key_ct[] = {0x94, 0x20, 0x5F, 0x53, 0x14, 0xF2, 0x61, 0x11, 0x54, 0xFF, 0x72, 0x99, 0x44, 0xF4, 0xA1, 0xAA, 0xBA, 0xC2, 0x54, 0x52, 0x6F, 0x73, 0x65, 0x2E, 0x65, 0x78, 0x65}; + + for(int i=0; i0; i--) { + int tmp = (key[i-1]-0x30)*multiplier; + multiplier *= 0x0a; + csumlen += tmp; + } + + delete [] key; + + unsigned short csumtable[] = {0x34, 0x5C, 0x3D, 0x52, 0xCE, 0xF3, 0x12, 0xD4, 0x05, 0x91, 0xEE, 0xFF, 0x49, 0xC2, 0xD2}; + + int tmp=0; + for(int i=0; i +#include "seh.hpp" + +#ifdef __WIN32__ + +EXCEPTION_DISPOSITION __SEH_HANDLER::ExceptionRouter(PEXCEPTION_RECORD pRecord, + __SEH_EXCEPTION_REGISTRATION* pReg, + PCONTEXT pContext, + PEXCEPTION_RECORD pRecord2) +{ + return pReg->exthandler->ExceptionHandler(pRecord, pReg, pContext, pRecord2); +} + +EXCEPTION_DISPOSITION __SEH_HANDLER::ExceptionHandler(PEXCEPTION_RECORD pRecord, + __SEH_EXCEPTION_REGISTRATION* pReg, + PCONTEXT pContext, + PEXCEPTION_RECORD pRecord2) +{ + /*std::memcpy(&excContext, pContext, sizeof(_CONTEXT)); + std::memcpy(&excRecord, pRecord, sizeof(_EXCEPTION_RECORD));*/ + memcpy(&excContext, pContext, sizeof(_CONTEXT)); + memcpy(&excRecord, pRecord, sizeof(_EXCEPTION_RECORD)); + longjmp(context, 1); +} + +#endif + + diff --git a/Src/Common/seh.hpp b/Src/Common/seh.hpp new file mode 100644 index 0000000..2f9362a --- /dev/null +++ b/Src/Common/seh.hpp @@ -0,0 +1,96 @@ + +#ifndef __FV_SEH_H__ +#define __FV_SEH_H__ + +#ifdef __WIN32__ +#include +#include +#endif + +#include +//#include + +//NAMESPACE_BEGIN(PU) +//NAMESPACE_BEGIN(SEH) + +#ifdef __WIN32__ + +class __SEH_HANDLER; + +typedef struct tag__SEH_EXCEPTION_REGISTRATION +{ + tag__SEH_EXCEPTION_REGISTRATION* prev; + PEXCEPTION_HANDLER handler; + __SEH_HANDLER* exthandler; +} __SEH_EXCEPTION_REGISTRATION; + + +class __SEH_HANDLER +{ +public: + __SEH_HANDLER() { } + static EXCEPTION_DISPOSITION ExceptionRouter(PEXCEPTION_RECORD pRecord, + __SEH_EXCEPTION_REGISTRATION* pReg, + PCONTEXT pContext, + PEXCEPTION_RECORD pRecord2); + + virtual EXCEPTION_DISPOSITION ExceptionHandler(PEXCEPTION_RECORD pRecord, + __SEH_EXCEPTION_REGISTRATION* pReg, + PCONTEXT pContext, + PEXCEPTION_RECORD pRecord2); + + jmp_buf context; + EXCEPTION_RECORD excRecord; + CONTEXT excContext; +}; + +#define __seh_try \ +{ \ + __SEH_EXCEPTION_REGISTRATION _lseh_er; \ + __SEH_HANDLER _lseh_handler; \ + \ + _lseh_er.handler = \ + reinterpret_cast(__SEH_HANDLER::ExceptionRouter); \ + _lseh_er.exthandler = &_lseh_handler; \ + asm volatile ("movl %%fs:0, %0" : "=r" (_lseh_er.prev)); \ + asm volatile ("movl %0, %%fs:0" : : "r" (&_lseh_er)); \ + int _lseh_setjmp_res = setjmp(_lseh_handler.context); \ + while(true) { \ + if(_lseh_setjmp_res != 0) { \ + break; \ + } \ + +#define __seh_finally \ + break; \ + } \ + while(true) { + + +#define __seh_except(rec, ctx) \ + break; \ + } \ + PEXCEPTION_RECORD rec = &_lseh_handler.excRecord; \ + PCONTEXT ctx = &_lseh_handler.excContext; \ + \ + asm volatile ("movl %0, %%fs:0" : : "r" (_lseh_er.prev)); \ + if(_lseh_setjmp_res != 0) + +#define __seh_end } + +#else + +#define __seh_try + +#define __seh_except(rec, ctx) if(false) { + +#define __seh_end } + +#define __seh_finally + +#endif + +//NAMESPACE_END(SEH) +//NAMESPACE_END(PU) + +#endif + diff --git a/Src/Common/sockets.h b/Src/Common/sockets.h new file mode 100644 index 0000000..68f578d --- /dev/null +++ b/Src/Common/sockets.h @@ -0,0 +1,438 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +//#define _CRT_NO_TIME_T +#ifndef __ROSE_SOCKETS__ +#define __ROSE_SOCKETS__ +#define USEIFO +#ifdef _WIN32 + + #include + #define close closesocket + #ifdef FD_SETSIZE + #undef FD_SETSIZE + #endif + #define FD_SETSIZE 1024 +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #define SOCKET int + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + #define PVOID void * + #define ioctlsocket ioctl + #define SOCKADDR struct sockaddr + #define closesocket close +#endif + +#include "mysql.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pthread.h" +#include +#include +#include "log.h" +#include "rosecrypt.hpp" +#include "config.h" +#include "database/database.h" +#pragma comment(lib, "Ws2_32.lib") +// Just some defs for easier use +typedef char SBYTE; +typedef unsigned char BYTE; +typedef short SWORD; +typedef unsigned short WORD; +typedef long SDWORD; +typedef unsigned long DWORD; +typedef long long SQWORD; +typedef unsigned long long QWORD; +typedef unsigned __int8 byte; +typedef unsigned __int16 word; +typedef unsigned __int32 dword; +typedef unsigned __int64 qword; +typedef char* strings; +using std::cout; +using std::endl; +using std::system; +using std::vector; +using std::map; +using std::nothrow; +using std::string; +#define STARTPACKET(p,c,s) CPacket p; p.Command = c; p.Size = s; +#define SETBYTE(p,o,v) p.Buffer[o]=v; +#define SETWORD(p,o,v) *((unsigned short*)&p.Buffer[o])=v; +#define SETDWORD(p,o,v) *((unsigned*)&p.Buffer[o])=v; +#define SETQWORD(p,o,v) *((long long*)&p.Buffer[o])=v; +#define SETFLOAT(p,o,v) *((float*)&p.Buffer[o])=v; + +#define BEGINPACKET(p,c) CPacket p; p.Command = c; p.Size = 6; +#define RESETPACKET(p,c) p.Command = c; p.Size = 6; +#define ADDBYTE(p,v) { p.Buffer[p.Size-6]=v; p.Size+=1; } +#define ADDWORD(p,v) { *((WORD*)&p.Buffer[p.Size-6])=v; p.Size+=2; } +#define ADDDWORD(p,v) { *((unsigned*)&p.Buffer[p.Size-6])=v; p.Size+=4; } +#define ADDQWORD(p,v) { *((QWORD*)&p.Buffer[p.Size-6])=v; p.Size+=8; } +#define ADDFLOAT(p,v) { *((float*)&p.Buffer[p.Size-6])=v; p.Size+=4; } +#define ADDSTRING(p,v) { for(int q=0; q<(int)strlen((char*)v); q++) ADDBYTE(p,(v)[q]); } +#define GETBYTE(p,o) (p.Buffer[o]) +#define GETWORD(p,o) *((WORD*)&p.Buffer[o]) +#define GETDWORD(p,o) *((DWORD*)&p.Buffer[o]) +#define GETQWORD(p,o) *((QWORD*)&p.Buffer[o]) +#define GETFLOAT(p,o) *((float*)&p.Buffer[o]) +#define pi 3.1415926535897932384626433832795 +#define r2d pi/180 +#define ZONE_SIZE 6400 +#define ENTITY_NO_TYPE 0 +#define ENTITY_PLAYER 1 +#define ENTITY_MONSTER 2 +#define ENTITY_NPC 3 +#define ENTITY_SUMMON 4 +#define ENTITY_DROP 5 + +// Structures +// Packet information +struct CPacket +{ + unsigned short Size; // Packet size + unsigned short Command; // Packet command + unsigned short Unused; // unused + unsigned char Buffer[0x400]; // Packet data //0x600 + + CPacket( unsigned short mycommand=0, unsigned short mysize=6, unsigned short myunused=0 ); + ~CPacket( ); + void StartPacket( unsigned short mycommand, unsigned short mysize=6, unsigned short myunused=0 ); + void AddByte( unsigned char value ); + void AddWord( unsigned short value ); + void AddDWord( unsigned value ); + void AddQWord( unsigned long long value ); + void AddFloat( float value ); + void AddString( char* value ); + void SetByte( unsigned short pos, unsigned char value ); + void SetWord( unsigned short pos, unsigned short value ); + void SetDWord( unsigned short pos, unsigned value ); + void SetQWord( unsigned short pos, unsigned long long value ); + void SetFloat( unsigned short pos, float value ); + unsigned char GetByte( unsigned short pos ); + unsigned short GetWord( unsigned short pos ); + unsigned GetDWord( unsigned short pos ); + unsigned long long GetQWord( unsigned short pos ); + float GetFloat( unsigned short pos ); + char* GetString( unsigned short pos ); + + // Functions added by Drakia + template void Add( T value ) { + *((T*)&Buffer[Size]) = value; + Size += sizeof(T); + } + void AddString( char* value, bool NullTerminate ) { + for (dword i = 0; i < strlen((const char*)value); i++) { + Add(value[i]); + } + if (NullTerminate) Add(0); + } + template void AddString( char* value ) { + Add(strlen((const char*)value)); + AddString(value, false); + } + void AddBytes( byte* value, dword len ) { + for (dword i = 0; i < len; i++) + Add((byte)value[i]); + } +}; + +// SQL Configuration +struct CROSEServerConfigSQL +{ + char* pcServer; // IP + char* pcDatabase; // Database + char* pcUserName; // UserName + char* pcPassword; // Password + int pcPort; // port +}; + +// Server configuration +struct CROSEServerConfig +{ + // GLOBAL + CROSEServerConfigSQL SQLServer; // SQL Information + bool usethreads; // Use Threads (in sockets) + bool Verbose; // print server information? + unsigned MaxConnections; // Max connections + unsigned UserPerThread; // User per thread + bool AllowMultipleConnections; // Allow multiple connection from same ip? + bool StayInServerLoop; // Server actived? + UINT ServerID; //id from this server + UINT ParentID; //id from the server to connect + char* ServerName; //Server name + UINT ServerType; //0 login - 1 server(char) - 2 channel(world) + + // LOGINSERVER + unsigned int MinimumAccessLevel; // Minimum access level + char* LoginIP; // LoginIP [external] + unsigned int LoginPort; // Login port + unsigned int LoginPass; + bool CreateLoginAccount; // Create account in login if not exist? + bool Testserver; // channel for test ? + + // CHARSEVER + unsigned long int DeleteTime; // Delete time + char* CharIP; // CharIP + unsigned int CharPort; // Char port + unsigned int CharPass; + unsigned int CharsPort; + + // WORLDSERVER + char* WorldIP; + unsigned int WorldPort; + unsigned int WorldsPort; + unsigned int WorldPass; + UINT EXP_RATE; + UINT DROP_RATE; + UINT BASE_DROP_CHANCE; + UINT ZULY_RATE; + UINT AUTOSAVE; + UINT SAVETIME; + UINT MapDelay; + UINT VisualDelay; + UINT WorldDelay; + UINT DeathDelay; //Set a timer delay so that we don't have a problem with monster death + UINT AIWatch; //Set an AI script number to watch for. Debug only + UINT SpawnType; + + char* WELCOME_MSG; + UINT DROP_TYPE; + int MaxStat; + int Partygap; + int FairyMode; + int FairyStay; + int FairyWait; + int FairyMax; + int ScrollFairy; + int FairyTestMode; + int PlayerDmg; + int MonsterDmg; + int Cfmode; + int BlueChance; + int StatChance; + int SlotChance; + int RefineChance; + int Rare_Refine; + int KillOnFail; + int MaxLevel; + int DropMultiple; + int KTPointRate; + UINT AttkMult; + UINT SkillMult; + UINT DropDelay; + + + // COMMAND LEVELS + int Command_Go; + int Command_Who; + int Command_Who2; + int Command_Broadcast; + int Command_Mute; + int Command_Tele; + int Command_Level; + int Command_LevelUp; + int Command_TeleToMe; + int Command_Save; + int Command_Reload; + int Command_Ann; + int Command_SSpawn; + int Command_Set; + int Command_ESpawn; + int Command_DSpawn; + int Command_DelSpawn; + int Command_Pak; + int Command_Pak2; + int Command_Pakm; + int Command_Info; + int Command_Exp; + int Command_Mon; + int Command_Kick; + int Command_Job; + int Command_Cha; + int Command_Item; + int Command_Drop; + int Command_GiveZuly; + int Command_Npc; + int Command_GiveFairy; + int Command_ManageFairy; + int Command_ChangeFairyWait; + int Command_ChangeFairyStay; + int Command_ChangeFairyTestMode; + int Command_Move; + int Command_Goto; + int Command_PlayerInfo; + int Command_Give2; + int Command_Ban; + int Command_Ani; + int Command_Summon; + int Command_ReloadQuest; + int Command_Shutdown; + int Command_DQuest; + int Command_IQuest; + int Command_ShopType; + int Command_Stat; + int Command_KillInRange; + int Command_GoToMap; + int Command_Heal; + int Command_ServerInfo; + int Command_TargetInfo; + int Command_Hide; + int Command_Class; + int Command_Monster; + int Command_GlobalTime; + int Command_Here; + int Command_Face; + int Command_Hair; + int Command_Pvp; + int Command_go; + int Command_Convert; + int Command_Rate; + int Command_Moveto; + int Command_Settime; + int Command_Transx; + int Command_Partylvl; + int Command_Pdmg; + int Command_Mdmg; + int Command_Rules; + int Command_Cfmode; + + char* Command_GlobalPrefix; + +}; + + +// Classes +// Base class +class CBaseSocket +{ + public: + CBaseSocket( ); // Constructor + virtual ~CBaseSocket( ); // Destructor + void CloseSocket( void ); // Close Socket + SOCKET sock; // This is our socket + SOCKET sckISC; // Raven0123 + SOCKET sckISCII; + bool isActive; // Is this socket connected? + bool isserver; + CCryptTable* CryptTable; // This is for decrypting incomming packets +#ifdef USE124 + char* csum; + int csumlen; +#endif + string ClientIP; // Client IP + char ClientSubNet[12]; // Lan Subnet +}; +// Client class +class CClientSocket : public CBaseSocket +{ + public: + CClientSocket( ); // Client Constructor + ~CClientSocket( ); // Client Destructor + bool ReceiveData( ); // Receive packet + void SendPacket( CPacket *P ); // Send Packet + void SendPacketCpy( CPacket *P ); // Send a packet without encrypting the orginal packet + CCryptStatus CryptStatus; // Status of the encryption + class CServerSocket* GS; // Pointer to Server class + bool ISCThread(); // Raven0123 + struct sockaddr_in clientinfo; + void* player; +#ifdef USE124 + char* ct; +#endif + private: + unsigned short PacketSize; // Size of the current packet + unsigned short PacketOffset; // Current offset of the data + unsigned char Buffer[ 0xFFF ]; // Buffer to receive data into + unsigned char tempBuffer[0xFFF]; +}; + +// Server class +class CServerSocket : public CBaseSocket +{ +private: + //void CryptISCPak( char* pak ); // Raven0123 + //void ISCThread(); // Raven0123 + //SOCKET sckISC; // Raven0123 + +public: + void CryptISCPak( char* pak ); // Raven0123 + CServerSocket( ); // Constructor + ~CServerSocket( ); // Destructor + + bool StartServer( ); // Start Server + void FillFDS( fd_set* fds ); // Create FDS table + void HandleClients( fd_set* fds ); // Handle Clients + void AddUser( SOCKET sock, sockaddr_in* ClientInfo, bool server ); // Add User + void DisconnectClient( CClientSocket* thisclient ); // Disconnect Client + bool DoSQL(char *Format, ...); + + // Virtual Functions + virtual void LoadEncryption( ); //load encryption + virtual void ServerLoop( ); // Main Loop + virtual CClientSocket* CreateClientSocket( ); // Create a client + virtual void DeleteClientSocket( CClientSocket* thisclient ); // Delete client + virtual bool OnServerReady( ); // Executed with server is ready + virtual void OnServerStep( ); // Executed + virtual void OnServerDie( ); + virtual bool OnReceivePacket( CClientSocket* thisclient, CPacket* P ); + virtual void ReceivedISCPacket( CPacket* pak ); // Raven0123 + virtual bool OnClientConnect( CClientSocket* thisclient ); + virtual void OnClientDisconnect( CClientSocket* thisclient ); + + // Variables + CROSEServerConfig Config; // Hold server configuration + int maxfd; + MYSQL *mysql; + virtual bool Ping( ); // Used to ping the mysql server + vector ClientList; + UINT ConnectedClients; + unsigned short port; + struct sockaddr_in ain; + struct sockaddr_in sain; + + pthread_t threads[65535]; + + void SendISCPacket( CPacket* pak ); // Raven0123 +}; +PVOID ClientMainThread( PVOID ); // Handle clients +bool InitWinSocket ( void ); // Init windows sockets (wsa) +void CloseWinSocket( void ); // Close Windows sockets (wsa) +// ----------------------------------------------------------------------------------------- + +#endif diff --git a/Src/Login Server/datatype.h b/Src/Login Server/datatype.h new file mode 100644 index 0000000..6b462a2 --- /dev/null +++ b/Src/Login Server/datatype.h @@ -0,0 +1,28 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +// Server list +struct CServers +{ + UINT id; + char* ip; + UINT port; + SOCKET sock; +}; diff --git a/Src/Login Server/extrafunction.cpp b/Src/Login Server/extrafunction.cpp new file mode 100644 index 0000000..1e57503 --- /dev/null +++ b/Src/Login Server/extrafunction.cpp @@ -0,0 +1,21 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" diff --git a/Src/Login Server/loginclient.cpp b/Src/Login Server/loginclient.cpp new file mode 100644 index 0000000..cf68f53 --- /dev/null +++ b/Src/Login Server/loginclient.cpp @@ -0,0 +1,37 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" + +// Constructor +CLoginClient::CLoginClient( ) +{ + userid = 0; + isLoggedIn = false; + accesslevel = 0; + username.reserve(17); + password.reserve(33); +} + +// Deconstructor +CLoginClient::~CLoginClient( ) +{ + +} diff --git a/Src/Login Server/loginpackets.cpp b/Src/Login Server/loginpackets.cpp new file mode 100644 index 0000000..1dd7bf9 --- /dev/null +++ b/Src/Login Server/loginpackets.cpp @@ -0,0 +1,240 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" + +// Send Server encryption +bool CLoginServer::pakEncryptionRequest( CLoginClient* thisclient, CPacket* P ) +{ + try + { + thisclient->CryptStatus.CurAddValue = rand( ) | (rand( )<<16); + STARTPACKET( pak, 0x7FF, 11 ); + SETBYTE ( pak, 0x00, 0x02 ); + SETDWORD ( pak, 0x01, thisclient->CryptStatus.CurAddValue ); + thisclient->SendPacket( &pak ); + return true; + } + catch(...) + { + Log(MSG_ERROR, "pakEncryptionRequest"); + } +} + +// Packet when user login (chck user and pass) +bool CLoginServer::pakUserLogin( CLoginClient* thisclient, CPacket* P ) +{ + Log(MSG_DEBUG,"User Login request received"); + try + { + if ( thisclient->isLoggedIn ) return false; + MYSQL_ROW row; + thisclient->username.assign( (const char*)&P->Buffer, 32, (P->Size-6-32)>16?16:P->Size-6-32 ); + thisclient->password.assign( (const char*)&P->Buffer, 0, 32 ); + BEGINPACKET( pak, 0x708 ); + MYSQL_RES *result = DB->QStore( "SELECT id,password,accesslevel,online FROM accounts WHERE username='%s'", thisclient->username.c_str() ); + if(result == NULL) + { + Log(MSG_WARNING,"DB Query returned a NULL result"); + return false; + } + if( mysql_num_rows( result ) == 1 ) + { + row = mysql_fetch_row(result); + int res = 0; + #ifdef _WIN32 + res = _stricmp( row[1], thisclient->password.c_str() ); + #else + res = strcasecmp( row[1], thisclient->password.c_str() ); + #endif + if ( res == 0 ) + { + if(atoi(row[3])==1) + { // characters is already logged + Log(MSG_WARNING, "Account %s try re-login", thisclient->username.c_str() ); + ADDBYTE( pak, 4 ); + ADDDWORD( pak, 0 ); + thisclient->SendPacket( &pak ); + DB->QFree( ); + DB->QExecute( "update accounts set login_count=1 WHERE username='%s'", thisclient->username.c_str()); + return true; + } + thisclient->accesslevel = atoi(row[2]); + if( thisclient->accesslevel < Config.MinimumAccessLevel ) + { //The server are under inspection + Log(MSG_WARNING,"User accesslevel %i is lower than minimum %i",thisclient->accesslevel,Config.MinimumAccessLevel); + ADDBYTE( pak, 0 ); + ADDDWORD( pak, 0 ); + thisclient->SendPacket( &pak ); + DB->QFree( ); + return true; + } + if ( thisclient->accesslevel > 0 ) + { + thisclient->userid = atoi(row[0]); + thisclient->isLoggedIn = true; + DB->QFree( ); + //OK! + Log(MSG_WARNING,"User %s passed to charserver",thisclient->username.c_str()); + ADDDWORD( pak, 0x0c000000 );//ADDDWORD( pak, 0x6f000000 ); + ADDBYTE( pak, 0 ); + result = DB->QStore( "SELECT id,name FROM channels WHERE owner=0" ); + if(result==NULL) return false; + while( row = mysql_fetch_row(result) ) + { + if (Config.Testserver){ADDBYTE( pak, 63 + atoi( row[0] ) );} + else{ + ADDBYTE( pak, 48 + atoi( row[0] ) );} + ADDSTRING( pak, row[1] ); + ADDBYTE( pak, 0 ); + ADDDWORD(pak, atoi( row[0] ) ); + } + DB->QFree( ); + } + else + { + //BANNED + Log(MSG_WARNING,"User %s banned",thisclient->username.c_str()); + ADDBYTE( pak, 5 ); + ADDDWORD( pak, 0 ); + DB->QFree( ); + } + } + else + { + //BAD PASSWORD + Log(MSG_WARNING,"User %s attempted to log in with invalid password",thisclient->username.c_str()); + ADDBYTE( pak, 3 ); + ADDDWORD( pak, 0 ); + DB->QFree( ); + } + } + else + { + DB->QFree( ); + if(Config.CreateLoginAccount) + { + if(!DB->QExecute("INSERT into accounts (username,password,accesslevel) values ('%s','%s',100)",thisclient->username.c_str(),thisclient->password.c_str())) + return true; + Log( MSG_INFO, "New Account created '%s'", thisclient->username.c_str() ); + } + //BAD USERNAME + ADDBYTE( pak, 2 ); + ADDDWORD( pak, 0 ); + } + /* + 1 - general error | 4 - your account is already logged + 6 - topup account | 7 - cannot connect to server please try again + 8 - server exceeded | 9 - account have not been verified + 10 - login failed | 11 - ip capacity is full + */ + thisclient->SendPacket ( &pak ); + return true; + } + catch(...) + { + Log(MSG_ERROR, "Error in pakUserLogin"); + } + +} + +// Send server list +bool CLoginServer::pakGetServers( CLoginClient* thisclient, CPacket* P ) +{ + try + { + if( !thisclient->isLoggedIn ) return false; + MYSQL_ROW row; + unsigned servernum = GETDWORD( (*P), 0 ); + MYSQL_RES *result = DB->QStore( "SELECT id,name,connected,maxconnections FROM channels WHERE owner=%i", servernum ); + if(result==NULL) return false; + BEGINPACKET( pak, 0x704 ); + ADDDWORD ( pak, servernum ); + ADDBYTE ( pak, (BYTE)mysql_num_rows( result ) ); //old function + while(row = mysql_fetch_row(result)) + { + UINT connected = atoi(row[2]); + UINT maxconnections = atoi(row[3]); + BYTE id = atoi( row[0] ); + BYTE status = ((100 * connected) / (maxconnections == 0 ? 1 : maxconnections)) & 0xff; + + ADDWORD( pak, id ); + ADDBYTE( pak, 0 ); + ADDWORD( pak, status ); + ADDSTRING( pak, row[1] ); // Name + ADDBYTE( pak, 0 ); + } + DB->QFree( ); + thisclient->SendPacket ( &pak ); + return true; + } + catch(...) + { + Log(MSG_ERROR, "Error in pakGetServers"); + } +} + +// Send server IP +bool CLoginServer::pakGetIP( CLoginClient* thisclient, CPacket* P ) +{ + try + { + if (!thisclient->isLoggedIn) return false; + + MYSQL_ROW row; + + DWORD servernum = GETDWORD( (*P), 0 ); + BYTE channelnum = GETBYTE( (*P), 4 ); + + + BEGINPACKET( pak, 0x70a ); + + if(!DB->QExecute( "UPDATE accounts SET lastsvr=%i,lastip='%s' WHERE id=%i", channelnum, inet_ntoa( thisclient->clientinfo.sin_addr ), thisclient->userid)) + return false; + MYSQL_RES *result = DB->QStore( "SELECT host,port,connected,maxconnections FROM channels WHERE id=%i", servernum ); + if(result==NULL) return false; + if(mysql_num_rows(result)!=1) + { + Log(MSG_WARNING, "Player selected a invalid channel or channel offline" ); + DB->QFree( ); + return true; + } + row = mysql_fetch_row(result); + UINT connected = atoi(row[2]); + UINT maxconnections = atoi(row[3]); + + Log(MSG_DEBUG, "IP from database. Sent to client: %s ",row[0] ); + + ADDBYTE( pak, 0 ); //atoi(row[0]) ); // What is status? It's NULL in tables - Drakia + ADDDWORD( pak, thisclient->userid ); + ADDDWORD( pak, 0x87654321 ); + ADDSTRING( pak, row[0] ); + ADDBYTE( pak, 0 ); + ADDWORD( pak, atoi(row[1]) ); + DB->QFree( ); + thisclient->SendPacket ( &pak ); + return true; + } + catch(...) + { + Log(MSG_ERROR, "Error in pakGetIP"); + } +} + diff --git a/Src/Login Server/loginserver.cpp b/Src/Login Server/loginserver.cpp new file mode 100644 index 0000000..45749af --- /dev/null +++ b/Src/Login Server/loginserver.cpp @@ -0,0 +1,142 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" + +// Constructor +CLoginServer::CLoginServer( string fn ) +{ + filename = fn; + LoadConfigurations( (const char*)filename.c_str() ); + GServer = this; +} + +// Destructor +CLoginServer::~CLoginServer( ) +{ + +} + +// Create client socket (alloc memory) +CLoginClient* CLoginServer::CreateClientSocket( ) +{ + try + { + CLoginClient* thisclient = new (nothrow) CLoginClient( ); + if(thisclient==NULL) + return NULL; + thisclient->GS = this; + return thisclient; + } + catch(...) + { + Log(MSG_ERROR, "Error in CreateClientSocket"); + } +} + +//loadserver encryption +void CLoginServer::LoadEncryption( ) +{ + GenerateLoginServerCryptTables( CryptTable ); +} + +// Delete client socket (free memory) +void CLoginServer::DeleteClientSocket( CClientSocket* thisclient ) +{ + try + { + Log( MSG_INFO, "User disconnected" ); + delete (CLoginClient*)thisclient; + } + catch(...) + { + Log(MSG_ERROR, "Error in DeleteClientSocket"); + } +} + +// Load information when server is ready +bool CLoginServer::OnServerReady( ) +{ + return true; +} + +// Load Server configuration +void CLoginServer::LoadConfigurations( const char* file ) +{ + try + { + //Database + Config.SQLServer.pcServer = ConfigGetString ( file, "mysql_host", "localhost" ); + Config.SQLServer.pcDatabase = ConfigGetString ( file, "mysql_database", "irose2" ); + Config.SQLServer.pcUserName = ConfigGetString ( file, "mysql_user", "root" ); + Config.SQLServer.pcPassword = ConfigGetString ( file, "mysql_pass", "root" ); + Config.SQLServer.pcPort = ConfigGetInt ( file, "mysql_port", 3306 ); + //Server + Config.ServerID = ConfigGetInt ( file, "serverid", 0 ); + Config.ServerType = 0; // Login is always 0 + Config.LoginIP = ConfigGetString ( file, "serverip", "127.0.0.1" ); + Config.LoginPort = ConfigGetInt ( file, "serverport", 29000 ); + Config.ServerName = ConfigGetString ( file, "servername", "Loginserver" ); + //Passwords + Config.LoginPass = ConfigGetInt ( file, "loginpass", 123456 ); + Config.CharPass = ConfigGetInt ( file, "charpass", 123456 ); + Config.WorldPass = ConfigGetInt ( file, "worldpass", 123456 ); + //Login + Config.MinimumAccessLevel = ConfigGetInt ( file, "accesslevel", 100 ); + Config.usethreads = ConfigGetInt ( file, "usethreads", 0 )==0?false:true; + Config.CreateLoginAccount = ConfigGetInt ( file, "CreateLoginAccount", 0 )==0?false:true; + Config.Testserver = ConfigGetInt ( file, "Testserver", 1 )==0?false:true; + } + catch(...) + { + Log(MSG_FATALERROR, "Error parsing configuration file"); + } + +} + +// Handle packets +bool CLoginServer::OnReceivePacket( CClientSocket* thisclient, CPacket *P ) +{ + //Log(MSG_ERROR, "Packet received"); + switch( P->Command ) + { + + case 0x703: return pakEncryptionRequest( (CLoginClient*)thisclient, P ); + case 0x708: return pakUserLogin ( (CLoginClient*)thisclient, P ); + case 0x704: return pakGetServers ( (CLoginClient*)thisclient, P ); + case 0x70a: return pakGetIP ( (CLoginClient*)thisclient, P ); + case 0x808: return true; + default: + Log( MSG_WARNING, "[%i]Login Server Received unknown packet. Command:%04x Size:%04x", thisclient->sock, P->Command, P->Size ); + break; + } + return true; +} +//<<<<<<< .mine + +//bool CLoginServer::Ping( ) { +// return DB->Ping( ); +//} +//======= + +bool CLoginServer::Ping( ) { + return DB->Ping( ); +} +//>>>>>>> .r81 diff --git a/Src/Login Server/loginserver.h b/Src/Login Server/loginserver.h new file mode 100644 index 0000000..c250d0b --- /dev/null +++ b/Src/Login Server/loginserver.h @@ -0,0 +1,77 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef __ROSE_SERVER__ +#define __ROSE_SERVER__ + +#include "../common/sockets.h" +#include "datatype.h" + +// Player class +class CLoginClient : public CClientSocket +{ + public: + CLoginClient ( ); // Constructor + ~CLoginClient ( ); // destructor + // Variables + bool isLoggedIn; + UINT userid; + string username; + string password; + WORD accesslevel; +}; + +// Server class +class CLoginServer : public CServerSocket +{ + public: + CLoginServer ( string ); // Constructor + ~CLoginServer( ); // Destructor + + //Functions + CLoginClient* CreateClientSocket( ); // Create a client + void DeleteClientSocket( CClientSocket* thisclient ); // Delete a client + void LoadEncryption( ); + bool OnReceivePacket( CClientSocket* thisclient, CPacket* P ); + bool OnServerReady(void); + void LoadConfigurations( const char* ); + bool Ping( ); + // Packets + bool pakEncryptionRequest( CLoginClient* thisclient, CPacket* P ); + bool pakConnectToChar( CLoginClient* thisclient, CPacket *P ); + bool pakUserLogin( CLoginClient* thisclient, CPacket* P ); + bool pakGetServers( CLoginClient* thisclient, CPacket* P ); + bool pakGetIP( CLoginClient* thisclient, CPacket* P ); + + // Variables + string filename; + vector ServerList; // list of channels; + + CDatabase *DB; +}; + +void StartSignal( ); +void StopSignal( ); +void HandleSignal( int num ); +extern class CLoginServer* GServer; + +#endif + + diff --git a/Src/Login Server/main.cpp b/Src/Login Server/main.cpp new file mode 100644 index 0000000..d44ec13 --- /dev/null +++ b/Src/Login Server/main.cpp @@ -0,0 +1,65 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" + +unsigned char LOG_THISSERVER = 0; +class CLoginServer* GServer; + +// Main Funtion +int main(int argc, char *argv[]) +{ + LOG_THISSERVER = LOG_LOGIN_SERVER; + InitWinSocket( ); + string fileconf = "loginserver.conf"; + if(argc>1) + { + fileconf = argv[1]; + } + + CLoginServer *server = new (nothrow) CLoginServer( fileconf ); + + MYSQL mysql; + server->DB = new CDatabase( server->Config.SQLServer.pcServer, + server->Config.SQLServer.pcUserName, + server->Config.SQLServer.pcPassword, + server->Config.SQLServer.pcDatabase, + server->Config.SQLServer.pcPort, &mysql ); + server->port = server->Config.LoginPort; + if(server->Config.usethreads) + Log( MSG_INFO, "Using Threads" ); + // Start server + if(server->DB->Connect( )==0) Log( MSG_INFO, "Connected to MySQL server" ); + else + { + delete server; + #ifdef _WIN32 + system("pause"); + #endif + return -1; + } + server->LoadEncryption (); + server->StartServer( ); + server->DB->Disconnect( ); + // Close server + delete server; + CloseWinSocket( ); + return EXIT_SUCCESS; +} diff --git a/Src/Login Server/signal.cpp b/Src/Login Server/signal.cpp new file mode 100644 index 0000000..37bb9db --- /dev/null +++ b/Src/Login Server/signal.cpp @@ -0,0 +1,109 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://osroseon.to.md + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "loginserver.h" +/* +/ from signal.h + * NOTE: SIGINT is produced when the user presses Ctrl-C. + * SIGILL has not been tested. + * SIGFPE doesn't seem to work? + * SIGSEGV does not catch writing to a NULL pointer (that shuts down + * your app; can you say "segmentation violation core dump"?). + * SIGTERM comes from what kind of termination request exactly? + * SIGBREAK is indeed produced by pressing Ctrl-Break. + * SIGABRT is produced by calling abort. +*/ + +void StartSignal( ) +{ + signal( SIGINT, HandleSignal ); + signal( SIGILL, HandleSignal ); + signal( SIGFPE, HandleSignal ); + signal( SIGSEGV, HandleSignal ); + signal( SIGTERM, HandleSignal ); + signal( SIGBREAK, HandleSignal ); + signal( SIGABRT, HandleSignal ); +} + +void StopSignal( ) +{ + signal( SIGINT, 0 ); + signal( SIGILL, 0 ); + signal( SIGFPE, 0 ); + signal( SIGSEGV, 0 ); + signal( SIGTERM, 0 ); + signal( SIGBREAK, 0 ); + signal( SIGABRT, 0 ); +} + +void HandleSignal( int num ) +{ + switch(num) + { + case SIGINT:/* Interactive attention */ + Log( MSG_ERROR, "Signal received: SIGINT" ); + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + case SIGILL:/* Illegal instruction */ + Log( MSG_ERROR, "Signal received: SIGILL, Server will be closed, Trying to save..." ); + GServer->isActive = false; + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + case SIGFPE:/* Floating point error */ + Log( MSG_ERROR, "Signal received: SIGFPE, Server will be closed, Trying to save..." ); + GServer->isActive = false; + raise(num); + break; + case SIGSEGV:/* Segmentation violation */ + Log( MSG_ERROR, "Signal received: SIGSEGV, Server will be closed, Trying to save..." ); + GServer->isActive = false; + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + raise(num); + break; + + case SIGTERM:/* Termination request */ + Log( MSG_INFO, "Signal received: SIGTERM, Server will be closed" ); + GServer->isActive = false; + break; + #ifdef _WIN32 + case SIGBREAK:/* Control-break */ + Log( MSG_INFO, "Signal received: SIGBREAK, Server will be closed" ); + GServer->isActive = false; + break; + #endif + default: + cout << "Unknown Signal Number: " << num << endl; + raise(num); + } +} diff --git a/Src/World Server/Buff.cpp b/Src/World Server/Buff.cpp new file mode 100644 index 0000000..f7ed9c2 --- /dev/null +++ b/Src/World Server/Buff.cpp @@ -0,0 +1,946 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Add Player buffs +bool CWorldServer::AddBuffs( CSkills* thisskill, CCharacter* character, int Evalue ) +{ + //Log(MSG_INFO, "called addbuffs." ); + bool bflag = false; + for(unsigned int i=0;i<2;i++) + { + if(thisskill->status[i] != 0) // sets status flags in magicstatus array + { + bflag = CheckABuffs( thisskill, character, Evalue, i ); + } + else // some skills are instant effects without status changes e.g. healing. + { + if(thisskill->buff[i] != 0) + { + bflag = InstantBuff( thisskill, character, Evalue, i ); + } + } + } + return bflag; +} + +// handle instant effect skills without status changes +bool CWorldServer::InstantBuff( CSkills* thisskill, CCharacter* character, int Evalue, int i ) +{ + //Log( MSG_INFO, "Instant skill cast: Status (should be zero):%i buff: %i", thisskill->status[i], thisskill->buff[i] ); + switch( thisskill->buff[i]) + { + case 16: // HP + { + if(thisskill->duration <= 0 )//is heal + { + if(character->Stats->HP <= 0) //character is dead + return true; + character->Stats->HP += (long int)thisskill->value1[i]; //apparently there is no buff here. // + Evalue - 8; + if(character->Stats->HP > character->Stats->MaxHP ) + character->Stats->HP = character->Stats->MaxHP; + return true; + } + } + break; + case 17: // MP + { + if(thisskill->duration <= 0 )// recover MP + { + character->Stats->MP += (long int)thisskill->value1[i]; + if(character->Stats->MP > character->Stats->MaxMP) + character->Stats->MP = character->Stats->MaxMP; + return true; + } + } + break; + case 76: // Stamina + { + if(character->IsPlayer()) //monsters do not have stamina it would seem + { + if(thisskill->duration <= 0 )// recover Stamina + { + CPlayer* thisclient = GServer->GetClientByID(character->clientid); + if(thisclient == NULL)return false; + thisclient->CharInfo->stamina += (long int)thisskill->value1[i]; + if(thisclient->CharInfo->stamina > 5000) + thisclient->CharInfo->stamina = 5000; + //Log(MSG_INFO,"Character %i Stamina is now %i", character->clientid, thisclient->CharInfo->stamina); + return true; + } + } + else + return false; + } + break; + default: + return false; + break; + } + return false; +} + +// Add DeBuffs +bool CWorldServer::CheckABuffs( CSkills* thisskill, CCharacter* character, int Evalue ,int i) +{ + //Log( MSG_INFO, "checkabuffs status: %i buff: %i",( thisskill->status[i], thisskill->buff[i] )); + bool bflag = false; + if(thisskill->status[i] != 0) + { + switch( thisskill->status[i]) + { + case 7: case 8: case 9: case 10: case 11: //poisoned + { + //Log( MSG_INFO, "checkabuffs: Poisoned Status detected %i", thisskill->status[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, 0xff, character->Status->Poisoned, character->Status->Poisoned,true, true ); + if(BuffValue.Position != 0xff) + { + UINT j = BuffValue.Position; + if(j>14) + { + character->Status->Poisoned = j; + character->MagicStatus[j].Buff = 1; // needs some value or else it will not be counted in GetBuffValue. trying it with 1 + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = 0; // this might cause headaches later + character->MagicStatus[j].Status = thisskill->status[i]; + //Log( MSG_INFO, "Poisoned Status applied"); + bflag = true; + } + } + } + break; + case 30: // muted + { + //Log( MSG_INFO, "checkabuffs: Mute Status detected %i", thisskill->status[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, 0xff, character->Status->Muted, character->Status->Muted, true, true ); + if(BuffValue.Position != 0xff) + { + UINT j = BuffValue.Position; + if(j>14) + { + character->Status->Muted = j; + character->Status->CanCastSkill = false; + character->MagicStatus[j].Buff = 1; // needs some value or else it will not be counted in GetBuffValue. trying it with 1 + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = 0; // this might cause headaches later + character->MagicStatus[j].Status = thisskill->status[i]; + //Log( MSG_INFO, "Muted Status applied"); + bflag = true; + } + } + } + break; + case 31: // sleep + { + //Log( MSG_INFO, "checkabuffs: Sleep Status detected %i", thisskill->status[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, 0xff, character->Status->Sleep, character->Status->Sleep, true, true ); + Log( MSG_INFO, "Sleep Status attempted. position = %i", BuffValue.Position); + if(BuffValue.Position != 0xff) + { + UINT j = BuffValue.Position; + if(j>14) + { + character->Status->Sleep = j; + character->Status->CanAttack = false; + character->Status->CanMove = false; + character->Status->CanCastSkill = false; + character->MagicStatus[j].Buff = 1; // needs some value or else it will not be counted in GetBuffValue. trying it with 1 + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = 0; // this might cause headaches later + character->MagicStatus[j].Status = thisskill->status[i]; + //Log( MSG_INFO, "Sleep Status applied"); + bflag = true; + } + } + } + break; + case 32: // faint + { + //Log( MSG_INFO, "checkabuffs: Fainted Status detected %i", thisskill->status[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, 0xff, character->Status->Faint, character->Status->Faint, true, true ); + //Log( MSG_INFO, "Stun Status attempted. position = %i", BuffValue.Position); + if(BuffValue.Position != 0xff) + { + UINT j = BuffValue.Position; + if(j>14) + { + character->Status->Faint = j; + character->Status->CanAttack = false; + character->MagicStatus[j].Buff = 1; // needs some value or else it will not be counted in GetBuffValue. trying it with 1 + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = 0; // this might cause headaches later + character->MagicStatus[j].Status = thisskill->status[i]; + //Log( MSG_INFO, "Fainted Status applied"); + bflag = true; + } + } + } + break; + case 39: // Dispell. removes all buff and debuff effects + { + Log( MSG_INFO, "checkabuffs: Dispell Status detected %i", thisskill->status[i] ); + // some dispell skills have duration of 2 seconds. Others have zero. + // for now we will treat them all as instants so we don't need a buff slot + float randv = GServer->RandNumber(0, 100); + if(randv <= thisskill->successrate) + { + for(int j=0;j<30;j++) + { // Clean Buffs + character->MagicStatus[j].Duration = 0; + } + Log( MSG_INFO, "Dispell Status applied successfully"); + // the rest will be taken care of in RefreshBuffs() + } + bflag = true; + } + break; + + case 54: //A_GMExtra_Damage: + case 36: //A_Extra_Damage: + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->ExtraDamage_up, + character->Status->ExtraDamage_down, + character->Stats->ExtraDamage, true ); + if(BuffValue.Position != 0xff) + { + UINT j = BuffValue.Position; + character->Stats->ExtraDamage = thisskill->atkpower; + if(j<15) + character->Status->ExtraDamage_up = j; + else + character->Status->ExtraDamage_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = thisskill->atkpower; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + Log( MSG_INFO, "Extra Damage Buff. Value %i Status %i",character->MagicStatus[j].Value,character->MagicStatus[j].Status); + } + } + case 35: // shield damage + { + // uses skill power not buff value. Add later + } + break; + case 18: // attack power up + case 19: // attack power down + case 48: // attack power up + { + //Log( MSG_INFO, "checkabuffs: Attack buff detected %i", thisskill->buff[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Attack_up, + character->Status->Attack_down, + character->Stats->Attack_Power, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Attack_Power = BuffValue.NewValue; + if(j<15) + character->Status->Attack_up = j; + else + character->Status->Attack_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 20: // def up + case 21: // def down + case 49: // def up + { + //Log( MSG_INFO, "checkabuffs: Defense buff detected %i", thisskill->buff[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Defense_up, + character->Status->Defense_down, + character->Stats->Defense, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Defense = BuffValue.NewValue; + if(j<15) + character->Status->Defense_up = j; + else + character->Status->Defense_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 24: //accuracy up + case 25: //accuracy down + case 51: //attack accuracy up. + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Accury_up, + character->Status->Accury_down, + character->Stats->Accury, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Accury = BuffValue.NewValue; + if(j<15) + character->Status->Accury_up = j; + else + character->Status->Accury_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 22: // macic resistance up + case 23: // magic resistance down + case 50: // magic resistance up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Magic_Defense_up, + character->Status->Magic_Defense_down, + character->Stats->Magic_Defense, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Magic_Defense = BuffValue.NewValue; + if(j<15) + character->Status->Magic_Defense_up = j; + else + character->Status->Magic_Defense_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 28: //dodge up + case 29: //dodge down + case 53: //dodge rate up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Dodge_up, + character->Status->Dodge_down, + character->Stats->Dodge, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Dodge = BuffValue.NewValue; + if(j<15) + character->Status->Dodge_up = j; + else + character->Status->Dodge_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 14: //dash + case 15: //slow + case 46: //movement speed increased + { + //Log( MSG_INFO, "checkabuffs: move speed buff detected %i", thisskill->buff[i] ); + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Dash_up, + character->Status->Dash_down, + character->Stats->Move_Speed, true ); + if(BuffValue.NewValue != 0) + { + UINT j = BuffValue.Position; + character->Stats->Move_Speed = BuffValue.NewValue; + if(j<15) + character->Status->Dash_up = j; + else + character->Status->Dash_down = j; + //Log( MSG_INFO, "Move speed buff position %i. new move speed %i. buff value %i", j,BuffValue.NewValue,BuffValue.Value ); + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 16: // haste attack + case 17: // slow attack + case 47: // attack speed up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Haste_up, + character->Status->Haste_down, + (UINT)character->Stats->Attack_Speed, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->Attack_Speed = BuffValue.NewValue; + if(j<15) + character->Status->Haste_up = j; + else + character->Status->Haste_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 26: // crit up + case 27: // crit down + case 52: // crit up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->Critical_up, + character->Status->Critical_down, + character->Stats->Critical, true ); + if(BuffValue.NewValue != 0) + { + UINT j = BuffValue.Position; + character->Stats->Critical = BuffValue.NewValue; + if(j<15) + character->Status->Critical_up = j; + else + character->Status->Critical_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 12: // max HP up + case 44: // max HP up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->HP_up, + character->Status->HP_down, + character->Stats->MaxHP, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->MaxHP = BuffValue.NewValue; + if(j<15) + character->Status->HP_up = j; + else + character->Status->HP_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + case 13: // max MP up + case 45: // max MP up + { + CBValue BuffValue = GetBuffValue( thisskill, character, Evalue, i, + character->Status->MP_up, + character->Status->MP_down, + character->Stats->MaxMP, true ); + if(BuffValue.NewValue!=0) + { + UINT j = BuffValue.Position; + character->Stats->MaxMP = BuffValue.NewValue; + if(j<15) + character->Status->MP_up = j; + else + character->Status->MP_down = j; + character->MagicStatus[j].Buff = thisskill->buff[i]; + character->MagicStatus[j].BuffTime = clock(); + character->MagicStatus[j].Duration = thisskill->duration; + character->MagicStatus[j].Value = BuffValue.Value; + character->MagicStatus[j].Status = thisskill->status[i]; + bflag = true; + } + } + break; + default: + Log(MSG_WARNING,"Status %i unknown. Possibly not coded yet",thisskill->status[i]); + break; + } + } + return bflag; +} + +// Get the new buffs values (character) +CBValue CWorldServer::GetBuffValue( CSkills* thisskill, CCharacter* character, UINT Evalue, UINT i, UINT up, UINT down, UINT CurrentValue, bool Buff, bool Status ) +{ + CBValue NewValue; + NewValue.Position = 0xff; + NewValue.NewValue = 0; + NewValue.Value = 0; + INT Value = 0; + UINT NormalValue = 0; + UINT UpValue = 0; + UINT DownValue = 0; + bool BuffFlag = false; + // not currently detecting debuffs properly so let's bring in some new code :) + //PY buff mod based on thiskill->status[i] + switch(thisskill->status[i]) + { + case 12: case 13: case 14: case 16: case 18: case 20: case 22: case 24: case 26: case 28: case 35: case 36: case 53: + case 54: + Buff = true; + break; + case 7: case 8: case 9: case 10: case 15: case 17: case 19: case 21: case 23: case 25: case 27: case 29: + case 30: case 31: case 32: case 59: case 60: + Buff = false; + break; + default: + Buff = true; + break; + } + Log(MSG_INFO, "Current Buff boolean status %i Skill status = %i up: %i down: %i", Buff,thisskill->status[i],up,down); + //PY end + if(Buff) //skill is a buff + { + //find whether character already has buffs for this stat + for(UINT z=0;z<15;z++) // 0 to 14 are upward + { + if(character->MagicStatus[z].Status == thisskill->status[i]) + { + NewValue.Position = z; + //Log(MSG_INFO, "Existing Buff position = %i",z); + NormalValue = CurrentValue - character->MagicStatus[z].Value; + UpValue = character->MagicStatus[z].Value; + BuffFlag = true; + break; + } + } + if(BuffFlag == false) // no active buffs so find a new slot + { + for(UINT z=0;z<15;z++) // 0 to 14 are upward + { + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; + //Log(MSG_INFO, "New Buff position = %i",z); + NormalValue = CurrentValue; + BuffFlag = true; + break; + } + } + } + } + else + { + //find whether character already has debuffs for this stat + for(UINT z=15;z<32;z++) // 15 to 32 are downward + { + if(character->MagicStatus[z].Status == thisskill->status[i]) + { + NewValue.Position = z; + //Log(MSG_INFO, "Existing De-Buff position = %i",z); + NormalValue = CurrentValue + character->MagicStatus[z].Value; + DownValue = character->MagicStatus[z].Value; + BuffFlag = true; + break; + } + } + if(BuffFlag == false) // no active debuffs so find a new slot + { + for(UINT z=15;z<32;z++) // 15 to 32 are downward + { + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; + //Log(MSG_INFO, "New De-Buff position = %i",z); + NormalValue = CurrentValue; + BuffFlag = true; + break; + } + } + } + } + if(BuffFlag == false) // no available spaces found + return NewValue; + + + + + //if(up != 0xff) //currently have a buff. up is its location + //{ + // UpValue = character->MagicStatus[up].Value; + //Log(MSG_INFO, "Current active buff detected in position %i with value %i", NewValue.Position,UpValue); + // NormalValue = CurrentValue - character->MagicStatus[up].Value; + // if(Buff) + // NewValue.Position = up; + //} + + //else // don't currently have a buff + if(NewValue.Position == 0xff) + { + //Log(MSG_INFO, "No buff currently active"); + if(Buff) //is this skill a buff? + { + for(UINT z=0;z<15;z++) // 0 to 14 are upward + { + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; //z is the new index in the magicstatus array where the buff will be placed + //Log(MSG_INFO, "Buff position = %i",z); + break; + } + } + if(NewValue.Position == 0xff) //no spaces found ?? + return NewValue; + NormalValue = CurrentValue; // no buffs currently active so set normalvalue to currentvalue + } + else //debuff + { + for(UINT z=15;z<30;z++) // 15 to 29 are downward + { + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; //z is the new index in the magicstatus array where the buff will be placed + Log(MSG_INFO, "DeBuff position = %i",z); + break; + } + } + if(NewValue.Position == 0xff) //no spaces found ?? + return NewValue; + NormalValue = CurrentValue; // no buffs currently active so set normalvalue to currentvalue + } + } + + /* + if (!Status) // status boolean set false as default so it will run this code in pretty much all cases + { + Log(MSG_INFO, "Status set to false so checking for debuffs"); + if( down != 0xff) //do we have a debuff in place already? down is its location + { + DownValue = character->MagicStatus[down].Value; + Log(MSG_INFO, "Current active de-buff detected in position %i with value %i", i,DownValue); + if( NormalValue!=0) + NormalValue += character->MagicStatus[down].Value; + else + NormalValue = CurrentValue + character->MagicStatus[down].Value; + if(!Buff) + NewValue.Position = down; + } + else + { + Log(MSG_INFO, "No de-buff currently active"); + if(!Buff) //is this a debuff? + { + Log(MSG_INFO, "Buff is false so adding a new de-buff"); + for(UINT z=15;z<30;z++) // 15 to 29 are downward + { + if(character->MagicStatus[z].Status == thisskill->status[i]) + { + NewValue.Position = z; //z is the index in the magicstatus array where the de-buff will be placed + Log(MSG_INFO, "#1 found status De-Buff in position = %i. refreshing it.",z); + break; + } + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; //z is the new index in the magicstatus array where the de-buff will be placed + Log(MSG_INFO, "#1 De-Buff position = %i",z); + break; + } + } + if(NewValue.Position == 0xff) + return NewValue; + NormalValue = CurrentValue; + } + } + } + else if ( down != 0xff ) //Status = true and we currently have a debuff in effect?? + { + Log(MSG_INFO, "Current active debuff detected in position %i with value %i", DownValue, character->MagicStatus[down].Value); + NormalValue = CurrentValue + character->MagicStatus[down].Value; + if(!Buff) + NewValue.Position = down; + } + else //status set true and no current debuff. Used for skills with only a Status effect but no buff/debuff value + { + for(UINT z=15;z<30;z++) //check magicstatus array debuff area for spaces + { + if(character->MagicStatus[z].Status == 0) + { + NewValue.Position = z; //z is the new index in the magicstatus array where the de-buff will be placed + Log(MSG_INFO, "#2 De-Buff position = %i",z); + break; + } + } + if(NewValue.Position == 0xff) + return NewValue; + NormalValue = CurrentValue; + } + */ + + //Log(MSG_INFO, "Newvalue.position = %i",NewValue.Position); + if( thisskill->value2[i] > 0 ) + { + Value = NormalValue * thisskill->value2[i] / 100; + //Log(MSG_INFO, "Percentage buff = %i",Value); + } + if( thisskill->value1[i] > 0 ) + { + Value += thisskill->value1[i]; + if(thisskill->buff[i] == A_DASH || thisskill->buff[i] == MOV_SPEED) + { + //Log(MSG_DEBUG,"Made it into the DASH calculations. Evalue = %i",Evalue); + if( Evalue <= 60 ) + { + int temp1 = Evalue - 60; + //Log(MSG_DEBUG,"temp1 = %i",temp1); + float temp2 = (20.0 / 45.0); + //Log(MSG_DEBUG,"temp2 = %f",temp2); + int temp = (int)(temp1 * temp2); + //Log(MSG_DEBUG,"Result of calculation for INT < 60 = %i",temp); + //int temp = (int)((Evalue - 60) * (20.0 / 45.0)); + //Log(MSG_DEBUG,"Result of calculation for INT < 60 = %i",temp); + Value += temp; + } + if( Evalue > 60 && Evalue <= 150) + { + int temp1 = Evalue - 60; + float temp2 = (17.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += temp; + } + if( Evalue > 150 && Evalue <= 195) + { + int temp1 = Evalue - 150; + float temp2 = (18.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 34 + temp; + } + if( Evalue > 195 && Evalue <= 240) + { + int temp1 = Evalue - 195; + float temp2 = (19.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 52 + temp; + } + if( Evalue > 240 && Evalue <= 285) + { + int temp1 = Evalue - 240; + float temp2 = (17.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 71 + temp; + } + if( Evalue > 285 && Evalue <= 330) + { + int temp1 = Evalue - 285; + float temp2 = (18.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 88 + temp; + } + if( Evalue > 330 && Evalue <= 375) + { + int temp1 = Evalue - 330; + float temp2 = (17.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 106 + temp; + } + if( Evalue > 375 && Evalue <= 385) + { + int temp1 = Evalue - 420; + float temp2 = (19.0 / 45.0); + int temp = (int)(temp1 * temp2); + Value += 123 + temp; + } + } + // Value += (Evalue - 15) / 10; + else + Value += (Evalue-15)/32; + //Log(MSG_INFO, "Number buff = %i",Value); + } + if(Buff) + { + if( Value + CurrentValue - DownValue >= CurrentValue ) + { + CurrentValue = NormalValue - DownValue + Value; + } + else + { + CurrentValue = 0; + } + } + else + { + CurrentValue = NormalValue + UpValue - Value; + } + NewValue.NewValue = CurrentValue; + NewValue.Value = Value; + return NewValue; +} + +// Build Buffs to Show +unsigned int CWorldServer::BuildBuffs( CCharacter* character ) +{ + BYTE buff1 = 0; + BYTE buff2 = 0; + BYTE buff3 = 0; + BYTE buff4 = 0; + //Build Debuffs and Buffs + //Up + if(character->Status->Attack_up != 0xff)//A_ATTACK: + buff2 += ATTACK_UP; + if(character->Status->Defense_up != 0xff)//A_DEFENSE: + buff2 += DEFENSE_UP; + if(character->Status->Magic_Defense_up != 0xff)//A_MDEFENSE_UP + buff2 += MDEFENSE_UP; + if(character->Status->Accury_up != 0xff)//A_ACCUR: + buff3 += HITRATE_UP; + if(character->Status->Dash_up != 0xff)//A_DASH: + buff1 += DASH_UP; + if(character->Status->Haste_up != 0xff)//A_HASTE: + buff2 += HASTE_UP; + if(character->Status->HP_up != 0xff)//A_HP: + buff1 += HP_UP; + if(character->Status->MP_up != 0xff)//A_MP: + buff1 += MP_UP; + if(character->Status->Critical_up != 0xff)//A_CRITICAL: + buff3 += CRITICAL_UP; + if(character->Status->Dodge_up != 0xff)//A_DODGE: + buff1 += DODGE_UP; + if(character->Status->ExtraDamage_up != 0xff)//A_Extra_Damage: + buff4 += DAMAGE_UP; + //Down + if(character->Status->Attack_down != 0xff) // A_ATTACK: + buff2 += ATTACK_DOWN; + if(character->Status->Defense_down != 0xff)//A_DEFENSE: + buff2 += DEFENSE_DOWN; + if(character->Status->Magic_Defense_down != 0xff) + buff2 += MDEFENSE_DOWN; + if(character->Status->Accury_down != 0xff)//A_ACCUR: + buff3 += HITRATE_DOWN; + if(character->Status->Dash_down != 0xff)//A_DASH: + buff1 += DASH_DOWN; + if(character->Status->Haste_down != 0xff)//A_HASTE: + buff2 += HASTE_DOWN; + if(character->Status->HP_down != 0xff)//A_HP: + buff1 += 0; + if(character->Status->MP_down != 0xff)//A_MP: + buff1 += 0; + if(character->Status->Critical_down != 0xff)//A_CRITICAL: + buff3 += CRITICAL_DOWN; + if(character->IsSummon( )) + buff4 += SUMMON; + if(character->Status->Stuned != 0xff)//A_STUN + buff4+= STUN; + if(character->Status->Poisoned != 0xff)//A_POISON + buff1+= POISONED; + if(character->Status->Muted != 0xff)//A_MUTE + buff3+= MUTED; + return (buff1 * 0x01) + (buff2 * 0x100 ) + (buff3 * 0x10000) + (buff4 * 0x1000000); +} + +// Build Buffs to Show +unsigned int CWorldServer::BuildUpBuffs( CCharacter* character ) +{ + BYTE buff1 = 0; + BYTE buff2 = 0; + BYTE buff3 = 0; + BYTE buff4 = 0; + // Buffs + //Up + if(character->Status->Attack_up != 0xff)//A_ATTACK: + buff2 += ATTACK_UP; + if(character->Status->Defense_up != 0xff)//A_DEFENSE: + buff2 += DEFENSE_UP; + if(character->Status->Magic_Defense_up != 0xff)//A_MDEFENSE_UP + buff2 += MDEFENSE_UP; + if(character->Status->Accury_up != 0xff)//A_ACCUR: + buff3 += HITRATE_UP; + if(character->Status->Dash_up != 0xff)//A_DASH: + buff1 += DASH_UP; + if(character->Status->Haste_up != 0xff)//A_HASTE: + buff2 += HASTE_UP; + if(character->Status->HP_up != 0xff)//A_HP: + buff1 += HP_UP; + if(character->Status->MP_up != 0xff)//A_MP: + buff1 += MP_UP; + if(character->Status->Critical_up != 0xff)//A_CRITICAL: + buff3 += CRITICAL_UP; + if(character->Status->Dodge_up != 0xff)//A_DODGE: + buff1 += DODGE_UP; + if(character->Status->ExtraDamage_up != 0xff)//A_Extra_Damage: + buff4 += DAMAGE_UP; + return (buff1 * 0x01) + (buff2 * 0x100 ) + (buff3 * 0x10000) + (buff4 * 0x1000000); +} +// Build Buffs to Show +unsigned int CWorldServer::BuildDeBuffs( CCharacter* character ) +{ + BYTE buff1 = 0; + BYTE buff2 = 0; + BYTE buff3 = 0; + BYTE buff4 = 0; + //Build Debuffs + //Down + if(character->Status->Attack_down != 0xff) // A_ATTACK: + buff2 += ATTACK_DOWN; + if(character->Status->Defense_down != 0xff)//A_DEFENSE: + buff2 += DEFENSE_DOWN; + if(character->Status->Magic_Defense_down != 0xff) + buff2 += MDEFENSE_DOWN; + if(character->Status->Accury_down != 0xff)//A_ACCUR: + buff3 += HITRATE_DOWN; + if(character->Status->Dash_down != 0xff)//A_DASH: + buff1 += DASH_DOWN; + if(character->Status->Haste_down != 0xff)//A_HASTE: + buff2 += HASTE_DOWN; + if(character->Status->HP_down != 0xff)//A_HP: + buff1 += 0; + if(character->Status->MP_down != 0xff)//A_MP: + buff1 += 0; + if(character->Status->Critical_down != 0xff)//A_CRITICAL: + buff3 += CRITICAL_DOWN; + if(character->IsSummon( )) + buff4 += SUMMON; + if(character->Status->Stuned != 0xff)//A_STUN + buff4+= STUN; + if(character->Status->Poisoned != 0xff)//A_POISON + buff1+= POISONED; + if(character->Status->Muted != 0xff)//A_MUTE + buff3+= MUTED; + return (buff1 * 0x01) + (buff2 * 0x100 ) + (buff3 * 0x10000) + (buff4 * 0x1000000); +} + diff --git a/Src/World Server/Copy of battle.cpp b/Src/World Server/Copy of battle.cpp new file mode 100644 index 0000000..1edf3bd --- /dev/null +++ b/Src/World Server/Copy of battle.cpp @@ -0,0 +1,847 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +void CCharacter::DoAttack( ) +{ + //if(Status->Sleep <= 0xff) + //{ + // ClearBattle( Battle ); + // return; // can't take any action while we are asleep can we? + //} + //Log(MSG_INFO,"skill type %i selected. Chartype = %i",Battle->atktype, CharType); + if(Battle->iscasting == 1) + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + //Log(MSG_DEBUG,"Iscasting detected as true. Reset to false."); + if(CanAttack()) + { + if(IsPlayer()) + { + Log(MSG_DEBUG,"Should reach here once on normal attack. iscasting state = %i",Battle->iscasting); + } + ClearBattle( Battle ); + + StartAction(Enemy, NORMAL_ATTACK, 0); + + return; + } + else + { + return; + } + } + if(!CanAttack()) + { + return; + } + if(IsSummon()) + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL || (Battle->atktype != SKILL_AOE && Battle->atktype != BUFF_AOE)) + { + //Log(MSG_DEBUG,"No Enemy found"); + ClearBattle( Battle ); + return; + } + if(this == Enemy) //summoned monster is attacking itself. It has been observed to happen + { + //Log(MSG_INFO,"Clearing Battle for this summon"); + ClearBattle( Battle ); + return; + } + if(Enemy->IsSummon()) + { + CMonster* thisMonster = reinterpret_cast(this); + if(thisMonster == NULL) + { + ClearBattle( Battle ); + return; + } + CMonster* otherMonster = reinterpret_cast(Enemy); + if(otherMonster == NULL) + { + ClearBattle( Battle ); + return; + } + if(thisMonster->owner == otherMonster->owner) + { + //Log(MSG_INFO,"Clearing Battle for this summon"); + ClearBattle( Battle ); + return; + } + } + } + CMap* map = GServer->MapList.Index[Position->Map]; + //Log(MSG_INFO,"DoAttack Battle Attack type = %i",Battle->atktype); + switch(Battle->atktype) + { + case NORMAL_ATTACK://normal attack + { + //if(!Status->CanAttack) + // return; //can't attack right now. + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + if(Enemy == this) + { + //Log(MSG_INFO,"WTF?? I AM trying to attack myself"); + ClearBattle( Battle ); + } + if(IsTargetReached( Enemy )) + { + //if(IsMonster()) + // Log(MSG_INFO,"Monster is trying to hit the player"); + NormalAttack( Enemy ); + if (Enemy->IsMonster()) // do monster AI script when monster is attacked. + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + //monster->DoAi(monster->thisnpc->AI, 3); + monster->DoAi(monster->monAI, 3); + } + } + } + break; + case SKILL_ATTACK://skill attack + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + if(IsTargetReached( Enemy, skill )) + { + SkillAttack( Enemy, skill ); + if (Enemy->IsMonster()) + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + monster->DoAi(monster->monAI, 3); + } + } + } + break; + case SKILL_BUFF://buffs + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + //Log(MSG_DEBUG,"this target is NULL"); + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //Log(MSG_DEBUG,"this skill is NULL"); + return; + } + if(IsTargetReached( Enemy, skill )) + { + //Log(MSG_DEBUG,"Skill successfully cast"); + BuffSkill( Enemy, skill ); + } + } + break; + case SKILL_AOE: + //case SKILL_SELF: //this is impossible. it can never be set + case AOE_TARGET: + { + //Log(MSG_DEBUG,"Called skill type %i",Battle->atktype); + CCharacter* Enemy = NULL; + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + ClearBattle( Battle ); + return; + } + if(Battle->atktype == AOE_TARGET) + { + float distance = GServer->distance( Position->current, Position->destiny ); + if(distance <= skill->range) + { + Position->destiny = Position->current; + AoeSkill( skill, Enemy ); + } + } + else + { + Position->skilltargetpos = Position->current; + AoeSkill( skill, NULL ); + } + } + break; + case BUFF_SELF: + { + // return; // can't cast skills right now + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + BuffSkill( this, skill ); + } + break; + case BUFF_AOE: + { + //Log(MSG_INFO,"BUFF AOE selected"); + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + + return; + } + AoeBuff( skill ); + } + break; + default: + return; + break; + } +} + + +// do normal attack +void CCharacter::NormalAttack( CCharacter* Enemy ) +{ + Enemy->OnBeAttacked( this ); + Position->destiny = Position->current; + // PY code + float mod = 0; + if(IsPlayer()) + { + mod = 23 + (Stats->Level / 1.5); + } + else + { + mod = 23 + (Stats->Level / 4); + + } + float attack = 0; + if(Stats->magicattack == 1) + { + attack = (float) Stats->Attack_Power / Enemy->Stats->Magic_Defense; + attack *= mod; + } + else + { + attack = (float) Stats->Attack_Power / Enemy->Stats->Defense; + attack *= mod; + } + //PY end + if(attack < 5) attack = 5; + float d_attack = attack / 100; + mod = GServer->RandNumber( 0, 10 ) * d_attack; + attack += mod; + long int hitpower = (long int)floor(attack + GServer->RandNumber(0, 10)); + if(IsPlayer( )) //temp fix to find balance btw monster and player + { + hitpower = (long int)floor(attack * (GServer->Config.PlayerDmg/100.00)); + hitpower+=((hitpower*(Stats->ExtraDamage))/100); + } + if(IsMonster( )) //temp fix to find balance btw monster and player + hitpower = (long int)floor(attack * (GServer->Config.MonsterDmg/100.00)); + bool critical = false; + if(hitpower <= 5) + { + hitpower = 5; + } + else + { + if(GServer->RandNumber(0,300)Critical) + { + hitpower = (long int)floor(hitpower * 1.5); + critical = true; + } + } + // dodge + unsigned int hitvalue = (unsigned int)floor((double)(Stats->Accury * 100 / Enemy->Stats->Dodge)); + if(hitvalue>100) hitvalue = 100; + if(GServer->RandNumber( 0, 100 )>hitvalue) + hitpower = 0; // dodged + if (hitpower > 0x7ff)//2047 packet size limit. + { + hitpower=0x7ff; + } + + //maybe this little line here will fix my ghosty problem + //they are definitely attacking. But no animation so maybe atktarget is empty + Battle->atktarget = Battle->target; + //Well it does make the damage show up at teh client but the animation is still dodgey + + if(IsMonster()) + { + //Log(MSG_INFO,"Monster hits player for %i damage. Attack target = %i",hitpower,Battle->atktarget); + } + //Log( MSG_INFO, "hitpower %i. Attack %f ", hitpower,attack ); + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, hitpower ); + Enemy->damagecounter += hitpower;// is for AI + } + Enemy->Stats->HP -= hitpower; + if(IsPlayer()) + Log(MSG_DEBUG,"Player attack did damage %i",hitpower); + if(IsMonster()) + Log(MSG_DEBUG,"Monster attack did damage %i",hitpower); + + + + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->atktarget ); + + if(Enemy->IsDead()) + { + CDrop* thisdrop = NULL; + ADDWORD ( pak, (hitpower | ( critical?0xb000:0x8000 ) )); + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + thisdrop = Enemy->GetDrop( ); + if(thisdrop != NULL) + { + ADDFLOAT ( pak, thisdrop->pos.x*100 ); + ADDFLOAT ( pak, thisdrop->pos.y*100 ); + if(thisdrop->type == 1) + { + ADDWORD( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + { + ADDWORD ( pak, GServer->BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, GServer->BuildItemData( thisdrop->item ) ); + } + ADDWORD ( pak, thisdrop->clientid ); + ADDWORD ( pak, thisdrop->owner ); + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, Enemy, thisdrop ); + OnEnemyDie( Enemy ); + } + else + { + ADDWORD ( pak, (hitpower|(hitpower>0?(critical?0x4000:0):0))); + GServer->SendToVisible( &pak, Enemy ); + } + ReduceABC( ); + Battle->lastAtkTime = clock( ); +} + +// do skill attack +bool CCharacter::SkillAttack( CCharacter* Enemy, CSkills* skill ) +{ + Position->destiny = Position->current; + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + UseAtkSkill( Enemy, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) + Stats->MP = 0; + //Battle->atktype = NORMAL_ATTACK; + //Battle->skilltarget = 0; + //Battle->atktarget = Battle->target; + //Battle->skillid = 0; + GServer->DoSkillScript( this, skill ); + Battle->lastAtkTime = clock( ); + if(Enemy->IsDead()) + ClearBattle(Battle); + Battle->iscasting = 1; + //Log(MSG_DEBUG,"Cast a skill. Iscasting set to true"); + return true; +} + +// do buff skill +bool CCharacter::BuffSkill( CCharacter* Target, CSkills* skill ) +{ + Position->destiny = Position->current; + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + //Log(MSG_DEBUG,"applied buff skill %i",skill->id); + UseBuffSkill( Target, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + GServer->DoSkillScript( this, skill ); //summons a monster. Why do we do this for all skills? + if(!IsMonster()) + { + ClearBattle( Battle ); // clear battle for players when they use buff skills + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + } + else //Monsters need to be reset to normal attack and clear skill attacks. + { + //Battle->atktarget = Battle->target; + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + //StartAction(Target, NORMAL_ATTACK); + } + Battle->lastAtkTime = clock( ); + Battle->iscasting = 1; + return true; +} +//arnold +// do AoE skill +bool CCharacter::AoeSkill( CSkills* skill, CCharacter* Enemy ) +{ + Position->destiny = Position->current; + //Log(MSG_INFO,"Doing AOE Skill"); + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + CMap* map = GServer->MapList.Index[Position->Map]; + + //PY replacement code + if(IsPlayer() || IsSummon()) + { + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster == NULL) continue; + if(monster->clientid == clientid) continue; + if(monster->IsSummon( ) && (map->allowpvp == 0 || monster->owner == clientid)) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill ); + } + } + if(IsMonster() && !IsSummon()) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + if(player->clientid == clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos,player->Position->current,(float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill ); + } + } + //PY end + + // this code appears to me to be completely wrong replaced with code above + /* + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster->clientid == clientid) continue; + if(IsSummon( ) || IsPlayer( )) + { + if(monster->IsSummon( ) && (map->allowpvp==0 || monster->owner == clientid)) continue; + } + else + { + if(!monster->IsSummon( )) continue; + } + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill ); + } + if(map->allowpvp!=0 || (IsMonster( ) && !IsSummon( ))) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player->clientid==clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos,player->Position->current,(float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill ); + } + }*/ + Battle->iscasting = 1; + //Log(MSG_DEBUG,"Cast a skill. Iscasting set to true"); + if(Enemy != NULL) + { + if(Enemy->IsDead( )) + ClearBattle( Battle ); + } + else + ClearBattle( Battle ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + Battle->lastAtkTime = clock( ); + + return true; +} + +bool CCharacter::AoeBuff( CSkills* skill ) +{ + //Log(MSG_INFO,"BUFF AOE called"); + Position->destiny = Position->current; + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + CMap* map = GServer->MapList.Index[Position->Map]; + + if(CharType != 2) //player || Summon || NPC. Anything that is not a monster + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + switch(skill->target) + { + case 1: //tPartyMember + if(player->Party->party == GetParty()) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough party member"); + if (player == this) //it is me so I had better cast it anyway ^-^ + { + UseBuffSkill( (CCharacter*)player, skill ); + } + else if(player->Party->party != NULL) // Don't cast the skill on players who are not in any party at all. + { + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + } + break; + case 2: //tClanMember + if (player->Clan == GetClan( ) && (player->Clan != 0 || player == this)) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough clan member"); + if(GServer->IsMonInCircle( Position->current, player->Position->current, (float)skill->aoeradius + 1 ) ) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough allied Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 5: //tHostileCharacter + { + if (CharType == 2) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough hostile Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + } + break; + } + } + } + else if (CharType == 2) //monster + { + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster == NULL)continue; + switch(skill->target) + { + case tPartyMember: // party + break; + case 2: //tClanMember + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough monster"); + if(GServer->IsMonInCircle( Position->current,monster->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)monster, skill ); + } + break; + case tHostileCharacter: + break; + } + } + } + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + //Battle->atktarget = Battle->target; + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + ClearBattle( Battle ); + Battle->lastAtkTime = clock( ); + Battle->iscasting = 1; + return true; +} + + + +// use skill attack +void CCharacter::UseAtkSkill( CCharacter* Enemy, CSkills* skill, bool deBuff) +{ + long int skillpower = skill->atkpower; + UINT weapontype = 0; + float mod = 0; + float attack = 0; + if(IsPlayer()) + { + mod = 20 + (Stats->Level / 2); + } + else + { + mod = 15 + (Stats->Level / 2); + } + if(skill->skilltype == 6 || skill->skilltype == 7) //magical damage + { + attack = skillpower / (Enemy->Stats->Magic_Defense / 2); + } + else + { + attack = skillpower / (Enemy->Stats->Defense / 2); + } + attack *= mod; + attack += (Stats->Attack_Power / 2); // let's add our AP to the skillpower to make it worthwhile to cast skills. + + skillpower = (int)attack; + bool bflag = false; + Enemy->OnBeAttacked( this ); + if(skillpower <= 0) skillpower = 0; + if(IsPlayer()) + { + skillpower += (int)(skillpower * (Stats->ExtraDamage))/100; + } + if (skill->atkpower == 0) + { + skillpower = 0; // If the skill has a 0 atk power, it isn't meant to damage + Log( MSG_INFO, "This skill does no damage" ); + //ClearBattle( Battle ); + return; // probably need to add some alternative code here for legitimate skills that have no attack power. + } + if(skillpower >2046)skillpower = 2046; + //Log(MSG_DEBUG,"Cast a skill. damage is %i",skillpower); + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, skillpower ); + Enemy->damagecounter += skillpower;// is for AI + } + Enemy->Stats->HP -= skillpower; + if(IsPlayer()) + Log(MSG_DEBUG,"Player Spell did damage %i",skillpower); + if(IsMonster()) + Log(MSG_DEBUG,"Monster spell did damage %i",skillpower); + BEGINPACKET( pak, 0x7b6 ); + ADDWORD ( pak, Enemy->clientid ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, 0x000007f8 ); + ADDBYTE ( pak, 0x00 ); + if (Enemy->Stats->HP<1) + { + ADDWORD ( pak, skillpower + 0x8000 ); + } + else + { + ADDWORD ( pak, skillpower + 0x2000 ); + } + if(Enemy->IsDead()) + { + //Log(MSG_DEBUG,"Enemy died"); + CDrop* thisdrop = NULL; + ADDDWORD ( pak, 16 ); + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + thisdrop = Enemy->GetDrop( ); + if(thisdrop != NULL) + { + ADDFLOAT ( pak, thisdrop->pos.x*100 ); + ADDFLOAT ( pak, thisdrop->pos.y*100 ); + if(thisdrop->type==1) + { + ADDWORD( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + { + ADDWORD ( pak, GServer->BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, GServer->BuildItemData( thisdrop->item ) ); + } + ADDWORD ( pak, thisdrop->clientid ); + ADDWORD ( pak, thisdrop->owner ); + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, Enemy, thisdrop ); + OnEnemyDie( Enemy ); + } + else + { + ADDDWORD ( pak, 4 ); + GServer->SendToVisible( &pak, Enemy ); + bflag = GServer->AddBuffs( skill, Enemy, GetInt( ) ); + + //Send (de)buff information to the whole world + if(skill->nbuffs > 0 && bflag) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Battle->skilltarget ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( ) ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Enemy ); + } + } + if (deBuff) return; + //Send skill animation to the world + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; +} + +// use buff skill +void CCharacter::UseBuffSkill( CCharacter* Target, CSkills* skill ) +{ + bool bflag = false; + bflag = GServer->AddBuffs( skill, Target, GetInt( ) ); + if(skill->nbuffs > 0 && bflag == true) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( )/4 ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Target ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 1"); + } + + BEGINPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 2 nbuffs = %i",skill->nbuffs); + //Log(MSG_INFO,"Target HP = %i",Target->Stats->HP); +} + +// Use a skill +bool CCharacter::UseSkill( CSkills* skill, CCharacter *Target ) +{ + if (skill->atkpower > 0) + { + Log(MSG_INFO, "Need to do %i%s %s in range %i to target %i", + ( skill->atkpower ), + ( (skill->range > 0) ? " AOE" : "" ), + ( (GServer->isSkillTargetFriendly(skill)) ? "healing" : "damage" ), + ( skill->range ), + ( skill->target ) ); + } + for (int i = 0; i < 2; i++) { + Log(MSG_INFO, "Status ID: %i", skill->status[i]); + if (skill->status[i] == 0) continue; + CStatus* status = GServer->GetStatusByID(skill->status[i]); + if (status == NULL) continue; + /************************ + We'll probably need to use status->decrease (Figure out whether buf is up/down + status->repeat will tell us whether it's a one-time (Stat Boost [2]), repeat + (Recovery, continueing damage [1]) or Special (Status Effect [3]). + ************************/ + if (status->repeat == 1) // Continuous + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s over %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + } + else if (status->repeat == 2) // Once (Stat Boost) + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s for %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + + } else if (status->repeat == 3) // Status Effect (Poison,etc) + { + Log(MSG_INFO, "Need to give user status effect %i for %i seconds", skill->buff[i], + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ( skill->duration ) ); + + } + } + return true; +} + + diff --git a/Src/World Server/Copy of charfunctions.cpp b/Src/World Server/Copy of charfunctions.cpp new file mode 100644 index 0000000..69f0bab --- /dev/null +++ b/Src/World Server/Copy of charfunctions.cpp @@ -0,0 +1,570 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +// return true if this character is monster +bool CCharacter::IsMonster( ) +{ + if(CharType == 2 || CharType == 3) + return true; + return false; +} + +// return true if this character is player +bool CCharacter::IsPlayer( ) +{ + if(CharType == 1) + return true; + return false; +} + +//return true if this character is an NPC +bool CCharacter::IsNPC( ) +{ + if(CharType == 4) + return true; + return false; +} + +// return true if HP is lower to 1 +bool CCharacter::IsDead( ) +{ + if(Stats->HP <= 0) + return true; + return false; +} + +// return true if this character is summon +bool CCharacter::IsSummon( ) +{ + if(this == NULL) return false; + if(CharType == 3)// this line is erroring. No idea why + return true; + return false; +} + +// Virtual function [return item drop] +CDrop* CCharacter::GetDrop( ) +{ + return NULL; +} + +// start action [attack] +void CCharacter::StartAction( CCharacter* Target, BYTE action, UINT skillid) +{ + + BEGINPACKET( pak, 0 ); + switch(action) + { + case NORMAL_ATTACK: + { + //Log( MSG_INFO, "case NORMAL_ATTACK"); + if(Target == NULL)return; + RESETPACKET( pak, 0x798 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, Stats->Move_Speed ); + ADDFLOAT ( pak, Target->Position->current.x * 100 ); + ADDFLOAT ( pak, Target->Position->current.y * 100 ); + Battle->target = Target->clientid; + Battle->atktarget = Target->clientid; + Battle->atktype = action; + Battle->skilltarget = 0; + Battle->bufftarget = 0; + Battle->skillid = 0; + Position->destiny = Target->Position->current; + Position->lastMoveTime = clock(); + } + break; + case SKILL_ATTACK: //Log( MSG_INFO, "case SKILL_ATTACK"); + case SKILL_BUFF: + { + if(Target == NULL)return; + RESETPACKET( pak, 0x7b3 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, skillid ); + ADDWORD ( pak, 0x0000 ); + ADDFLOAT ( pak, Target->Position->current.x * 100 ); + ADDFLOAT ( pak, Target->Position->current.y * 100 ); + Battle->target = Target->clientid; + + if(action == SKILL_ATTACK) + { + //Log( MSG_INFO, "case SKILL_ATTACK"); + Battle->skilltarget = Target->clientid; + Battle->bufftarget = 0; + } + else + { + //Log( MSG_INFO, "case SKILL_BUFF"); + Battle->bufftarget = Target->clientid; + Battle->skilltarget = 0; + } + + Battle->atktype = action; + Position->destiny = Target->Position->current; + Battle->skillid = skillid; + Position->lastMoveTime = clock(); + } + break; + case SKILL_AOE://Log( MSG_INFO, "case SKILL_AOE"); + case BUFF_SELF://Log( MSG_INFO, "case BUFF_SELF"); + case BUFF_AOE: + { + //Log( MSG_INFO, "case BUFF_AOE"); + RESETPACKET( pak, 0x7b2); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, skillid ); + Battle->atktype = action; + Battle->skillid = skillid; + //Log( MSG_INFO, "BUFF_AOE packet sent"); + } + break; + case AOE_TARGET: + { + //Log( MSG_INFO, "case AOE_TARGET"); + //Target is NULL for this function when cast by a player + //Target is NOT NULL when it comes from a monster + if(Target != NULL) //monster attack + { + Position->skilltargetpos.x = Target->Position->current.x; + Position->skilltargetpos.y = Target->Position->current.y; + } + RESETPACKET( pak, 0x7b4 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, skillid ); + ADDFLOAT ( pak, Position->skilltargetpos.x * 100 ); + ADDFLOAT ( pak, Position->skilltargetpos.y * 100 ); + Battle->atktype = action; + Battle->skillid = skillid; + Battle->skilltarget = 0; + Battle->bufftarget = 0; + } + break; + default: + return; + break; + } + GServer->SendToVisible( &pak, this ); + Battle->contatk = true; + + //Log(MSG_INFO,"Battle Attacktype = %i",Battle->atktype); + return; +} + +bool CCharacter::IsOnBattle( ) +{ + if(Battle->atktype == 0) + { + if(Battle->iscasting == 1) + { + clock_t etime = clock() - Battle->lastAtkTime; + if( etime < CLOCKS_PER_SEC * 100 / Stats->Attack_Speed ) + { + //Log(MSG_DEBUG,"Iscasting is true while NOT in battle. Reset to false."); + Battle->iscasting = 0; + return false; + } + } + return false; + } + switch(Battle->atktype) + { + case NORMAL_ATTACK: + { + if(Battle->atktarget != 0) return true; + } + break; + case SKILL_ATTACK: + case AOE_TARGET: + { + if(Battle->skilltarget != 0 && Battle->skillid != 0) return true; + } + break; + case SKILL_BUFF: + { + if(Battle->bufftarget != 0 && Battle->skillid != 0) return true; + } + break; + case SKILL_AOE: + //case SKILL_SELF: //no such skill type + case BUFF_SELF: + case BUFF_AOE: + { + if(Battle->skillid != 0) return true; + } + break; + default: return false; + } +} + +bool CCharacter::CanAttack( ) +{ + + clock_t etime = clock() - Battle->lastAtkTime; + if (Stats->Attack_Speed < 1) + { + Log( MSG_INFO, "weapon with no attackspeed."); + return false; + } + if(Battle->iscasting == 1) + { + if( etime < CLOCKS_PER_SEC * 25 / Stats->Attack_Speed ) //should make the delay 4 times longer + { + //if(IsPlayer()) + // Log(MSG_INFO,"Cannot use magic attack yet. Last attack time = %i",Battle->lastAtkTime); + return false; + } + else + { + Battle->iscasting = 0; + //if(IsPlayer()) + // Log(MSG_INFO,"OK Now we can use a new (magic or normal) attack."); + return true; + } + } + else //normal attack + { + if( etime < CLOCKS_PER_SEC * 100 / Stats->Attack_Speed ) + { + //if(IsPlayer()) + // Log(MSG_INFO,"Cannot attack yet. Last attack time = %i",Battle->lastAtkTime); + return false; + } + //if(IsPlayer()) + // Log(MSG_INFO,"OK Now we can use a normal attack"); + } + return true; +} + +// return true if target is reached +bool CCharacter::IsTargetReached( CCharacter* Enemy, CSkills* skill ) +{ + CMap* map = GServer->MapList.Index[Position->Map]; + if(map == NULL)return false; + float distance = GServer->distance( Position->current, Enemy->Position->current ); + if(skill == NULL) //it's a normal attack + { + if(distance <= Stats->Attack_Distance) + { + return true; + } + else + { + Position->destiny = Enemy->Position->current; + } + } + else + { + if(distance <= skill->range) + { + return true; + } + else + { + Position->destiny = Enemy->Position->current; + } + } + //Log(MSG_INFO,"Monster cannot attack. Too far away"); + return false; +} + +// return true if the character is moving +bool CCharacter::IsMoving( ) +{ + if(Position->current.x == Position->destiny.x && Position->current.y == Position->destiny.y ) + return false; + return true; +} + +// VIRTUAL [add player damage] +void CCharacter::AddDamage( CCharacter* enemy, long int hitpower) +{ + +} + +// return character target +CCharacter* CCharacter::GetCharTarget( ) +{ + CCharacter* thistarget = GServer->MapList.Index[Position->Map]->GetCharInMap( Battle->target ); + if (thistarget != NULL) + return thistarget; + //if I don't currently have a target then return the character who just hit me + return GServer->MapList.Index[Position->Map]->GetCharInMap( Battle->hitby ); +} + +// VIRTUAL [reduce ammon/bullets/cannons] +void CCharacter::ReduceABC( ) +{ + +} + +// VIRTUAL [get extra buff value] +int CCharacter::GetEVBuff( ) +{ + return 0; +} + +// VIRTUAL [return intelligence] +unsigned int CCharacter::GetInt( ) +{ + return 15; +} + + +// Check For Debuffs +void CCharacter::RefreshBuff( ) +{ + bool bflag = false; + for( UINT i=0;i<30;i++) + { + if(MagicStatus[i].Buff == 0) continue; + clock_t etime = clock() - MagicStatus[i].BuffTime; + if( etime >= MagicStatus[i].Duration * CLOCKS_PER_SEC ) + { + switch(MagicStatus[i].Status) + { + case 18: // attack power up + case 19: // attack power down + case 48: // attack power up + if(i<15) + Status->Attack_up = 0xff; + else + Status->Attack_down = 0xff; + Stats->Attack_Power = GetAttackPower( ); + break; + case 20: // def up + case 21: // def down + case 49: // def up + if(i<15) + Status->Defense_up= 0xff; + else + Status->Defense_down = 0xff; + Stats->Defense = GetDefense( ); + break; + case 24: //accuracy up + case 25: //accuracy down + case 51: //attack accuracy up. + if(i<15) + Status->Accury_up= 0xff; + else + Status->Accury_down = 0xff; + Stats->Accury = GetAccury( ); + break; + case 22: // macic resistance up + case 23: // magic resistance down + case 50: // magic resistance up + if(i<15) + Status->Magic_Defense_up = 0xff; + else + Status->Magic_Defense_down = 0xff; + Stats->Magic_Defense = GetMagicDefense( ); + break; + case 28: //dodge up + case 29: //dodge down + case 53: //dodge rate up + if(i<15) + Status->Dodge_up = 0xff; + else + Status->Dodge_down = 0xff; + Stats->Dodge = GetDodge( ); + break; + case 14: //dash + case 15: //slow + case 46: //movement speed increased + if(i<15) + Status->Dash_up = 0xff; + else + Status->Dash_down = 0xff; + Stats->Move_Speed = GetMoveSpeed( ); + break; + case 16: // haste attack + case 17: // slow attack + case 47: // attack speed up + if(i<15) + Status->Haste_up = 0xff; + else + Status->Haste_down = 0xff; + Stats->Attack_Speed = GetAttackSpeed( ); + break; + case 26: // crit up + case 27: // crit down + case 52: // crit up + if(i<15) + Status->Critical_up = 0xff; + else + Status->Critical_down = 0xff; + Stats->Critical = GetCritical( ); + break; + case 12: // max HP up + case 44: // max HP up + if(i<15) + Status->HP_up = 0xff; + else + Status->HP_down = 0xff; + Stats->MaxHP = GetMaxHP( ); + break; + case 13: // max MP up + case 45: // max MP up + if(i<15) + Status->MP_up = 0xff; + else + Status->MP_down = 0xff; + Stats->MaxMP = GetMaxMP( ); + break; + case 32: // faint + Status->Stuned = 0xff; + Status->CanAttack = true; + //printf("removing stun\n"); + break; + case 7: case 8: case 9: case 10: case 11: //poisoned + Status->Poisoned = 0xff; + //printf("removing poison\n"); + break; + case 30: // muted + Status->Muted = 0xff; + Status->CanCastSkill = true; + break; + case 31: // sleep May need to be fixed later to accomodate multiple status effects. + Status->Sleep = 0xff; + Status->CanMove = true; + Status->CanCastSkill = true; + Status->CanAttack = true; + break; + case 54: //A_GMExtra_Damage: + case 36: //A_Extra_Damage: + if(i<15) + { + Status->ExtraDamage_up = 0xff; + Stats->ExtraDamage = 0xff; + } + else + { + Status->ExtraDamage_down = 0xff; + Stats->ExtraDamage = 0xff; + } + + break; + case 56: // Taunt + Status->Taunt = 0xff; + break; + } + + MagicStatus[i].Status = 0; + MagicStatus[i].Buff = 0; + MagicStatus[i].BuffTime = 0; + MagicStatus[i].Duration = 0; + MagicStatus[i].Value = 0; + bflag = true; + } + else if (MagicStatus[i].Status >= 7 && MagicStatus[i].Status <= 11 && etime > 1*CLOCKS_PER_SEC) //Do poison dmg every 1.5 seconds + { + Stats->HP -= MagicStatus[i].Status; //Actually take 7, 8, 9, 10 or 11 from the health. Based on the Status itself + MagicStatus[i].BuffTime += 1*CLOCKS_PER_SEC; + MagicStatus[i].Duration -= 1; + //printf("did %i poison dmg to the player, still %i seconds and %i HP remain \n", MagicStatus[i].Status, MagicStatus[i].Duration, Stats->HP); + + //A bunch of messy code to send dmg packet + BEGINPACKET( pak, 0x7b6 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, 0 ); + ADDDWORD ( pak, 0x000007f8 ); + ADDBYTE ( pak, 0x00 ); + ADDDWORD ( pak, MagicStatus[i].Status ); + + //If Enemy is killed + if( IsDead()) + { + //printf("char died\n"); + CDrop* thisdrop = NULL; + ADDDWORD ( pak, 16 ); + if( !IsSummon( ) && !IsPlayer( )) + { + thisdrop = GetDrop( ); + if( thisdrop!=NULL) + { + ADDFLOAT ( pak, thisdrop->pos.x*100 ); + ADDFLOAT ( pak, thisdrop->pos.y*100 ); + if( thisdrop->type==1) + { + ADDDWORD( pak, 0xccccccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + { + ADDDWORD ( pak, GServer->BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, GServer->BuildItemData( thisdrop->item ) ); + } + ADDWORD ( pak, thisdrop->clientid ); + ADDWORD ( pak, thisdrop->owner ); + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, this, thisdrop ); + } + + //If enemy is still alive + else + { + ADDDWORD ( pak, 4 ); + GServer->SendToVisible( &pak, this ); + } + } + } + if(bflag) + { + BEGINPACKET( pak,0x7b7 ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, GServer->BuildBuffs( this ) ); + GServer->SendToVisible( &pak, this ); + } +} + +// VIRTUAL [return party pointer] +CParty* CCharacter::GetParty( ) +{ + return NULL; +} + +// VIRTUAL [return clan pointer] +CLAN* CCharacter::GetClan( ) +{ + return NULL; +} + +bool CCharacter::IsAttacking( ) +{ + if(IsOnBattle( )) + { + switch(Battle->atktype) + { + case SKILL_BUFF: + case BUFF_SELF: + case BUFF_AOE: + return false; + } + } + else return false; + return true; +} diff --git a/Src/World Server/MainProcess.cpp b/Src/World Server/MainProcess.cpp new file mode 100644 index 0000000..55c02d0 --- /dev/null +++ b/Src/World Server/MainProcess.cpp @@ -0,0 +1,478 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Map Process +PVOID MapProcess( PVOID TS ) +{ + bool only_npc = false; + while(GServer->ServerOnline) + { + pthread_mutex_lock( &GServer->PlayerMutex ); + pthread_mutex_lock( &GServer->MapMutex ); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + only_npc = true; + else + only_npc = false; + //continue; + + if(!only_npc) + { + // Player update //------------------------ + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if(!player->Session->inGame && player->Session->Respawned == 1) //only set in pakUserDied on receiving respawn packet 0x753 + { + Log( MSG_INFO, "ingame: = %i respawned: = %i",player->Session->inGame,player->Session->Respawned); + if(!player->RespawnPlayer(player)) //so we respawn the player + { + Log( MSG_INFO, "Player not respawned correctly"); + } + else + { + Log( MSG_INFO, "Player Respawned" ); + } + player->Session->inGame = true; + player->Session->Respawned = 2; //testing toggle + } + if(player->IsDead( )) continue; + if(player->UpdateValues( )) //checks if player is on a cart. otherwise returns true + { + //if(player->Session->Respawned == 2){Log( MSG_INFO, "Running UpdatePosition" );} + player->UpdatePosition( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran UpdatePosition" );} + } + + if(player->IsOnBattle( )) + { + //if(player->Session->Respawned == 2){Log( MSG_INFO, "Player is in battle so call DoAttack" );} + player->DoAttack( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran DoAttack" );} + } + player->RefreshBuff( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran refreshbuff" );} + player->PlayerHeal( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran playerheal" );} + player->Regeneration( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran regeneration" );} + player->CheckPlayerLevelUP( ); + //if(player->Session->Respawned == 2){Log( MSG_INFO, "ran Check Levelup" );} + if(3600000 < (UINT)GServer->round((clock( ) - player->Session->LogTime))) //1 hour since login + { + //if(player->Session->Respawned == 2){Log( MSG_INFO, "updating logtime stuff" );} + player->Session->logtime += 1; + player->Session->TotalLogTime ++; + GServer->DB->QExecute("UPDATE accounts SET logtime = %i, totlogtime = %i WHERE id = %i", player->Session->logtime, player->Session->TotalLogTime, player->Session->userid); + player->Session->LogTime = clock(); + } + //if(player->Session->Respawned == 2){Log( MSG_INFO, "Checking autosave" );} + if( GServer->Config.AUTOSAVE == 1 ) + { + clock_t etime = clock() - player->lastSaveTime; + if( etime >= GServer->Config.SAVETIME*1000 ) + { + player->savedata( ); + player->lastSaveTime = clock(); + } + //if(player->Session->Respawned == 2){Log( MSG_INFO, "saved player data" );} + } + //if(player->Session->Respawned == 2){Log( MSG_INFO, "Checking ItemXPTimer" );} + if(player->Stats->ItemXPTimer > 0) // does the player have a timed XPRate buff? + { + clock_t etime = clock() - player->Stats->ItemXPTime; + if(etime >= player->Stats->ItemXPTimer) // turn off timed XPRate buff when time exceeds timer + { + player->Stats->ItemXPTime = 0; + player->Stats->ItemXPRate = 1; + } + //if(player->Session->Respawned == 2){Log( MSG_INFO, "set XPRates and stuff" );} + } + player->Session->Respawned = 0; //resetting respawned status + } + // Monster update //------------------------ + pthread_mutex_lock( &map->MonsterMutex ); + //spawn TD monsters + unsigned nextmon = map->TDMobList[map->TDNextSpawn]; + if(nextmon != 0) + { + //Log(MSG_DEBUG,"Spawning TD mobs"); + clock_t etime = clock() - map->lastTDSpawnTime; + if(etime >= map->TDMobDelay) // check if elapsed time is greater than spawn delay + { + //spawn the next monster on the list + //Log(MSG_DEBUG,"Spawning TD mob of type %i at position %i in the list for map %i",nextmon, map->TDNextSpawn, map->id); + map->AddMonster(nextmon, GServer->WPList[map->id][1].pos, 0, 0, 0, 1, 0, 999 ); //spawn a TD mob with no AI + //Log(MSG_DEBUG,"Spawned mob in addmonster"); + map->TDMobList[map->TDNextSpawn] = 0; //clear the spawn + //Log(MSG_DEBUG,"cleared the spawn"); + map->TDNextSpawn++; //move the pointer + //Log(MSG_DEBUG,"moved the pointer"); + if(map->TDNextSpawn > 100)map->TDNextSpawn = 0; + map->lastTDSpawnTime = clock(); + } + } + //TD spawn end + + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* monster = map->MonsterList.at(j); + + //UINT thistimer = monster->AItimer; + clock_t etime = clock() - monster->lastAiUpdate; + //if(etime < monster->AItimer) continue; //not time to run AI for this monster yet + if(monster->hitcount == 0xFF)//this is a delay for new monster spawns this might olso fix invisible monsters(if they attack directly on spawning the client dosn't get the attack packet(its not in it's visible list yet)) + { + monster->hitcount = 0; + //monster->DoAi(monster->thisnpc->AI, 0); + monster->DoAi(monster->monAI, 0); + monster->lastAiUpdate=clock(); + } + + //still need this for special spawns + if(!map->IsNight( ) && monster->Status->nightonly)// if day, delete all night time monsters + { + //Log( MSG_INFO, "Night Only monster deleted. Type %i", monster->montype); + map->DeleteMonster( monster, true, j ); + continue; + } + if(map->IsNight() && monster->Status->dayonly) + { + //Log( MSG_INFO, "Day Only monster deleted. Type %i", monster->montype); + map->DeleteMonster( monster, true, j ); + continue; + } + //Do TD stuff + if(monster->monAI == 999 && etime >= monster->AItimer) //It's a TD mob + { + monster->UpdatePosition( ); + float distance = GServer->distance( monster->Position->current, monster->Position->destiny ); + //Log( MSG_INFO, "Found a TD monster %f from target. Waypoint type = %i stance %i speed %i", distance,GServer->WPList[map->id][monster->NextWayPoint].WPType,monster->Stats->stance, monster->Stats->Move_Speed); + //Log( MSG_INFO, "monster location X: %f Y: %f",monster->Position->current.x, monster->Position->current.y); + //Log( MSG_INFO, "WP location X: %f Y: %f ", GServer->WPList[map->id][monster->NextWayPoint].pos.x,GServer->WPList[map->id][monster->NextWayPoint].pos.y); + if(distance < 1) //monster has reached it's destination + { + switch(GServer->WPList[map->id][monster->NextWayPoint].WPType) + { + case 1: //Start waypoint + case 2: //regular waypoint. Increment waypoint counter. Set detiny to next waypoint + { + //Log( MSG_INFO, "Monster reached waypoint %i type: %i", monster->NextWayPoint,GServer->WPList[map->id][monster->NextWayPoint].WPType); + monster->NextWayPoint++; + monster->Position->destiny.x = GServer->WPList[map->id][monster->NextWayPoint].pos.x; + monster->Position->destiny.y = GServer->WPList[map->id][monster->NextWayPoint].pos.y; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x * 100 ); + ADDFLOAT ( pak, monster->Position->destiny.y * 100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); //should be walking + GServer->SendToVisible(&pak, monster); + } + break; + case 3: //Final waypoint. do stuff. suicide + { + //Log( MSG_INFO, "Reached final waypoint"); + //monster->Stats->HP = 0; + map->DeleteMonster( monster, true, j ); + //Log( MSG_INFO, "monster successfully deleted"); + continue; + } + break; + default: + Log( MSG_INFO, "WPType %i not recognized",GServer->WPList[map->id][monster->NextWayPoint].WPType); + break; + } + } + else + { + //send move packet again to try to synchronise the monster movement + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x * 100 ); + ADDFLOAT ( pak, monster->Position->destiny.y * 100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); //should be walking + GServer->SendToVisible(&pak, monster); + } + monster->lastAiUpdate = clock(); + } + //end TD stuff + + if(!monster->PlayerInRange( )) continue; + //if(!monster->UpdateValues( )) continue; + monster->UpdatePosition( ); + if(monster->IsOnBattle( )) + { + monster->DoAttack( ); // why was this commented? Monsters were not attacking + //monster->DoAi(monster->thisnpc->AI, 2); //don't use thisnpc->AI any more. + if(etime >= monster->AItimer) + { + monster->DoAi(monster->monAI, 2); + monster->lastAiUpdate = clock(); + } + //Log(MSG_INFO,"Monster type: %i current HP: %i",monster->montype, monster->Stats->HP); + } + else + { + //monster->DoAi(monster->thisnpc->AI, 1); + if(etime >= monster->AItimer) + { + monster->DoAi(monster->monAI, 1); + monster->lastAiUpdate = clock(); + } + } + monster->RefreshBuff( ); + if (monster->IsSummon()) + { + monster->SummonUpdate(monster,map, j); + continue; + } + if(monster->IsDead()) + { + if(clock() - monster->DeathDelayTimer > GServer->Config.DeathDelay) + { + //Log(MSG_DEBUG,"Found dead monster montype %i",monster->montype); + monster->OnDie( ); //all this does is give exp + //Log(MSG_DEBUG,"back from giving exp"); + monster->DoAi(monster->monAI, 5); + //Log(MSG_DEBUG,"ran AI"); + map->DeleteMonster( monster, true, j ); + //Log(MSG_DEBUG,"deleted monster"); + continue; + } + /*else + { + Log(MSG_DEBUG,"Dead monster found. waiting for death delay timer"); + }*/ + } + } + } + if(only_npc) + pthread_mutex_lock( &map->MonsterMutex ); + + //AIP for NPCs + for(UINT j=0;jNPCList.size();j++) + { + CNPC* npc = map->NPCList.at(j); + if(npc->thisnpc->AI != 0) + { + CAip* script = NULL; + for(unsigned k=0; k < GServer->AipList.size(); k++) + { + if (GServer->AipList.at(k)->AInumber == npc->thisnpc->AI) + { + script = GServer->AipList.at(k); + break; + } + } + if(script == NULL) + { + //Log( MSG_WARNING, "Invalid AI script for AI %i", npc->thisnpc->AI ); + continue; + } + UINT thistimer = script->minTime * 1000; //seems to be set in seconds in AIP + if(thistimer<(UINT)GServer->round((clock( ) - npc->lastAiUpdate))) //check AIP conditions when the timer calls for it + { + CNPCData* thisnpc = GServer->GetNPCDataByID( npc->npctype ); + if(thisnpc == NULL) + { + Log( MSG_WARNING, "Invalid montype %i", npc->npctype ); + continue; + } + CMonster* monster = new (nothrow) CMonster( npc->pos, npc->npctype, map->id, 0, 0 ); + monster->thisnpc = thisnpc; + monster->CharType = 4; + int AIP = monster->thisnpc->AI; + int lma_previous_eventID = npc->thisnpc->eventid; + monster->DoAi(monster->thisnpc->AI, 1); + //check if eventID changed, if we do it in AIP conditions / actions, it just fails... + if (lma_previous_eventID != npc->thisnpc->eventid) + { + //Log(MSG_WARNING,"Event ID not the same NPC %i from %i to %i in map %i, npc->thisnpc->eventid=%i !", npc->npctype, lma_previous_eventID, monster->thisnpc->eventid, map->id, npc->thisnpc->eventid); + npc->thisnpc->eventid = monster->thisnpc->eventid; + BEGINPACKET( pak, 0x790 ); + ADDWORD ( pak, npc->clientid ); + ADDWORD ( pak, npc->thisnpc->eventid ); + GServer->SendToMap(&pak, map->id); + } + + + if(AIP == GServer->Config.AIWatch) + Log(MSG_DEBUG,"NPC AI number %i successfully run",monster->thisnpc->AI); + if(monster->IsOnBattle()) + { + //Log(MSG_DEBUG,"NPC %i is registered as in battle",monster->thisnpc->id); + monster->DoAttack( ); + } + //map->DeleteMonster(monster); + //delete monster; + npc->lastAiUpdate = clock(); + if(AIP == GServer->Config.AIWatch) + Log(MSG_DEBUG,"LastAiUpdate clock reset"); + } + } + + //LMA: Sometimes another NPC does the job for you. + if(npc->thisnpc->eventid != GServer->ObjVar[npc->npctype][0]) + { + int new_event_id = GServer->ObjVar[npc->npctype][0]; + //Log(MSG_WARNING,"(2)Event ID not the same NPC %i from %i to %i in map %i, npc->thisnpc->eventid=%i !",npc->npctype,npc->thisnpc->eventid,new_event_id,map->id,npc->thisnpc->eventid); + npc->thisnpc->eventid = new_event_id; + //LMA: We have to change the event ID here since we didn't send the clientID :( + BEGINPACKET( pak, 0x790 ); + ADDWORD ( pak, npc->clientid ); + ADDWORD ( pak, npc->thisnpc->eventid ); + GServer->SendToMap(&pak,map->id); + } + } + + pthread_mutex_unlock( &map->MonsterMutex ); + } + pthread_mutex_unlock( &GServer->MapMutex ); + pthread_mutex_unlock( &GServer->PlayerMutex ); + #ifdef _WIN32 + Sleep(GServer->Config.MapDelay); + #else + usleep(GServer->Config.MapDelay); + #endif + } + pthread_exit( NULL ); + return 0; +} + +// Visibility Process +PVOID VisibilityProcess(PVOID TS) +{ + while(GServer->ServerOnline) + { + pthread_mutex_lock( &GServer->PlayerMutex ); + pthread_mutex_lock( &GServer->MapMutex ); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + map->CleanDrops( );//moved for test + map->RespawnMonster( );//moved for test + if( map->PlayerList.size()<1 ) + continue; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if(!player->Session->inGame) continue; + if(!player->VisiblityList()) Log(MSG_WARNING, "Visibility False: %u", player->clientid ); + + } + } + pthread_mutex_unlock( &GServer->MapMutex ); + pthread_mutex_unlock( &GServer->PlayerMutex ); + #ifdef _WIN32 + Sleep(GServer->Config.VisualDelay); + #else + usleep(GServer->Config.VisualDelay); + #endif + } + pthread_exit(NULL); + return 0; +} + +// World Process +PVOID WorldProcess( PVOID TS ) +{ + while( GServer->ServerOnline ) + { + pthread_mutex_lock( &GServer->MapMutex ); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + continue; + map->UpdateTime( ); + pthread_mutex_lock( &map->DropMutex ); + //map->CleanDrops( ); + pthread_mutex_unlock( &map->DropMutex ); + pthread_mutex_lock( &map->MonsterMutex ); + //map->RespawnMonster( ); + pthread_mutex_unlock( &map->MonsterMutex ); + } + pthread_mutex_unlock( &GServer->MapMutex ); + GServer->RefreshFairy( ); + #ifdef _WIN32 + Sleep(GServer->Config.WorldDelay); + #else + usleep(GServer->Config.WorldDelay); + #endif + } + pthread_exit(NULL); + return 0; +} + +// Shutdown Server Process +void ShutdownServer(PVOID TS) +{ + int minutes = (int)TS; + #ifdef _WIN32 + Sleep(minutes*60000); + #else + usleep(minutes*60000); + #endif + Log( MSG_INFO, "Saving User Information... " ); + GServer->DisconnectAll(); + Log( MSG_INFO, "Waiting Process to ShutDown... " ); + GServer->ServerOnline = false; + int status,res; + res = pthread_join( GServer->WorldThread[0], (PVOID*)&status ); + if(res) + { + Log( MSG_WARNING, "World thread can't be joined" ); + } + else + { + Log( MSG_INFO, "World thread closed." ); + } + res = pthread_join( GServer->WorldThread[1], (PVOID*)&status ); + if(res) + { + Log( MSG_WARNING, "Visibility thread can't be joined" ); + } + else + { + Log( MSG_INFO, "Visibility thread closed." ); + } + res = pthread_join( GServer->MapThread[0], (PVOID*)&status ); + if(res) + { + Log( MSG_WARNING, "Map thread can't be joined" ); + } + else + { + Log( MSG_INFO, "Map thread closed." ); + } + Log( MSG_INFO, "All Threads Closed." ); + GServer->isActive = false; + pthread_exit(NULL); +} diff --git a/Src/World Server/Monster.cpp b/Src/World Server/Monster.cpp new file mode 100644 index 0000000..0fb0187 --- /dev/null +++ b/Src/World Server/Monster.cpp @@ -0,0 +1,195 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" + +// update attack values and destiny position +bool CMonster::UpdateValues( ) +{ + return true; //bypass for AIP + /*if(IsSummon( )) + { + CPlayer* thisclient = GetOwner( ); + if(thisclient!=NULL)if(thisclient->Stats->HP>0) + { + if(!IsOnBattle( ) && thisclient->IsAttacking( )) + { + Battle->target = thisclient->Battle->target; + Battle->atktarget = Battle->target; + Battle->atktype = NORMAL_ATTACK; + Battle->contatk = true; + CCharacter* Enemy = GetCharTarget( ); + if(Enemy!=NULL) + StartAction( Enemy, NORMAL_ATTACK ); + } + else + if(!IsOnBattle( )) + { + Position->source = thisclient->Position->current; + float distance = GServer->distance( Position->destiny , thisclient->Position->current ); + if((distance>15 && !IsOnBattle()) || distance>50) + Move( ); + } + } + else + { + UnspawnMonster( ); + return false; + } + } + if(!IsMoving( ) && !IsOnBattle( ) && CanMove( )) + { + clock_t etime = clock() - Position->lastMoveTime; + if(etime > 20*CLOCKS_PER_SEC) + Move( ); + } + if(!IsSummon( )) + { + CPlayer* player = GetNearPlayer( 10 ); + if(player!=NULL) + OnEnemyOnSight( (CCharacter*)player ); + CCharacter* Enemy = GetCharTarget( ); + if(Enemy!=NULL) + { + //need a skill timer here. It casts all teh time otherwise. + int randv = GServer->RandNumber(1, 100); + if(randv < 50 || GServer->Config.MobSkill == 0)//normal attack + { + StartAction( Enemy, NORMAL_ATTACK ); + } + else //skill attack + { + CSkills* skill = GServer->GetSkillByID(GServer->Config.MobSkill); + switch (skill->type) + { + case 6: + Log( MSG_DEBUG, "Monster used an attack skill. id = %i",GServer->Config.MobSkill); + StartAction( Enemy, SKILL_ATTACK, GServer->Config.MobSkill); + break; + case 17: + Log( MSG_DEBUG, "Monster used an AOE skill. id = %i",GServer->Config.MobSkill); + StartAction( Enemy, SKILL_AOE, GServer->Config.MobSkill); + break; + } + } + } + }*/ + if(IsMonster( )) + {} + return true; +} + +// Spawn a monster +void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) +{ + BEGINPACKET( pak, 0x792 ); + ADDWORD ( pak, clientid ); + ADDFLOAT ( pak, Position->current.x*100 ); + ADDFLOAT ( pak, Position->current.y*100 ); + ADDFLOAT ( pak, Position->destiny.x*100 ); + ADDFLOAT ( pak, Position->destiny.y*100 ); + /* + if(IsDead( )) + { + ADDWORD ( pak, 0x0003 ); + ADDWORD ( pak, 0x0000 ); + } + else if(IsOnBattle( )) + { + ADDWORD ( pak, 0x0002 ); + ADDWORD ( pak, Battle->target ); + } + else if(IsMoving( )) + { + ADDWORD ( pak, 0x0001 ); + ADDWORD ( pak, 0x0000 ); + } + else + { + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + } + */ + ADDBYTE ( pak, 0x00 ); + ADDDWORD ( pak, 0x00000000 ); + + ADDDWORD ( pak, Stats->HP ); + if(thismon->owner != player->clientid) + { + CMap* map = GServer->MapList.Index[Position->Map]; + if(IsSummon( ) && map->allowpvp!=0) {ADDDWORD( pak, 0x00000064 );} //Hostil + else if (IsSummon( ) && map->allowpvp==0) {ADDDWORD ( pak, 0x00000000 );}//Friendly + else {ADDDWORD( pak, 0x00000064 );} //Hostil + } + else {ADDDWORD( pak, 0x00000000 );}//Friendly + ADDDWORD( pak, GServer->BuildBuffs( this ) ); + ADDWORD ( pak, montype ); + ADDWORD ( pak, 0x0000 ); + if(IsSummon( )) + { + ADDWORD( pak, owner ); + ADDWORD( pak, 0x0000 ); //id del skill (si es summon de skill) + } + player->client->SendPacket( &pak ); +} + +// UnSpawn a Monster Why the fuck do we need this?????????? +bool CMonster::UnspawnMonster( ) +{ + Log( MSG_INFO, "Unspawning monster %i ",this->montype); + GServer->MapList.Index[Position->Map]->DeleteMonster( this, true ); + return true; +} + +// Move a mob (random place) +void CMonster::Move( ) +{ + Position->destiny = GServer->RandInCircle( Position->source, 5 ); + Position->lastMoveTime = clock(); + //ClearBattle( Battle ); + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, Stats->Move_Speed ); //speed + ADDFLOAT ( pak, Position->destiny.x*100 ); + ADDFLOAT ( pak, Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + //ADDBYTE ( pak, 0x01 ); //replaced for AIP + ADDBYTE ( pak, Stats->stance ); + GServer->SendToVisible(&pak, this); +} + +// Move a mob (specified point) +void CMonster::MoveTo( fPoint nPos, bool randcircle ) +{ + if(randcircle) Position->destiny = GServer->RandInCircle( nPos, 5 ); + else Position->destiny = nPos; + Position->lastMoveTime = clock(); + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, 0x0000 ); // ??? + ADDWORD ( pak, Stats->Move_Speed ); + ADDFLOAT ( pak, Position->destiny.x*100 ); + ADDFLOAT ( pak, Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + //ADDBYTE ( pak, 0x01 ); // replaced for AIP + ADDBYTE ( pak, Stats->stance); + GServer->SendToVisible(&pak, this ); +} diff --git a/Src/World Server/MonsterDamage.cpp b/Src/World Server/MonsterDamage.cpp new file mode 100644 index 0000000..3b13448 --- /dev/null +++ b/Src/World Server/MonsterDamage.cpp @@ -0,0 +1,21 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" diff --git a/Src/World Server/MonsterEvents.cpp b/Src/World Server/MonsterEvents.cpp new file mode 100644 index 0000000..ae5ab90 --- /dev/null +++ b/Src/World Server/MonsterEvents.cpp @@ -0,0 +1,435 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" + +// called when a monster is attacked [attack/use atk skill/use buff/run/summon]//Enemy is the one attacking? +bool CMonster::OnBeAttacked( CCharacter* Enemy ) +{ + + Battle->hitby = Enemy->clientid; + if(!IsOnBattle( )) + { + CMap* map = GServer->MapList.Index[Position->Map]; + map->UpdateSpawnCounter( montype, Status->spawnid, 3); // update the counters + if(thisnpc->aggresive == 0) //prevent monsters with zero aggresion fighting back. + return true; + //StartAction( Enemy, NORMAL_ATTACK, 0 ); // temporary removal for testing + } + return true; +} + +// called when a monster die [give exp/give extra drop] +bool CMonster::OnDie( ) +{ + /* Don't need this now. AIP takes care of it. + CMap* map = GServer->MapList.Index[Position->Map]; + if(map->ghost!=0) + { + if((map->IsNight( ) || map->ghost==2) && !IsGhost( ) && !IsGhostSeed( ) && !IsSummon( )) + { + UINT gs = GServer->RandNumber( 0, 100 ); + if(gs<30) // 30% / 70% + { // spawn a ghost seed [for now will be 701 [level 1 ghost seed] ] + map->AddMonster( 701, Position->current, 0, Position->respawn, 0 ); + } + } + }*/ + + GServer->GiveExp( this ); + return true; +} +// called when we create a new monster [attack players?] +bool CMonster::OnSpawn( bool Attack ) +{ + if(IsGhost( ) && Attack ) + { + CPlayer* player = GetNearPlayer( ); + if(player == NULL) // no players or too far + return true; + StartAction( (CCharacter*)player, NORMAL_ATTACK, 0 ); + } + return true; +} + +// [not]called when a monster is almost dead [run/use only skills/call a healer? :S xD] +bool CMonster::OnAlmostDie( ) +{ + return true; +} + +// called when a enemy is on sight +bool CMonster::OnEnemyOnSight( CCharacter* Enemy ) +{ + //return true //enable for AI stuff + clock_t etime = clock() - lastSighCheck; + if(etime<5000) return true; + if(!IsOnBattle( )) + { + if(thisnpc->aggresive>1) + { + UINT aggro = GServer->RandNumber(2,15); + if(thisnpc->aggresive>=aggro && !IsGhostSeed( )) + { + StartAction( (CCharacter*) Enemy, NORMAL_ATTACK, 0 ); + } + else + if(IsGhostSeed( ) || thisnpc->aggresive>5) + MoveTo( Enemy->Position->current, true ); + } + lastSighCheck = clock(); + } + return true; +} + +// called when enemy die +bool CMonster::OnEnemyDie( CCharacter* Enemy ) +{ + Position->destiny = Position->source; + ClearBattle( Battle ); + MoveTo( Position->source ); + return true; +} + +bool CMonster::OnFar( ) +{ + if(!IsSummon())return true; + Position->destiny = Position->source; + ClearBattle( Battle ); + MoveTo( Position->source ); + return true; +} +/*bool CMonster::Guardiantree(CMonster* monster,CMap* map) +{ + return true; + if(Stats->HP>9000)monster->hitcount=0; + switch (monster->hitcount) + { + case 0: + if(Stats->HP<9000) + { + for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, 0, 10 ); + newmonster->hitcount=0; + } + monster->hitcount=1; + } + break; + case 1: + if(Stats->HP<8000) + { + /* for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + }*/ + /*monster->hitcount=2; + } + break; + case 2: + if(Stats->HP<7000) + { + for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + } + monster->hitcount=3; + } + break; + case 3: + if(Stats->HP<6000) + { + /* for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + }*/ + /*monster->hitcount=4; + } + break; + case 4: + if(Stats->HP<5000) + { + for(unsigned char i=0;i<5;i++) + { + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + } + monster->hitcount=5; + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GServer->GetNewClientID(); + thisdrop->type = 2; // ITEM + thisdrop->pos = GServer->RandInCircle( monster->Position->current, 3 ); + thisdrop->posMap = monster->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = 1; + thisdrop->item.count = thisdrop->amount; + thisdrop->owner = 0; + thisdrop->item.itemtype=10; + thisdrop->item.itemnum=rand()%4+10; + map->AddDrop( thisdrop ); + } + break; + case 5: + if(Stats->HP<4000) + { + /* for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + }*/ + /*monster->hitcount=6; + } + break; + case 6: + if(Stats->HP<3000) + { + for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + } + monster->hitcount=7; + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GServer->GetNewClientID(); + thisdrop->type = 2; // ITEM + thisdrop->pos = GServer->RandInCircle( monster->Position->current, 3 ); + thisdrop->posMap = monster->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = 1; + thisdrop->item.count = thisdrop->amount; + thisdrop->owner = 0; + thisdrop->item.itemtype=10; + thisdrop->item.itemnum=rand()%4+29; + map->AddDrop( thisdrop ); + } + break; + case 7: + if(Stats->HP<2000) + { + /* for(unsigned char i=0;i<5;i++){ + fPoint position = GServer->RandInCircle( monster->Position->current, 5 ); + CMonster* newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=0; + }*/ + /*monster->hitcount=8; + } + break; + default: + monster->hitcount=9; + + } + return true; +} +*/ +/*bool CMonster::Scarab(CMonster* monster,CMap* map) +{ + return true; + fPoint position; + CMonster* othermon; + CMonster* newmonster; + if (monster->hitcount>5)monster->hitcount=0; + switch (monster->hitcount) + { + case 0: + position = GServer->RandInCircle( monster->Position->current, 1 ); + newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=1; + GServer->ClearClientID( monster->clientid ); + for(UINT i=0;iMonsterList.size();i++) + { + othermon = map->MonsterList.at(i); + if(othermon==monster) + { + map->MonsterList.erase( map->MonsterList.begin()+i ); + delete monster; + } + } + break; + case 1: + position = GServer->RandInCircle( monster->Position->current, 1 ); + newmonster=map->AddMonster( 210, position, 0, NULL, NULL, 0, true ); + newmonster->hitcount=2; + GServer->ClearClientID( monster->clientid ); + for(UINT i=0;iMonsterList.size();i++) + { + othermon = map->MonsterList.at(i); + if(othermon==monster) + { + map->MonsterList.erase( map->MonsterList.begin()+i ); + delete monster; + } + } + break; + case 2: + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GServer->GetNewClientID(); + thisdrop->type = 2; // ITEM + thisdrop->pos = GServer->RandInCircle( monster->Position->current, 3 ); + thisdrop->posMap = monster->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = rand()%3+1; + thisdrop->item.count = thisdrop->amount; + thisdrop->owner = 0; + thisdrop->item.itemtype=12; + thisdrop->item.itemnum=rand()%10+11; + map->AddDrop( thisdrop ); + GServer->ClearClientID( monster->clientid ); + for(UINT i=0;iMonsterList.size();i++) + { + othermon = map->MonsterList.at(i); + if(othermon==monster) + { + map->MonsterList.erase( map->MonsterList.begin()+i ); + delete monster; + } + } + break; + } + return true; +}*/ +bool CMonster::SummonUpdate(CMonster* monster, CMap* map, UINT j) +{ + BEGINPACKET( pak, 0x799 ); + clock_t etime = clock() - lastDegenTime; + CPlayer* ownerclient = monster->GetOwner( ); + if( etime >= 4 * CLOCKS_PER_SEC && Stats->HP > 0 ) + { + Stats->HP -= 4; + lastDegenTime = clock(); + if (Stats->HP < 1||ownerclient == NULL||ownerclient->IsDead())//orphan check + { + Log( MSG_INFO, "Deleting orphan summon %i (summonupdate)",monster->montype); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, monster->clientid ); + ADDDWORD ( pak, 0x8005 ); + GServer->SendToVisible( &pak, monster ); + map->DeleteMonster( monster, true, j ); + return true; + } + else + if (montype > 800 && montype < 811) + { + /* + CPlayer* ownerclient = monster->GetOwner( ); + float distance = 0x9; + for(UINT i=0;iMapList.Index[Position->Map]->PlayerList.size();i++) + { + CPlayer* thisclient = GServer->MapList.Index[Position->Map]->PlayerList.at(i); + float tempdist = GServer->distance( Position->current, thisclient->Position->current ); + if(tempdistStats->HP += ((((montype-800)*5)+((ownerclient->Stats->Level)/10))+10); + thisclient->Stats->MP += (((montype-800)*5)+((ownerclient->Stats->Level)/25)); + BEGINPACKET( pak, 0x7b2); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x0b9d ); + ADDBYTE ( pak, 0x06 ); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x7b5); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x0b9d+0x3000+((ownerclient->Stats->Level%4)*0x4000) ); + ADDBYTE ( pak, ownerclient->Stats->Level/4 ); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x0b9d ); + thisclient->client->SendPacket(&pak); + } + }*/ + } + // SET MONSTER HEALTH + //hopefully this will 100% control the health of summons from the server end + BEGINPACKET( pak1, 0x79f ); + ADDWORD( pak1, clientid ); + ADDWORD( pak1, Stats->HP ); + ADDWORD( pak1, 0 ); + //thisclient->client->SendPacket( &pak ); + GServer->SendToVisible( &pak1, monster ); + } + else + if (Stats->HP < 1) + { + Log( MSG_INFO, "Deleting dead summon %i (summonupdate)",monster->montype); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, monster->clientid ); + ADDDWORD ( pak, 0x8005 ); + GServer->SendToVisible( &pak, monster ); + map->DeleteMonster( monster, true, j ); + } + + return true; +} + +void CMonster::DoAi(int ainumber,char type)//ainumber is monster->AI type is add=0 idle=1 attacking=2 attacked=3 after killing target=4 hp<1=5 +{ + CAip* script = NULL; + int AIWatch = GServer->Config.AIWatch; + int aiindex = (ainumber*0x10000)+(type*0x100); + if(type == 5) + { + //Log(MSG_INFO,"Monster died. Activating AI type 5"); + } + for(unsigned j=0; j < GServer->AipList.size(); j++) + { + if (GServer->AipList.at(j)->AipID == aiindex) + { + script = GServer->AipList.at(j); + + //if(ainumber == AIWatch)Log(MSG_DEBUG, "Record count = %i",script->recordcount[type]); + if(ainumber == AIWatch)Log(MSG_DEBUG, "aiCondition type: %i Index: %i condition count %i", type, aiindex, script->ConditionCount); + int success = AI_SUCCESS; //needs to be AI_SUCCESS otherwise would not perform conditionless actions + int thisaction = 0; + for (dword i = 0; i < script->ConditionCount; i++) + { + int command = script->Conditions[i]->opcode; + if (command > 30 || command < 0) continue; + success = (*GServer->aiCondFunc[command])(GServer, this, script->Conditions[i]->data); + if(ainumber == AIWatch)Log(MSG_DEBUG, "aiCondition %03u returned %d", command, success); + if (success == AI_FAILURE) + break; + } + if (success == AI_SUCCESS) + { + for (dword i = 0; i < script->ActionCount; i++) + { + int command = script->Actions[i]->opcode; + if (command > 38 || command < 0) continue; + success = (*GServer->aiActFunc[command])(GServer, this, script->Actions[i]->data); + if(ainumber == AIWatch)Log(MSG_DEBUG, "aiAction: %03u returned %d", command, success); + } + if(success == AI_SUCCESS) + return; //automatically return after performing the first successful action + } + aiindex++; + } + else if(GServer->AipList.at(j)->AipID > aiindex)return; + } + return; +} diff --git a/Src/World Server/MonsterStats.cpp b/Src/World Server/MonsterStats.cpp new file mode 100644 index 0000000..7319620 --- /dev/null +++ b/Src/World Server/MonsterStats.cpp @@ -0,0 +1,195 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" + +// Get Mob Attack Power +unsigned int CMonster::GetAttackPower( ) +{ + unsigned int attack; + attack = thisnpc->atkpower; + if(thisnpc->weapon != 0) + attack += GServer->EquipList[8].Index[thisnpc->weapon]->attackpower; + if(Status->Attack_up!=0xff) + attack += MagicStatus[Status->Attack_up].Value; + if(Status->Attack_down!=0xff) + attack -= MagicStatus[Status->Attack_down].Value; + return attack; +} + +// Get Monster Defense +unsigned int CMonster::GetDefense( ) +{ + unsigned int defense; + defense = thisnpc->defense; + if(thisnpc->subweapon!=0) + defense += GServer->EquipList[SUBWEAPON].Index[thisnpc->subweapon]->defense; + if(Status->Defense_up!=0xff) + defense += MagicStatus[Status->Defense_up].Value; + if(Status->Defense_down!=0xff) + defense -= MagicStatus[Status->Defense_down].Value; + if(defense == 0)defense = 1; //hopefully stops some division by zero errors. + return defense; +} + +// Get Monster atk speed +unsigned int CMonster::GetAttackSpeed( ) +{ + unsigned int aspeed = (unsigned int)thisnpc->atkspeed; + if(aspeed == 0) aspeed = 1; // prevent a /0 when hunting dragon egg + if(Status->Haste_up!=0xff) + aspeed += MagicStatus[Status->Haste_up].Value; + if(Status->Haste_down!=0xff) + aspeed -= MagicStatus[Status->Haste_down].Value; + return aspeed; +} + +// Get Monster Movement speed +unsigned int CMonster::GetMoveSpeed( ) +{ + unsigned int mspeed = 0; + if (Stats->stance == 0) //mWalking. All other stances produce running animation at client + { + mspeed = thisnpc->wspeed; + } + else + { + mspeed = thisnpc->rspeed; + } + if(Status->Dash_up!=0xff) + mspeed += MagicStatus[Status->Dash_up].Value; + if(Status->Dash_down!=0xff) + mspeed -= MagicStatus[Status->Dash_down].Value; + return mspeed; +} + +// return dodge +unsigned int CMonster::GetDodge( ) +{ + UINT dodge = 0; + dodge = thisnpc->dodge; + if(dodge == 0) dodge = thisnpc->defense; // prevent a /0 for hawk skill + if(Status->Dodge_up!=0xff) + dodge += MagicStatus[Status->Dodge_up].Value; + if(Status->Dodge_down!=0xff) + dodge -= MagicStatus[Status->Dodge_down].Value; + if(dodge == 0)dodge = 1; //hopefully stops some division by zero errors. + return dodge; +} + +// return accury +unsigned int CMonster::GetAccury( ) +{ + UINT hitrate = 0; + hitrate = thisnpc->hitrate; + if(Status->Accury_up!=0xff) + hitrate += MagicStatus[Status->Accury_up].Value; + if(Status->Accury_down!=0xff) + hitrate -= MagicStatus[Status->Accury_down].Value; + return hitrate; +} + +// return magic defense +unsigned int CMonster::GetMagicDefense( ) +{ + UINT mdef = 0; + mdef = thisnpc->magicdefense; + if(Status->Magic_Defense_up!=0xff) + mdef += MagicStatus[Status->Magic_Defense_up].Value; + if(Status->Magic_Defense_down!=0xff) + mdef -= MagicStatus[Status->Magic_Defense_down].Value; + if(mdef == 0)mdef = 1; //hopefully stops some division by zero errors. + return mdef; +} + +// return magic attack +unsigned int CMonster::GetMagicAttack( ) +{ + return thisnpc->magicattack; +} + + +// return max hp +unsigned int CMonster::GetMaxHP( ) +{ + UINT MaxHP = 0; + MaxHP = thisnpc->hp; + if(owner == 0) + MaxHP *= thisnpc->level; + //if(MaxHP == 0) + //MaxHP = thisnpc->shp; // Bullshit. npc->shp is the flying height of a monster such as a bee. Dumb ass!! + return MaxHP; +} + +unsigned int CMonster::GetCritical( ) +{ + return 60; //60 = 20% of 300 our critical probability +} + +float CMonster::GetAttackDistance( ) +{ + return thisnpc->atkdistance; +} + +unsigned int CMonster::GetXPRate( ) +{ + return 0; +} + +unsigned int CMonster::GetItemDropRate( ) +{ + return 0; +} + +unsigned int CMonster::GetItemDropCountRate( ) +{ + return 0; +} + +unsigned int CMonster::GetZulyDropRate( ) +{ + return 0; +} + + +// set monster stats values +bool CMonster::SetStats( ) +{ + if(thisnpc == NULL) return false; + Stats->Level = thisnpc->level; + Stats->Attack_Power = GetAttackPower( ); + Stats->Defense = GetDefense( ); + Stats->xprate = GetXPRate( ); + Stats->itemdroprate = GetItemDropRate( ); + Stats->zulydroprate = GetZulyDropRate( ); + Stats->Attack_Speed = GetAttackSpeed( ); + Stats->Move_Speed = GetMoveSpeed( ); + Stats->Dodge = GetDodge( ); + Stats->Accury = GetAccury( ); + Stats->Critical = GetCritical( ); + Stats->Magic_Defense = GetMagicDefense( ); + Stats->magicattack = GetMagicAttack( ); + Stats->Attack_Distance = GetAttackDistance( ); + for(int i=0;i<20;i++) + { + AIVar[i] = 0; + } + return true; +} diff --git a/Src/World Server/Party.cpp b/Src/World Server/Party.cpp new file mode 100644 index 0000000..72818cc --- /dev/null +++ b/Src/World Server/Party.cpp @@ -0,0 +1,375 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Party Actions [invite/leave/kick] +bool CWorldServer::pakPartyActions( CPlayer* thisclient, CPacket* P ) +{ + unsigned int action = GETBYTE((*P),0); + CMap* map = MapList.Index[thisclient->Position->Map]; + if(map == NULL)return true; + switch(action) + { + case 0x00://Invita a new party + case 0x01://invite a existent party + { + UINT clientid = GETWORD((*P),1); + if(thisclient->Party->party!=NULL) + { + if(thisclient->Party->party->Members.size()>=thisclient->Party->party->Capacity) + { + SendSysMsg( thisclient, "Party is full" ); + return true; + } + } + CPlayer* otherclient = map->GetPlayerInMap( clientid );// have to be in same map + if(otherclient == NULL) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x00 );//No encontro el ID + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + if(otherclient->Party->party != NULL) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x01 );//No puede ser solicitado (ya tiene party) + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + if(abs(otherclient->Stats->Level-thisclient->Stats->Level)>(Config.Partygap+1)) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x07 );//Level inapropiado + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + BEGINPACKET( pak, 0x7d0 ); + ADDBYTE ( pak, action ); + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, 0x00 ); + otherclient->client->SendPacketCpy( &pak ); + } + break; + case 0x02://Leave Party + { + if(thisclient->Party->party == NULL) + return true; + CParty* party = thisclient->Party->party; + if(party == NULL) + return true; + + bool pflag = false; + if(!party->RemovePlayer( thisclient )) //if this player is not in this party + return true; + if(party->Members.size()>1) + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* thismember = party->Members.at(i); + if(thismember == NULL) + return true; + BEGINPACKET( pak, 0x7d2 ); + ADDWORD ( pak, 0xff00 ); + ADDDWORD ( pak, thisclient->CharInfo->charid ); + if(!pflag) + { + ADDDWORD( pak, thismember->CharInfo->charid ); + if(thisclient->Party->IsMaster) + thismember->Party->IsMaster = true; + pflag = true; + } + thismember->client->SendPacketCpy( &pak ); + } + } + else + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* thismember = party->Members.at(i); + if(thismember == NULL) + return true; + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x05 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + thismember->client->SendPacket( &pak ); + thismember->Party->party = NULL; + thismember->Party->IsMaster = true; + } + RemoveParty( party ); + delete party; + } + } + break; + case 0x03: // Give leader + { + if(thisclient->Party->party == NULL) + return true; + unsigned int charid = GETDWORD((*P),1); + if( !thisclient->Party->IsMaster || thisclient->CharInfo->charid == charid ) + return true; + CPlayer* otherclient = GetClientByCID( charid ); + if(otherclient == NULL) + return true; + BEGINPACKET(pak, 0x07d1) + ADDBYTE(pak, 8); + ADDWORD(pak, otherclient->clientid); + ADDWORD(pak, 0); + otherclient->Party->IsMaster = true; + thisclient->Party->IsMaster = false; + thisclient->Party->party->SendToMembers( &pak ); + } + break; + case 0x81: //Kick from party + { + unsigned int charid = GETDWORD((*P),1); + if(thisclient->Party->party==NULL) + return true; + if(!thisclient->Party->IsMaster || thisclient->CharInfo->charid==charid) + return true; + CParty* party = thisclient->Party->party; + if(party == NULL) + return true; + CPlayer* thismember = party->GetMemberByCharID( charid ); + if(thismember == NULL) + return true; + + BEGINPACKET( pak, 0x7d1 ); // Kicked from party message + ADDBYTE ( pak, 0x80 ); + ADDDWORD ( pak, thismember->CharInfo->charid ); + thismember->client->SendPacketCpy( &pak ); + RESETPACKET( pak, 0x7d2 ); + ADDWORD ( pak, 0xff00 ); + ADDDWORD ( pak, thismember->CharInfo->charid ); + bool pflag = false; + if(!party->RemovePlayer( thismember )) //if this player is not in this party + return true; + if(party->Members.size()>1) + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* othermember = party->Members.at(i); + if(othermember == NULL) + return true; + if(!pflag) + { + ADDDWORD( pak, othermember->CharInfo->charid ); + if(thismember->Party->IsMaster) + othermember->Party->IsMaster = true; + pflag = true; + } + othermember->client->SendPacketCpy( &pak ); + } + } + else + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* othermember = party->Members.at(i); + if(othermember == NULL) + return true; + RESETPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x05 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + othermember->client->SendPacketCpy( &pak ); + othermember->Party->party = NULL; + othermember->Party->IsMaster = true; + } + RemoveParty( party ); + delete party; + } + } + break; + default: + Log(MSG_WARNING,"Party unknown action: %i", action); + } + return true; +} + +// Party Manager +bool CWorldServer::pakPartyManager( CPlayer* thisclient, CPacket* P ) +{ + unsigned int action = GETBYTE((*P),0); + switch(action) + { + case 0x02://Acepto + { + if(thisclient->Party->party!=NULL)// have party + return true; + unsigned int clientid = GETWORD((*P),1); + CPlayer* otherclient = GetClientByID( clientid, thisclient->Position->Map ); + + if(otherclient == NULL) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x00 );//No encontro el ID + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + CParty* party = otherclient->Party->party; + if(party != NULL) + { + if(party->Members.size()>=party->Capacity) + { + SendSysMsg( thisclient, "Party is Full" ); + return true; + } + } + if(abs(otherclient->Stats->Level-thisclient->Stats->Level)>(Config.Partygap+1)) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x07 );//Level inapropiado + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x02 );//Acepto Party + ADDWORD ( pak, otherclient->clientid ); + ADDBYTE ( pak, 0x00 ); + otherclient->client->SendPacketCpy( &pak ); + if( party == NULL ) + { // new party + CParty* thisparty = new CParty; + thisparty->counted = false; + thisparty->Pending_Exp = 0; + thisparty->AddPlayer( otherclient ); + AddParty( thisparty ); + otherclient->Party->IsMaster = true; + party = thisparty; + } + //Send Party Level and Party Exp + RESETPACKET( pak, 0x7d4 ); // + ADDBYTE ( pak, party->PartyLevel ); + ADDDWORD ( pak, party->Exp ); + thisclient->client->SendPacketCpy( &pak ); + thisclient->Party->IsMaster = false; + // Send New Party Member info to other players + RESETPACKET( pak, 0x7d2 ); + ADDBYTE ( pak, party->Option ); + ADDBYTE ( pak, 0x01 ); + ADDDWORD ( pak, thisclient->CharInfo->charid ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisclient->Stats->MaxHP ); + ADDWORD ( pak, thisclient->Stats->HP ); + ADDDWORD ( pak, 0x00000000 ); + ADDDWORD ( pak, 0x0000000f ); + ADDWORD ( pak, 0x1388 ); + ADDSTRING ( pak, thisclient->CharInfo->charname ); + ADDBYTE ( pak, 0x00 ); + party->SendToMembers( &pak ); + + // Send To New Party Member the members List + RESETPACKET( pak, 0x7d2 ); + ADDBYTE ( pak, party->Option ); + ADDBYTE ( pak, party->Members.size() ); + for(int i=0;iMembers.size();i++) + { + CPlayer* member= party->Members.at(i); + if(member == NULL) + continue; + ADDDWORD ( pak, member->CharInfo->charid ); + ADDWORD ( pak, member->clientid ); + ADDWORD ( pak, member->Stats->MaxHP ); + ADDWORD ( pak, member->Stats->HP ); + ADDDWORD ( pak, 0x00000000 ); + ADDDWORD ( pak, 0x0000000f ); + ADDWORD ( pak, 0x1388 ); + ADDSTRING ( pak, member->CharInfo->charname ); + ADDBYTE ( pak, 0x00 ); + } + thisclient->client->SendPacketCpy( &pak ); + party->AddPlayer( thisclient ); + } + break; + case 0x04://No acepto + { + unsigned int clientid = GETWORD((*P),1); + CPlayer* otherclient = GetClientByID( clientid, thisclient->Position->Map ); + if(otherclient == NULL) + { + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x00 );//No encontro el ID + ADDWORD ( pak, clientid ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacketCpy( &pak ); + return true; + } + BEGINPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x04 );//No acepto + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, 0x00 ); + otherclient->client->SendPacketCpy( &pak ); + } + break; + default: + Log(MSG_WARNING,"Party Manager unknown action: %i", action); + } + return true; +} + +// Party Chat +bool CWorldServer::pakPartyChat( CPlayer* thisclient, CPacket* P ) +{ + CParty* party = thisclient->Party->party; + if(party == NULL) + return true; + BEGINPACKET( pak, 0x786 ); + ADDWORD ( pak, thisclient->clientid ); + ADDSTRING ( pak, P->Buffer ); + ADDBYTE ( pak, 0 ); + party->SendToMembers( &pak ); + return true; +} + +// Change Party Options +bool CWorldServer::pakPartyOption( CPlayer* thisclient, CPacket* P ) +{ + if(!thisclient->Party->IsMaster) + return true; + CParty* party = thisclient->Party->party; + if(party == NULL) + return true; + party->Option = GETBYTE((*P),0); + BEGINPACKET( pak, 0x7d7 ); + ADDBYTE ( pak, party->Option ); + ADDBYTE ( pak, 0x00 ); + party->SendToMembers( &pak ); + return true; +} + +unsigned int CWorldServer::GetMaxPartyExp( unsigned int partylevel ) +{ + unsigned int exp = 540; + exp += 100 * partylevel + ( partylevel-2*10 ); + return exp; +} diff --git a/Src/World Server/QuestActions.cpp b/Src/World Server/QuestActions.cpp new file mode 100644 index 0000000..4e4a380 --- /dev/null +++ b/Src/World Server/QuestActions.cpp @@ -0,0 +1,296 @@ +#include "main.h" + +dword GetRewardValue(dword function, dword amount, CWorldClient* client, word nDupCNT){ + switch(function){ + case 0: + { + dword tempVal = amount + 0x1E; + tempVal *= client->GetStat(sCharm) + 0x0a; + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->stats_base[sLevel] + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 1: + { + dword tempVal = (client->stats_base[sLevel] + 3) * amount; + tempVal *= (client->GetStat(sCharm) >> 1) + client->stats_base[sLevel] + 0x28; + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x2710; + } + break; + case 2: + return (amount * nDupCNT); + break; + case 3: + case 5: + { + dword tempVal = amount + 0x14; + tempVal *= client->GetStat(sCharm) + 0x0a; + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->stats_base[sLevel] + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 4: + { + dword tempVal = amount + 0x2; + tempVal *= (client->GetStat(sCharm) + client->stats_base[sLevel] + 0x28); + tempVal *= 0x28;//Fame + 0x28 + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x222E0; + } + break; + case 6: + { + dword tempVal = amount + 0x14; + tempVal *= client->GetStat(sCharm) + client->stats_base[sLevel]; + tempVal *= 0x14;//Fame + 0x14 + tempVal *= (100 & 0xFFFF);//World Rate + return (tempVal / 0x2DC6C0) + amount; + } + break; + } + return amount; +} + +//Update Quest +QUESTREWD(000){ + GETREWDDATA(000); + + switch(data->btOp){//0 remove, 1 start, 4 select + case 0: + { + for(dword i = 0; i < 10; i++){ + if(client->quest.quests[i].QuestID != data->iQuestSN) continue; + + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + break; + } + } + break; + case 1: + { + for(dword i = 0; i < 10; i++){ + if(client->quest.quests[i].QuestID == 0) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].Time = clock_t(); + break; + } + } + break; + case 4: + { + client->ActiveQuest = data->iQuestSN; + } + break; + } + return QUEST_SUCCESS; +} + +//Update Quest Items +QUESTREWD(001){ + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(002){ + return QUEST_SUCCESS; +} + +//Update Stats +QUESTREWD(003){ + GETREWDDATA(003); + for(dword i = 0; i < data->iDataCnt; i++){ + qword address = i * 0x0C; + address += (qword)data; + address += 4; + STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if(!OperateValues(curAbil->btOp, &client->stats_base[curAbil->iType], curAbil->iValue)) + return QUEST_FAILURE; + } + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(004){ + return QUEST_SUCCESS; +} + +//Give Reward +QUESTREWD(005){ + GETREWDDATA(005); + + switch(data->btTarget){ + case 0://EXP + { + client->basic.exp += GetRewardValue(data->btEquation, data->iValue, client, 0); + CPacket pakout(0x79b); + pakout.Add(client->basic.exp); + pakout.Add(client->stats_base[sStamina]); + pakout.Add(0); + server->SendPacket(client, &pakout); + } + break; + case 1://Zuly + client->inv.money += GetRewardValue(data->btEquation, data->iValue, client, 1);//dunno nDupCount for this one! + CPacket pakout(0x71D); + pakout.Add(client->inv.money); + server->SendPacket(client, &pakout); + break; + case 2://Item + { + CItem nItem; + nItem.type = data->iItemSN / 1000; + nItem.id = data->iItemSN - (nItem.type * 1000); + if(nItem.IsStackable()){ + nItem.count = GetRewardValue(data->btEquation, data->iValue, client, 0); + }else{ + nItem.itemdata = 0; + nItem.durability = 40; + nItem.lifespan = 1000; + nItem.appraised = 1; + } + client->AddItem(&nItem, true); + } + break; + } + + return QUEST_SUCCESS; +} + +//Restore HP/MP +QUESTREWD(006){ + return QUEST_SUCCESS; +} + +//Teleport +QUESTREWD(007){ + GETREWDDATA(007); + client->MoveMap(data->iZoneSN, (float)data->iX, (float)data->iY); + return QUEST_SUCCESS; +} + +//Spawn Monster +QUESTREWD(008){ + return QUEST_SUCCESS; +} + +//Execute Quest Trigger +QUESTREWD(009){ + return QUEST_SUCCESS; +} + +//Reset Stats +QUESTREWD(010){ + client->stats_base[sStatPoints] = 0; + + client->stats_base[sStrength] = 15; + client->stats_base[sDexterity] = 15; + client->stats_base[sIntelligence] = 15; + client->stats_base[sConcentration] = 15; + client->stats_base[sCharm] = 10; + client->stats_base[sSensibility] = 10; + + for(int i = 0; i <= client->stats_base[sLevel]; i++) + client->stats_base[sStatPoints] += (i + 9); + + return QUEST_SUCCESS; +} + +//Update Quest Var +QUESTREWD(011){ + return QUEST_SUCCESS; +} + +//NPC Speak +QUESTREWD(012){ + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(013){ + return QUEST_SUCCESS; +} + +//Learn Skill +QUESTREWD(014){ + + return QUEST_SUCCESS; +} + +//Set Quest Flag +QUESTREWD(015){ + GETREWDDATA(015); + client->quest.SetFlag(data->nSN, (data->btOp == 1)?true:false); + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(016){ + return QUEST_SUCCESS; +} + +//Reset All Quest Flags +QUESTREWD(017){ + memset(&client->quest.flags, 0, 244); + return QUEST_SUCCESS; +} + +//Send Announcement +QUESTREWD(018){ + return QUEST_SUCCESS; +} + +//Execute Quest Trigger in Other Map +QUESTREWD(019){ + return QUEST_SUCCESS; +} + +//PvP Status +QUESTREWD(020){ + return QUEST_SUCCESS; +} + +//Set Respawn Position +QUESTREWD(021){ + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(022){ + return QUEST_SUCCESS; +} + +//Raise Clan Grade +QUESTREWD(023){ + return QUEST_SUCCESS; +} + +//Clan Money +QUESTREWD(024){ + return QUEST_SUCCESS; +} + +//Clan Points +QUESTREWD(025){ + return QUEST_SUCCESS; +} + +//Clan Skill +QUESTREWD(026){ + return QUEST_SUCCESS; +} + +//Clan Contribution +QUESTREWD(027){ + return QUEST_SUCCESS; +} + +//Clan Teleportation +QUESTREWD(028){ + return QUEST_SUCCESS; +} \ No newline at end of file diff --git a/Src/World Server/QuestConditions.cpp b/Src/World Server/QuestConditions.cpp new file mode 100644 index 0000000..0e1e2d9 --- /dev/null +++ b/Src/World Server/QuestConditions.cpp @@ -0,0 +1,229 @@ +#include "main.h" + +int OperateValues(byte btOp, int* value1PTR, int value2){ + int value1 = *value1PTR; + switch(btOp){ + case 0: + return (value1 == value2); + case 1: + return (value1 <= value2); + case 2: + return (value1 >= value2); + case 3: + return (value1 > value2); + case 4: + return (value1 < value2); + case 5: + (*value1PTR) = value2; + return 1; + case 6: + (*value1PTR) = value1 + value2; + return 1; + case 7: + (*value1PTR) = value1 - value2; + return 1; + default: + return 0; + } +} + +//Check Quest +QUESTCOND(000){ + GETCONDDATA(000); + for(dword i = 0; i < 10; i++){ + if(client->quest.quests[i].QuestID == data->iQuestSN) + return QUEST_SUCCESS; + } + return QUEST_FAILURE; +} + +//Check Quest Variable +QUESTCOND(001){ + return QUEST_SUCCESS; +} + +//Check Quest Variable +QUESTCOND(002){ + return QUEST_SUCCESS; +} + +//Check Stats +QUESTCOND(003){ + GETCONDDATA(003); + for(int i = 0; i < data->iDataCnt; i++){ + qword address = i * 0x0C; + address += (qword)data; + address += 4; + STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if(!OperateValues(curAbil->btOp, &client->stats_base[curAbil->iType], curAbil->iValue)) + return QUEST_FAILURE; + } + return QUEST_SUCCESS; +} + +//Check Items +QUESTCOND(004){ + GETCONDDATA(004); + for(int i = 0; i < data->iDataCnt; i++){ + //QWord for 64bit safe pointers. + qword address = i * 0x10; + address += (qword)data; + address += 4; + STR_ITEM_DATA* curItem = (STR_ITEM_DATA*)address; + if(curItem->uiItemSN == 0 && curItem->iWhere != 0){ + int itemHead = client->inv.items[curItem->iWhere].itemhead; + if(!OperateValues(curItem->btOp, &itemHead, curItem->uiItemSN)) + return QUEST_FAILURE; + } + Log(MSG_INFO,"uiItemSN: %u iWhere: %u iRequestCnt: %u btOp: %u", curItem->uiItemSN, curItem->iWhere, curItem->iRequestCnt, curItem->btOp); + } + return QUEST_SUCCESS; +} + +//Check Party +QUESTCOND(005){ + return QUEST_SUCCESS; +} + +//Near Point +QUESTCOND(006){ + GETCONDDATA(006); + client->UpdatePosition(); + if(data->iZoneSN != 0){ + if(client->basic.map != data->iZoneSN) + return QUEST_FAILURE; + } + + float dx = client->basic.pos.x - (float)data->iX; + float dy = client->basic.pos.y - (float)data->iY; + float distance = sqrt((dx*dx) + (dy*dy)); + return (distance < data->iRadius)?QUEST_SUCCESS:QUEST_FAILURE; +} + +//World Time +QUESTCOND(007){ + return QUEST_SUCCESS; +} + +//Quest Time +QUESTCOND(008){ + return QUEST_SUCCESS; +} + +//Check Skill +QUESTCOND(009){ + GETCONDDATA(009); + int checkVal = (data->btOp)?QUEST_SUCCESS:QUEST_FAILURE; + for(dword i = 0; i < 90; i++){ + if(client->skill.Skills[i] == data->iSkillSN1) + return (data->btOp)?QUEST_SUCCESS:QUEST_FAILURE; + } + return (data->btOp)?QUEST_FAILURE:QUEST_SUCCESS; +} + +//Unknown +QUESTCOND(010){ + return QUEST_SUCCESS; +} + +//Quest Variable +QUESTCOND(011){ + return QUEST_SUCCESS; +} + +//Execute Trigger in Zone +QUESTCOND(012){ + return QUEST_SUCCESS; +} + +//Near NPC +QUESTCOND(013){ + return QUEST_SUCCESS; +} + +//Check Quest Flag +QUESTCOND(014){ + GETCONDDATA(014); + bool dataOp = (data->btOp)?true:false; + return (client->quest.GetFlag(data->nSN) == dataOp)?QUEST_SUCCESS:QUEST_FAILURE; +} + +//Unknown +QUESTCOND(015){ + return QUEST_SUCCESS; +} + +//Zone Time +QUESTCOND(016){ + return QUEST_SUCCESS; +} + +//NPC Variables +QUESTCOND(017){ + return QUEST_SUCCESS; +} + +//Time on Date +QUESTCOND(018){ + return QUEST_SUCCESS; +} + +//Time on Day +QUESTCOND(019){ + return QUEST_SUCCESS; +} + +//Unknown +QUESTCOND(020){ + return QUEST_SUCCESS; +} + +//Unknown +QUESTCOND(021){ + return QUEST_SUCCESS; +} + +//Check Server/Channel +QUESTCOND(022){ + return QUEST_SUCCESS; +} + +//In Clan +QUESTCOND(023){ + return QUEST_SUCCESS; +} + +//Clan Position +QUESTCOND(024){ + return QUEST_SUCCESS; +} + +//Clan Contribution +QUESTCOND(025){ + return QUEST_SUCCESS; +} + +//Clan Grade +QUESTCOND(026){ + return QUEST_SUCCESS; +} + +//Clan Points +QUESTCOND(027){ + return QUEST_SUCCESS; +} + +//Clan Funds +QUESTCOND(028){ + return QUEST_SUCCESS; +} + +//Clan Members +QUESTCOND(029){ + return QUEST_SUCCESS; +} + +//Clan Skills +QUESTCOND(030){ + return QUEST_SUCCESS; +} \ No newline at end of file diff --git a/Src/World Server/QuestConditions.h b/Src/World Server/QuestConditions.h new file mode 100644 index 0000000..04a7b81 --- /dev/null +++ b/Src/World Server/QuestConditions.h @@ -0,0 +1,200 @@ +#define QUESTCOND(cond) int QUEST_COND_ ## cond ## (class CWorldServer* server, class CPlayer* client, byte* raw) +typedef int (*fpQuestCond)(class CWorldServer*, class CPlayer*, byte*); +#define GETCONDDATA(cond) STR_COND_ ## cond ## * data = (STR_COND_ ## cond ## *)raw; + +QUESTCOND(000);QUESTCOND(001);QUESTCOND(002); +QUESTCOND(003);QUESTCOND(004);QUESTCOND(005); +QUESTCOND(006);QUESTCOND(007);QUESTCOND(008); +QUESTCOND(009);QUESTCOND(010);QUESTCOND(011); +QUESTCOND(012);QUESTCOND(013);QUESTCOND(014); +QUESTCOND(015);QUESTCOND(016);QUESTCOND(017); +QUESTCOND(018);QUESTCOND(019);QUESTCOND(020); +QUESTCOND(021);QUESTCOND(022);QUESTCOND(023); +QUESTCOND(024);QUESTCOND(025);QUESTCOND(026); +QUESTCOND(027);QUESTCOND(028);QUESTCOND(029); +QUESTCOND(030); + +struct STR_COND_000 { + int iQuestSN; +}; + +struct STR_COND_001 { + int iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_COND_002 { + int iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_COND_003 { + int iDataCnt; + STR_ABIL_DATA* CheckData; +}; + +struct STR_COND_004 { + int iDataCnt; + STR_ITEM_DATA* CheckData; +}; + +struct STR_COND_005 { + byte btIsLeader; + int iLevel; + byte btReversed; +}; + +struct STR_COND_006 { + int iZoneSN; + int iX; + int iY; + int iZ; + int iRadius; +}; + +struct STR_COND_007 { + dword ulTime; + dword ulEndTime; +}; + +struct STR_COND_008 { + dword ulTime; + byte btOp; +}; + +struct STR_COND_009 { + int iSkillSN1; + int iSkillSN2; + byte btOp; +}; + +struct STR_COND_010 { + byte btLowPcnt; + byte btHighPcnt; +}; + +struct STR_COND_011 { + union { + byte btWho; + word usWho; + }; + word nVarNo; + int iValue; + byte btOp; +}; + +struct STR_COND_012 { + union { + word iZone; + dword ulZone; + }; + int iX; + int iY; + union { + int iEventID; + dword m_HashEventOBJ; + }; +}; + +struct STR_COND_013 { + int iNpcNo; +}; + +struct STR_COND_014 { + word nSN; + byte btOp; +}; + +struct STR_COND_015 { + word nNumber1; + word nNumber2; +}; + +struct STR_COND_016 { + union { + byte btWho; + dword ulWho; + }; + dword ulTime; + dword ulEndTime; +}; + +struct STR_COND_017 { + STR_NPCVAR NpcVar1; + STR_NPCVAR NpcVar2; + byte btOp; +}; + +struct STR_COND_018 { + byte btDate; + byte btHour1; + byte btMin1; + byte btHour2; + byte btMin2; +}; + +struct STR_COND_019 { + byte btWeekDay; + byte btHour1; + byte btMin1; + byte btHour2; + byte btMin2; +}; + +struct STR_COND_020 { + int iNo1; + int iNo2; +}; + +struct STR_COND_021 { + union { + byte btSelObjType; + dword ulSelObjType; + }; + int iRadius; +}; + +struct STR_COND_022 { + word nX; + word nY; +}; + +struct STR_COND_023 { + byte btReg; +}; + +struct STR_COND_024 { + word nPOS; + byte btOP; +}; + +struct STR_COND_025 { + word nCONT; + byte btOP; +}; + +struct STR_COND_026 { + word nGRD; + byte btOP; +}; + +struct STR_COND_027 { + word nPOINT; + byte btOP; +}; + +struct STR_COND_028 { + word iMONEY; + byte btOP; +}; + +struct STR_COND_029 { + word nMemberCNT; + byte btOP; +}; + +struct STR_COND_030 { + word nSkill1; + word nSkill2; + byte btOP; +}; diff --git a/Src/World Server/QuestTrigger.h b/Src/World Server/QuestTrigger.h new file mode 100644 index 0000000..82170e9 --- /dev/null +++ b/Src/World Server/QuestTrigger.h @@ -0,0 +1,61 @@ +#pragma once +#include "worldserver.h" +class CQuestTrigger { +public: + struct SQuestDatum { + int size; + int opcode; + byte* data; + }; + + string TriggerName; + dword TriggerHash; + + byte CheckNext; + + dword ConditionCount; + SQuestDatum** Conditions; + + dword ActionCount; + SQuestDatum** Actions; +private: +}; + +struct STR_NPCVAR { + dword iNpcNo; + word nVarNo; + word unused; +}; + +struct STR_ITEM_DATA { + dword uiItemSN; + dword iWhere; + dword iRequestCnt; + byte btOp; +}; + +struct STR_QUEST_DATA { + union { + dword iType; + struct { + word m_wVarNO; + word m_wVarTYPE; + }; + }; + word nValue; + byte btOp; +}; + +struct STR_ABIL_DATA { + dword iType; + int iValue; + byte btOp; +}; + +#define QUEST_FAILURE 0 +#define QUEST_SUCCESS 1 + +int OperateValues(byte btOp, int* value1PTR, int value2); + +#include "QuestConditions.h" +#include "quest/QuestActions.h" diff --git a/Src/World Server/ServerFunctions.cpp b/Src/World Server/ServerFunctions.cpp new file mode 100644 index 0000000..f00fd38 --- /dev/null +++ b/Src/World Server/ServerFunctions.cpp @@ -0,0 +1,898 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// from Paul_T +bool CWorldServer::SendPM( CPlayer* thisclient, const char* Format, ... ) +{ + char buf[512]; + va_list ap; + va_start( ap, Format ); + vsprintf( buf, Format, ap ); + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, "Server" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, buf ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket(&pak); + va_end( ap ); + return true; +} + +// from Paul_T +bool CWorldServer::SendGlobalMSG( CPlayer* thisclient, const char* Format, ... ) +{ + char buf[512]; + va_list ap; + va_start( ap, Format ); + vsprintf( buf, Format, ap ); + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, thisclient->CharInfo->charname ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, buf ); + ADDBYTE( pak, 0 ); + SendToAll(&pak); + va_end( ap ); + return true; +} + +// NPC Announce to the server +bool CWorldServer::NPCAnnounce( const char msg[200], const char npc[50]) +{ + BEGINPACKET( pak, 0x702 ); + ADDSTRING( pak, npc ); + ADDSTRING( pak, "> " ); + ADDSTRING( pak, msg); + ADDBYTE( pak, 0x00); + SendToAll( &pak ); + return true; +} + +// NPC Whisper to a character +bool CWorldServer::NPCWhisper( CPlayer* thisclient, CMonster* thismon, const char msg[200], const char npc[50] ) +{ + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, npc ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, msg ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket(&pak); + return true; +} + +// NPC Shout to the current map +bool CWorldServer::NPCShout( int map, const char msg[200], const char npc[50] ) +{ + BEGINPACKET(pak, 0x0785); + ADDSTRING ( pak, npc ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, msg ); + ADDBYTE ( pak, 0 ); + SendToMap ( &pak, map ); + return true; +} + +bool CWorldServer::DoSkillScript( CCharacter* character, CSkills* thisskill ) +{ + try + { + if(thisskill == 0) + return false; + if(thisskill->script == 0) + return false; + if(thisskill->svalue1 == 0) + return false; + fPoint position = RandInCircle( character->Position->current, 1 ); + CMap* map = MapList.Index[character->Position->Map]; + map->AddMonster( thisskill->svalue1, position, character->clientid,0,1 ); + return true; + } + catch (...) + { + Log(MSG_WARNING, "Error in CWorldServer::DoSkillScript( CCharacter* character, CSkills* thisskill )(serverFunctions.cpp)" ); + return false; + } +} + +// Build Drop. New experimental method. +CDrop* CWorldServer::GetNewDrop( CMonster* thismon ) +{ + //Log(MSG_DEBUG,"Monster %i killed. FInding a drop",thismon->montype); + CPlayer* thisclient = GServer->GetClientByCID(thismon->MonsterDrop->firsthit); + if(thisclient == NULL)return NULL; + if( thisclient->Session->codedebug ) + SendPM(thisclient, "Drops function called after killing monster type %i",thismon->montype); + + int randv; + int i; + CDrop* newdrop = new (nothrow) CDrop; + + if(newdrop == NULL) + { + Log(MSG_WARNING, "newDrop is NULL [getdrop]" ); + return NULL; + } + CMDrops* thisdrop = NULL; + CMDrops* testdrop = NULL; + + newdrop->clientid = GetNewClientID( ); + newdrop->posMap = thismon->Position->Map; + newdrop->pos = RandInCircle( thismon->Position->current, 3 ); + //newdrop->droptime = time(NULL); + newdrop->owner = thismon->MonsterDrop->firsthit; + newdrop->thisparty = thismon->thisparty; + ClearItem(newdrop->item); + + + + int droprate = 80; // base drop percentage for monsters your own level + int DropFloor = Config.BASE_DROP_CHANCE; + int leveldif = (int)((float)thismon->thisnpc->level - (float)thisclient->Stats->Level); + float mod = droprate * 0.01 * leveldif; + int dropchance = int(droprate + mod); + if(dropchance < DropFloor) dropchance = DropFloor; //always a small chance of a drop even when the mob is more than 20 levels beneath your own + if(thismon->thisnpc->level == 1) + dropchance = 80; + //if( thisclient->Session->codedebug ) + // SendPM(thisclient, "Drop chance (based on level diference) = %i", dropchance ); + int droprand = GServer->RandNumber(0,100); + if (droprand > dropchance) + { + if( thisclient->Session->codedebug ) + SendPM(thisclient, "Drop rate check failed with %i. No drop this time",droprand); + return NULL; // no drop here. not this time anyway. + } + + + // Each monster has its own rates for zuly and item drops defined in the stb + // first check for Zuly drops + randv = RandNumber( 1, 100); + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "randv = %i. money = %i. zulyrate = %i. ", randv,thismon->thisnpc->money,thisclient->Stats->zulydroprate ); + //} + if(randv <= (thismon->thisnpc->money + thisclient->Stats->zulydroprate)) // zuly drop + { + newdrop->type = 1; //zuly + newdrop->amount = thismon->thisnpc->level * 5 * (Config.ZULY_RATE + thisclient->Stats->zulydroprate) + RandNumber( 1, 10 ); + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Zuly drop selected " ); + } + return newdrop; + } + //now check for item drops + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "randv = %i. Item = %i. Itemrate = %i", randv,thismon->thisnpc->item,thisclient->Stats->itemdroprate ); + //} + if(randv <= (thismon->thisnpc->item + thisclient->Stats->itemdroprate + thismon->thisnpc->money)) // Item drop + { + //OK got an item. Now we find which one + //Map drop or mob drop? map = 50% mob = 50%. Maybe should be configurable or modified by monster data? + int randv2 = RandNumber( 1, 100); + if(randv2 <= 50) //map drop + { + for(i=0;iMDropList.size();i++) + { + testdrop = GServer->MDropList.at(i); + if(testdrop == NULL) return NULL; + if(testdrop->Dropid == thismon->Position->Map) + { + thisdrop = testdrop; + } + } + + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Map drop selected for map %i. ", thismon->Position->Map ); + } + } + else // mob drop + { + for(i=0;iMDropList.size();i++) + { + testdrop = GServer->MDropList.at(i); + if(testdrop == NULL) return NULL; + if(testdrop->Dropid == thismon->thisnpc->dropid) + { + thisdrop = testdrop; + } + } + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Mob drop selected for dropid %i. ", thismon->thisnpc->dropid ); + } + } + if(thisdrop == NULL) + { + Log(MSG_WARNING, "Thisdrop is NULL [getdrop]" ); + return NULL; + } + newdrop->type = 2; //Item + dword randitem = RandNumber( 1, thisdrop->TotalChance ); + UINT itemindex = 0; + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "random chance %i", randitem); + //} + + i=0; + do + { + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "Index: %i Item id: %i chance: %i", i, thisdrop->item[i], thisdrop->RunningTotal[i]); + //} + i++; + } + while (randitem > thisdrop->RunningTotal[i]); + itemindex = i; + + if(thisdrop->item[itemindex] == 0) + { + Log(MSG_DEBUG, "item at itemindex %i does not exist",itemindex ); + return NULL; //no item listed for this dropid + } + //ok we found an item. Now we process it. + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "Found an item to drop itemindex: %i id: %i. ", itemindex, thisdrop->item[itemindex] ); + //} + if(thisdrop->item[itemindex] < 100000) + { + newdrop->item.itemtype = thisdrop->item[itemindex] / 1000; + newdrop->item.itemnum = thisdrop->item[itemindex] % 1000; + } + else + { + newdrop->item.itemtype = thisdrop->item[itemindex] / 1000000; + newdrop->item.itemnum = thisdrop->item[itemindex] % 1000000; + } + //if( thisclient->Session->codedebug ) + //{ + // SendPM(thisclient, "Item type = %i. item number = %i", newdrop->item.itemtype,newdrop->item.itemnum ); + //} + newdrop->item.lifespan = 10 + rand()%80; + switch( newdrop->item.itemtype ) + { + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: + newdrop->item.type = GServer->EquipList[newdrop->item.itemtype].Index[newdrop->item.itemnum]->type; + newdrop->item.raretype = GServer->EquipList[newdrop->item.itemtype].Index[newdrop->item.itemnum]->raretype; + break; + case 10: + newdrop->item.type = GServer->UseList.Index[newdrop->item.itemnum]->type; + newdrop->item.raretype = 0; + break; + case 11: + newdrop->item.type = GServer->JemList.Index[newdrop->item.itemnum]->type; + newdrop->item.raretype = 0; + break; + case 12: + newdrop->item.type = GServer->NaturalList.Index[newdrop->item.itemnum]->type; + newdrop->item.raretype = 0; + break; + case 14: + newdrop->item.type = GServer->PatList.Index[newdrop->item.itemnum]->type; + newdrop->item.raretype = 0; + break; + default: + Log(MSG_DEBUG, "itemtype %i does not exist",newdrop->item.itemtype ); + return NULL; + break; + } + float dmod = 0; //random number from 0 to 100 made up of 5 sub numbers to keep + //the average value near to 50 + for(i=0; i<5; i++) + { + float r1 = rand()%20; + dmod += r1; + } + newdrop->item.durability = 10 + (int)dmod; + newdrop->item.appraised = true; + newdrop->item.stats = 0; + newdrop->item.count = 1; + + if( newdrop->item.itemtype == 8 || newdrop->item.itemtype == 9 ) + { + //This probability is now configurable + int psocked = rand()%101; //Probability of finding a socketed item + if( psocked < Config.SlotChance) //default should be around 5% needs to be rare + { + newdrop->item.socketed = true; + } + else + { + newdrop->item.socketed = false; + } + } + else + { + newdrop->item.socketed = false; + } + + float TmpMod = thisclient->Attr->Cha / 30; + int chamod = (int)floor(TmpMod); + if(chamod < 0) chamod = 0; + int basedrop = thisclient->Stats->itemdroprate; //Base number of items to be dropped. + int maxdrop = basedrop + Config.DropMultiple; + if(newdrop->item.itemtype == 11) //gems only get 1 + newdrop->item.count = 1; + if( newdrop->item.itemtype == 10 || newdrop->item.itemtype == 12 ) + { + newdrop->item.count = RandNumber(basedrop, maxdrop); + if(thismon->thisnpc->level == 1) + newdrop->item.count = 1; //limit the drop rate of items from level 1 event mobs + if(newdrop->item.count == 0) + newdrop->item.count = 1; + if(newdrop->item.type == 314) //skillbooks + newdrop->item.count = 1; + if(newdrop->item.itemtype == 12) + { + newdrop->item.count = RandNumber(1, 6) + RandNumber(0, thisclient->Stats->itemdropcountrate); //basic type 12 items + //some item types have their own rules + if(newdrop->item.type == 428) //special skill items such as mana jams and scrolls + newdrop->item.count = RandNumber(1, 3); + if(newdrop->item.itemnum > 300 && newdrop->item.itemnum < 360) //bullets get a lot higher count. + { + newdrop->item.count *= 10; + newdrop->item.count += 10; + } + } + } + else if( newdrop->item.itemtype >1 && newdrop->item.itemtype !=7 && newdrop->item.itemtype < 10) + { + // check to see if the item will be refined + int prefine = rand()%100; //Probability of finding a refined item + int refinechance = Config.RefineChance; + if(prefine < refinechance) // default = 5% + { + int refinelevel = rand()%101; //which level of refine do we actually get + if( refinelevel < 5) //default 5% + newdrop->item.refine = 4 * 16; + else if( refinelevel < 15 ) //10% + newdrop->item.refine = 3 * 16; + else if(refinelevel < 35 ) // 20% + newdrop->item.refine = 2 * 16; + else // 65% + newdrop->item.refine = 16; + } + else //95% + newdrop->item.refine = 0; + + int StatChance = Config.StatChance; + if(newdrop->item.raretype > 0) // it's a blue item + { + newdrop->item.durability += 10; //make blues a little more durable + newdrop->item.appraised = false; //blues need to be appraised + newdrop->item.stats = RandNumber(0, 29) * 10 + 1; // stats can only be 1,11,21,,,,,291 + } + else // not a blue item + { + int pstats = RandNumber(1, 100); + if( pstats < StatChance) // default 5% + newdrop->item.stats = RandNumber(0, 29) * 10 + 1; // stats can only be 1,11,21,,,,,291 + } + } + newdrop->item.gem = 0; + if(newdrop->item.count == 0) + newdrop->item.count = 1; //just in case this is causing bugged items. + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Dropped item type: %i ID: %i count %i", newdrop->item.itemtype, newdrop->item.itemnum,newdrop->item.count); + } + return newdrop; + + } + return NULL; + // did not qualify to drop anything this time +} + +// Build Drop the PY way +CDrop* CWorldServer::GetPYDrop( CMonster* thismon, UINT droptype ) +{ //if droptype = 1 then it is a normal drop. if it is 2 then it is a potential side drop. + //type 2 removed a it is now handled by AIP + //Log( MSG_INFO, "PYDrops function selected" ); + //Log( MSG_INFO, "monster is %i", thismon->montype ); + CDrop* newdrop = new (nothrow) CDrop; + /* + if(newdrop == NULL) + { + Log(MSG_WARNING, "Error allocing memory [getdrop]" ); + return NULL; + } + newdrop->clientid = GetNewClientID( ); + newdrop->posMap = thismon->Position->Map; + newdrop->pos = RandInCircle( thismon->Position->current, 3 ); + //newdrop->droptime = time(NULL); + newdrop->owner = thismon->MonsterDrop->firsthit; + newdrop->thisparty = thismon->thisparty; + ClearItem(newdrop->item); + + CPlayer* thisclient = GServer->GetClientByCID(thismon->MonsterDrop->firsthit); + if(thisclient == NULL)return NULL; + + // code to modify drop chance for different levels + //float charm = 0; + //float droprate = thismon->thisnpc->dropchance; + int droprate = Config.DROP_RATE; + int DropFloor = Config.BASE_DROP_CHANCE; + int leveldif = (int)((float)thismon->thisnpc->level - (float)thisclient->Stats->Level); + float mod = droprate * 0.01 * leveldif; + int dropchance = int(droprate + mod); + if(dropchance < DropFloor) dropchance = DropFloor; //always a small chance of a drop even when the mob is more than 20 levels beneath your own + if(thismon->thisnpc->level == 1) + dropchance = 80; + if( thisclient->Session->codedebug ) + SendPM(thisclient, "Drop rate (based on level diference) = %i", dropchance ); + int droprand = GServer->RandNumber(0,100); + if (droprand > dropchance) + { + if( thisclient->Session->codedebug ) + SendPM(thisclient, "Drop rate check failed with %i. No drop this time",droprand); + return NULL; // no drop here. not this time anyway. + } + int DropListSize = MDropList.size(); + CItemType prob[MDropList.size()]; + bool isdrop = false; + int n = 0; + int test = 0; + long int probmax = 0; + int itemnumber[MDropList.size()]; + int itemtype[MDropList.size()]; + int probability[MDropList.size()]; + int alternate[MDropList.size()][8]; + + if( thismon->IsGhost()) + { + // Stuff to do if the mob is a ghost of any type + int selection = 1 + rand()%10; + if( selection <= 5 ) + { + newdrop->type = 1; //Drop Zuly. + newdrop->amount = thismon->thisnpc->level * 10 * Config.ZULY_RATE + RandNumber( 1, 10 ); + return newdrop; + } + else + { + for(int i=0; itype = 2; + CMDrops* thisdrop = GServer->SkillbookList.at(i); + if(thisdrop->level_min <= thismon->thisnpc->level && thisdrop->level_max >= thismon->thisnpc->level) + { + itemnumber[n] = thisdrop->itemnum; + itemtype[n] = thisdrop->itemtype; + probability[n] = thisdrop->prob; + probmax += thisdrop->prob; + n++; + } + } + } + } + else // Stuff to do if the mob isn't a ghost + { + int dropmode = 0; + int randv = RandNumber( 1, 100); + // Each monster has its own rates for zuly and item drops defined in the stb + // We also add a small amount based on the player's ItemDropRate and ZulyDroprate. + dropchance = thismon->thisnpc->item + thismon->thisnpc->money + thisclient->Stats->itemdroprate + thisclient->Stats->zulydroprate; + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "randv = %i. total drop chance = %i. money = %i. zulyrate = %i. Item = %i. Itemrate = %i", randv,dropchance,thismon->thisnpc->money,thisclient->Stats->zulydroprate,thismon->thisnpc->item,thisclient->Stats->itemdroprate ); + } + if(randv > dropchance)return NULL; // did not qualify to drop anything this time + if(randv <= (thismon->thisnpc->money + thisclient->Stats->zulydroprate)) // zuly drop instead of item drop + { + newdrop->type = 1; + newdrop->amount = thismon->thisnpc->level * 5 * Config.ZULY_RATE + RandNumber( 1, 10 ); + return newdrop; + } + + int randomdrop = GServer->RandNumber(1, 100); + for(int i=0; iMDropList.at(i); + if(thisdrop->mob != 0 && thisdrop->mob != thismon->montype) + isdrop = false; + if(thisdrop->map != 0 && thisdrop->map != thismon->Position->Map) + isdrop = false; + if(thisdrop->level_max > thisdrop->level_min && (thismon->thisnpc->level < thisdrop->level_min || thismon->thisnpc->level > thisdrop->level_max)) + isdrop = false; + + test = GServer->RandNumber(1, 10000); + if(thisdrop->prob < test) + isdrop = false; + if(isdrop == true) + { + itemnumber[n] = thisdrop->itemnum; + itemtype[n] = thisdrop->itemtype; + alternate[n][0] = 0; + for(int j=1;i<8;i++) + { + alternate[n][j] = thisdrop->alt[j]; + } + n++; + } + } + } + int newn = n; + if(n == 0) + return NULL; + int maxitems = n; + // randomize the item from the list + n = GServer->RandNumber(0, maxitems); + newdrop->item.itemnum = itemnumber[n]; + newdrop->item.itemtype = itemtype[n]; + newdrop->type = 2; + if( thisclient->Session->codedebug ) + { + for(int j=0;jitem.itemtype,newdrop->item.itemnum); + } + + newdrop->item.lifespan = 10 + rand()%80; + float dmod = 0; //random number from 0 to 100 made up of 5 sub numbers to keep + //the average value near to 50 + for(int i=0; i<5; i++) + { + float r1 = rand()%20; + dmod += r1; + } + newdrop->item.durability = 10 + (int)dmod; + if( newdrop->item.itemtype == 8 || newdrop->item.itemtype == 9 ) + { + //This probability is now configurable from WorldServer.conf + int psocked = rand()%101; //Probability of finding a socketed item + if( psocked < Config.SlotChance) //default should be around 5% needs to be rare + { + newdrop->item.socketed = true; + } + else + { + newdrop->item.socketed = false; + } + } + else + { + newdrop->item.socketed = false; + } + //Log( MSG_INFO, "Socket are OK"); + newdrop->item.appraised = false; + newdrop->item.stats = 0; + newdrop->item.count = 1; + + //int chamod = 0; + int chamod = (int)floor(thisclient->Attr->Cha / 30); + if(chamod < 0) chamod = 0; + int basedrop = thisclient->Stats->itemdroprate; //Base number of items to be dropped. + int maxdrop = basedrop + Config.DropMultiple + chamod; + if(newdrop->item.itemtype == 11) //gems only get 1 + newdrop->item.count = 1; + if( newdrop->item.itemtype == 10 || newdrop->item.itemtype == 12 ) + { + newdrop->item.count = RandNumber(basedrop, maxdrop); + if(thismon->thisnpc->level == 1) + newdrop->item.count = 1 + chamod; //limit the drop rate of items from level 1 event mobs + if(newdrop->item.count == 0) + newdrop->item.count = 1; + if(newdrop->item.type == 314) //skillbooks + newdrop->item.count = 1; + if(newdrop->item.itemtype == 10) + { + if(newdrop->item.itemnum >= 441 && newdrop->item.itemnum <= 888)// skillbooks + newdrop->item.count = 1; + } + + if(newdrop->item.itemtype == 12) + { + newdrop->item.count = RandNumber(0, 6) + RandNumber(0, thisclient->Stats->itemdropcountrate); //basic type 12 items + //some item types have their own rules + if(newdrop->item.type == 428) //special skill items such as mana jams and scrolls + newdrop->item.count = RandNumber(0, 3); + if(newdrop->item.itemnum > 300 && newdrop->item.itemnum < 360) //bullets get a lot higher count. + { + newdrop->item.count *= 10; + newdrop->item.count += 10; + } + } + } + else if( newdrop->item.itemtype >1 && newdrop->item.itemtype !=7 && newdrop->item.itemtype < 10) + { + // check to see if the item will be refined + int prefine = rand()%100; //Probability of finding a refined item + int refinechance = Config.RefineChance; + if(prefine < refinechance) // default = 5% + { + int refinelevel = rand()%101; //which level of refine do we actually get + if( refinelevel < 5) //default 5% + newdrop->item.refine = 4 * 16; + else if( refinelevel < 15 ) //10% + newdrop->item.refine = 3 * 16; + else if(refinelevel < 35 ) // 20% + newdrop->item.refine = 2 * 16; + else // 65% + newdrop->item.refine = 16; + } + else //99% + newdrop->item.refine = 0; + + // will the item be a blue? + bool blue = false; + int bluechance1 = RandNumber( 1, 100); + int bluechance2 = Config.BlueChance + chamod; + if( thisclient->Session->codedebug ) + Log( MSG_INFO, "Blue chance = %i", bluechance2); + //This probability is now configurable from WorldServer.conf. CHA also has an effect + if(bluechance1 < bluechance2) // some percentage of drops will be specials or blues whenever one is available. + { + if( thisclient->Session->codedebug ) + Log( MSG_INFO, "Selected a blue item"); + int p = 1; + while(alternate[n][p] != 0 && p < 8) + { + p++; + } + if(p > 1) // blues available for this item + { + //Log( MSG_INFO, "blue item available"); + p--; + int bluenum = RandNumber( 1, p); + newdrop->item.itemnum = alternate[n][bluenum]; + blue = true; + } + } + //is the item a blue or other special or unique type? + if(newdrop->item.itemtype < 10 || newdrop->item.itemtype == 14) //equipable or PAT + { + if(GServer->EquipList[newdrop->item.itemtype].Index[newdrop->item.itemnum]->raretype != 0) + blue = true; + } + // will the items get stats? All blue items will. Uniques count as blues. + int pstats = rand()%101; //Probability of the item having stats. default = 5% + if(blue == true) + { + pstats = 0; + } + int StatChance = Config.StatChance; + if( pstats < StatChance) // default 5% + newdrop->item.stats = RandNumber( 1, 300); + } + newdrop->item.gem = 0; + //newdrop->droptime = clock(); //moved to AddDrop function + */ + return newdrop; + +} + + +UINT CWorldServer::GetColorExp( UINT playerlevel,UINT moblevel, UINT exp ) +{ +/* +purple = +16 - 200% +red = +10 - 150% +orange = +4 - 125% +yellow = same - 100% +green = -4 - 80% +blue = -9 - 50% +white = -15 - 20% +*/ + int dif = moblevel - playerlevel; + //Log(MSG_INFO,"Level dif = %i XP factor is %i",dif,factor); + if(dif > 15) //purple + exp = (UINT)floor(exp * 2.0); + else if(dif > 10) //red + exp = (UINT)floor(exp * 1.5); + else if(dif > 4) //orange + exp = (UINT)floor(exp * 1.25); + else if(dif >= 0) //yellow + exp = (UINT)floor(exp * 1.0); + else if(dif > -4) //green + exp = (UINT)floor(exp * 0.8); + else if(dif > -9) //blue + exp = (UINT)floor(exp * 0.5); + if(dif <= -9) //white + exp = (UINT)floor(exp * 0.2); + + if(playerlevel < 10) // lower the rate for the first 10 levels + exp = (UINT)floor(exp * 0.5); + return exp; +} + +bool CWorldServer::SendSysMsg( CPlayer* thisclient, string message ) +{ + BEGINPACKET( pak, 0x7df ); + ADDBYTE ( pak, 0xf1 ); + ADDSTRING ( pak, message.c_str( )); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; +} + + +// teleport to specificated map and position +bool CWorldServer::TeleportTo ( CPlayer* thisclient, int map, fPoint position ) +{ + if(map > 70) + return true; + MapList.Index[map]->TeleportPlayer( thisclient, position, false ); + return true; +} + + +// Learn Skill +bool CWorldServer::LearnSkill( CPlayer* thisclient, UINT skill, bool cost ) +{ +/* +0 - ya aprendido +1 - aprendio +2 - no job +3 - falta otro skill +4 - no status points (lvl) +5 - no se puede subir de lvl el skill +6 - numero incorrecto de skill +7 - no tienes suficientes sp +*/ + BEGINPACKET(pak1, 0 ); + BEGINPACKET(pak2, 0 ); + BEGINPACKET(pak3, 0 ); + //int b=1; + CSkills* thisskill = SkillList.at( skill); + char skilltype = thisskill->type; + //int skillpoints = thisskill->sp; + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Attempting to learn a new skill. id = %i skill type = %i Cost to learn = %i", thisskill->id,thisskill->type,thisskill->sp ); + SendPM(thisclient, buffer); + } + int newslot = thisclient->GetNewSkillSlot(skilltype); + //CSkills* thisskill = 0; + if (newslot != 0xffff) + { + switch(skilltype) + { + case 0: + thisclient->bskills[newslot]= skill; + if (cost) + thisclient->CharInfo->SkillPoints -= thisskill->sp; + RESETPACKET(pak1, 0x7b0); + ADDBYTE(pak1, 1); + ADDBYTE(pak1, newslot); + ADDWORD(pak1, skill); + ADDWORD(pak1, thisclient->CharInfo->SkillPoints); + thisclient->client->SendPacket(&pak1); + thisclient->SetStats(); + return true; + case 1: + thisclient->askill[newslot]= skill; + if (cost) + thisclient->CharInfo->SkillPoints-=thisskill->sp; + RESETPACKET(pak2, 0x7b0); + ADDBYTE(pak2, 1); + ADDBYTE(pak2, 30+newslot); + ADDWORD(pak2, skill); + ADDWORD(pak2, thisclient->CharInfo->SkillPoints); + thisclient->client->SendPacket(&pak2); + thisclient->SetStats(); + return true; + case 2: + thisclient->pskill[newslot]= skill; + if (cost) + thisclient->CharInfo->SkillPoints-=thisskill->sp; + RESETPACKET(pak3, 0x7b0); + ADDBYTE(pak3, 1); + ADDBYTE(pak3, 60+newslot); + ADDWORD(pak3, skill); + ADDWORD(pak3, thisclient->CharInfo->SkillPoints); + thisclient->client->SendPacket(&pak3); + thisclient->SetStats(); + return true; + default: + return false; + } + } + return false; +} + +//add / remove a Fairy +void CWorldServer::DoFairyStuff(CPlayer* targetclient, int action) +{ + BEGINPACKET( pak, 0x7dc ); + ADDBYTE( pak, action ); + ADDWORD( pak, targetclient->clientid); + SendToVisible( &pak, targetclient ); +} + +void CWorldServer::DoFairyFree( int fairy ) +{ + GServer->FairyList.at(fairy)->assigned = false; + GServer->FairyList.at(fairy)->ListIndex = 0; + + if(GServer->Config.FairyTestMode == 0) + GServer->FairyList.at(fairy)->WaitTime = GServer->Config.FairyWait * (rand()% GServer->GetFairyRange(1)+ GServer->GetFairyRange(0)); + + GServer->FairyList.at(fairy)->LastTime = clock(); +} + +void CWorldServer::RefreshFairy( ) +{ + //fairy: The wait timer should be possibibly bigger when there is few people online. + if (Config.FairyMode == 1 && ClientList.size() > 2) //if fairy mode on and a few people online + { + for (int i=0; iLastTime <= ( clock() - (FairyList.at(i)->WaitTime * 60 * CLOCKS_PER_SEC) ) && !FairyList.at(i)->assigned ) // if fairy hour is now + { + if(ClientList.size() > 2) //stop solo players from getting fairied and screwing up their stats during testing. + { + int value = rand()%(int)(ClientList.size()-1) + 1; //choose random value in clientlist + CPlayer* targetclient = (CPlayer*) ClientList.at(value)->player; + if(targetclient == NULL) + return; + FairyList.at(i)->ListIndex = value; + FairyList.at(i)->LastTime = clock(); + FairyList.at(i)->assigned = true; + FairyList.at(i)->scroll = false; // Scroll value set to false. this is NOT a scroll buff fairy + targetclient->Fairy = true; + targetclient->FairyListIndex = i; // FairyList index number of our actual fairy + DoFairyStuff(targetclient, 1); // spawn fairy to target + targetclient->SetStats(); + } + } + if ( (FairyList.at(i)->LastTime + (Config.FairyStay*60*CLOCKS_PER_SEC)) <= clock() && FairyList.at(i)->assigned) // id time for our fairy to go away + { + CPlayer* oldclient = (CPlayer*) ClientList.at(FairyList.at(i)->ListIndex)->player; + if(FairyList.at(i)->scroll == true) //this is a scroll fairy + { + GServer->Config.ScrollFairy --; //reduce count of scroll fairies by one + if(GServer->Config.ScrollFairy < 0)GServer->Config.ScrollFairy = 0; //Make sure we don't go negative + GServer->Config.FairyMax = (int)ceil((float)GServer->ClientList.size() / 50.0) + GServer->Config.ScrollFairy; //recalculate the maximum number of fairies + FairyList.at(i)->scroll = false; //reset scroll value + } + DoFairyFree(i); + oldclient->Fairy = false; + oldclient->FairyListIndex = 0; + DoFairyStuff(oldclient, 0); // unspawn fairy + oldclient->SetStats(); + } + } + } + // this close fairies after their time if GM de activate fairy mode when some fairies are assigned. + if (Config.FairyMode == 0 && ClientList.size() > 1){ // if serevr mode off and someone online + for (int i=0; iLastTime + (Config.FairyStay*60*CLOCKS_PER_SEC)) <= clock() && FairyList.at(i)->assigned) + { + CPlayer* oldclient = (CPlayer*) ClientList.at(FairyList.at(i)->ListIndex)->player; + DoFairyFree(i); + oldclient->Fairy = 0; + oldclient->FairyListIndex = 0; + DoFairyStuff(oldclient, 0); + oldclient->SetStats(); + } + } + } +} diff --git a/Src/World Server/Signal.cpp b/Src/World Server/Signal.cpp new file mode 100644 index 0000000..bfc133a --- /dev/null +++ b/Src/World Server/Signal.cpp @@ -0,0 +1,113 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" +/* +/ from signal.h + * NOTE: SIGINT is produced when the user presses Ctrl-C. + * SIGILL has not been tested. + * SIGFPE doesn't seem to work? + * SIGSEGV does not catch writing to a NULL pointer (that shuts down + * your app; can you say "segmentation violation core dump"?). + * SIGTERM comes from what kind of termination request exactly? + * SIGBREAK is indeed produced by pressing Ctrl-Break. + * SIGABRT is produced by calling abort. +*/ + +void StartSignal( ) +{ + signal( SIGINT, HandleSignal ); + signal( SIGILL, HandleSignal ); + signal( SIGFPE, HandleSignal ); + signal( SIGSEGV, HandleSignal ); + signal( SIGTERM, HandleSignal ); + signal( SIGBREAK, HandleSignal ); + signal( SIGABRT, HandleSignal ); +} + +void StopSignal( ) +{ + signal( SIGINT, 0 ); + signal( SIGILL, 0 ); + signal( SIGFPE, 0 ); + signal( SIGSEGV, 0 ); + signal( SIGTERM, 0 ); + signal( SIGBREAK, 0 ); + signal( SIGABRT, 0 ); +} + +void HandleSignal( int num ) +{ + switch(num) + { + case SIGINT:/* Interactive attention */ + Log( MSG_ERROR, "Signal received: SIGINT" ); + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + delete GServer; + raise(num); + break; + case SIGILL:/* Illegal instruction */ + Log( MSG_ERROR, "Signal received: SIGILL, Server will be closed, Trying to save..." ); + GServer->DisconnectAll(); + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + delete GServer; + raise(num); + break; + case SIGFPE:/* Floating point error */ + Log( MSG_ERROR, "Signal received: SIGFPE, Server will be closed, Trying to save..." ); + GServer->DisconnectAll(); + delete GServer; + raise(num); + break; + case SIGSEGV:/* Segmentation violation */ + Log( MSG_ERROR, "Signal received: SIGSEGV, Server will be closed, Trying to save..." ); + GServer->DisconnectAll(); + #ifdef _WIN32 + Sleep(1000); + #else + usleep(1000); + #endif + delete GServer; + raise(num); + break; + + case SIGTERM:/* Termination request */ + Log( MSG_INFO, "Signal received: SIGTERM, Server will be closed" ); + pthread_create( &GServer->WorldThread[SHUTDOWN_THREAD], NULL, (void *(__cdecl *)(void *))ShutdownServer, (PVOID)0); + break; + #ifdef _WIN32 + case SIGBREAK:/* Control-break */ + Log( MSG_INFO, "Signal received: SIGBREAK, Server will be closed" ); + pthread_create( &GServer->WorldThread[SHUTDOWN_THREAD], NULL, (void *(__cdecl *)(void *))ShutdownServer, (PVOID)0); + break; + #endif + default: + cout << "Unknown Signal Number: " << num << endl; + raise(num); + } +} diff --git a/Src/World Server/SpawnMonsterCode.txt b/Src/World Server/SpawnMonsterCode.txt new file mode 100644 index 0000000..c3fbea6 --- /dev/null +++ b/Src/World Server/SpawnMonsterCode.txt @@ -0,0 +1,53 @@ +// Spawn a monster +void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) +{ + BEGINPACKET( pak, 0x792 ); + ADDWORD ( pak, clientid ); + ADDFLOAT ( pak, Position->current.x*100 ); + ADDFLOAT ( pak, Position->current.y*100 ); + ADDFLOAT ( pak, Position->destiny.x*100 ); + ADDFLOAT ( pak, Position->destiny.y*100 ); + + if(IsDead( )) + { + ADDWORD ( pak, 0x0003 ); + ADDWORD ( pak, 0x0000 ); + } + else if(IsOnBattle( )) + { + ADDWORD ( pak, 0x0002 ); + ADDWORD ( pak, Battle->target ); + } + else if(IsMoving( )) + { + ADDWORD ( pak, 0x0001 ); + ADDWORD ( pak, 0x0000 ); + } + else + { + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + } + + ADDBYTE ( pak, thisnpc->stance ); + ADDWORD ( pak, Stats->HP ); + + ADDDWORD ( pak, Stats->HP ); + if(thismon->owner != player->clientid) + { + CMap* map = GServer->MapList.Index[Position->Map]; + if(IsSummon( ) && map->allowpvp!=0) {ADDWORD( pak, 0x0064 );} //Hostil + else if (IsSummon( ) && map->allowpvp==0) {ADDWORD ( pak, 0x0000 );}//Friendly + else {ADDWORD( pak, 0x0064 );} //Hostil + } + else {ADDWORD( pak, 0x0000 );}//Friendly + ADDDWORD( pak, GServer->BuildBuffs( this ) ); + ADDWORD ( pak, montype ); + ADDWORD ( pak, 0x0000 ); + if(IsSummon( )) + { + ADDWORD( pak, owner ); + ADDWORD( pak, 0x0000 ); // + } + player->client->SendPacket( &pak ); +} \ No newline at end of file diff --git a/Src/World Server/WorldMonster.cpp b/Src/World Server/WorldMonster.cpp new file mode 100644 index 0000000..84739a1 --- /dev/null +++ b/Src/World Server/WorldMonster.cpp @@ -0,0 +1,70 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" + +// Monster Contructor +CMonster::CMonster( fPoint MonsterPosition, UINT MonsterType, UINT MonsterMap, UINT Owner, UINT spawnid ) +{ + montype = MonsterType; + SpawnTime = clock( ); + lastSighCheck = clock( ); + MonsterDrop = NULL; + Position->source = MonsterPosition; + Position->current = MonsterPosition; + Position->destiny = MonsterPosition; + Position->Map = MonsterMap; + Position->respawn = spawnid; + Position->lastMoveTime = clock(); + // Magic status (buffs) + for(unsigned int i=0;i<30;i++) + { + MagicStatus[i].Status = 0; + MagicStatus[i].Buff = 0; + MagicStatus[i].Value = 0; + MagicStatus[i].Duration = 0; + MagicStatus[i].BuffTime = 0; + } + + // Monster drop + MonsterDrop = new MDrop; + assert(MonsterDrop); + MonsterDrop->drop = false; + MonsterDrop->firsthit = 0; + MonsterDrop->firstlevel = 0; + MonsterDrop->mobdrop = NULL; + MonsterDrop->mapdrop = NULL; + clientid = GServer->GetNewClientID(); + owner = Owner; + thisparty = NULL; + if(owner == 0) CharType = TMONSTER; + else CharType = TSUMMON; + +} + +// Monster destructor +CMonster::~CMonster( ) +{ + if(MonsterDrop!=NULL); + delete MonsterDrop; + for(UINT i=0;i PlayersDamage; + + // Monster Functions + void DoAi(int ainumber,char type); + //bool Guardiantree(CMonster* monster, CMap* map); + bool Scarab(CMonster* monster, CMap* map); + bool SummonUpdate(CMonster* monster, CMap* map, UINT j); + bool SetStats( ); + bool UnspawnMonster( ); + bool IsGhost( ); + bool IsGhostSeed( ); + bool CanMove( ); + void OnEnemyDie( void* enemy ); + bool ReadyToMove( ); + void MoveTo( fPoint nPos, bool randcircle=true ); + void Move( ); + CPlayer* GetOwner( ); + void SpawnMonster( CPlayer* player, CMonster* thismon ); + bool PlayerInRange( ); + CPlayer* GetNearPlayer( UINT mdist = 20 ); + void AddDamage( CCharacter* enemy, long int hitpower); + CDrop* GetDrop( ); + + // + bool IsMonster( ); + bool UpdateValues( ); + + // Stats + unsigned int GetAttackPower( ); + unsigned int GetDefense( ); + unsigned int GetXPRate( ); + unsigned int GetItemDropRate( ); + unsigned int GetItemDropCountRate( ); + unsigned int GetZulyDropRate( ); + unsigned int GetGreyDrops( ); + unsigned int GetDodge( ); + unsigned int GetAccury( ); + unsigned int GetMagicDefense( ); + unsigned int GetMagicAttack( ); + unsigned int GetCritical( ); + unsigned int GetAttackSpeed( ); + unsigned int GetMoveSpeed( ); + unsigned int GetMaxHP( ); + float GetAttackDistance( ); + + // Events + bool OnBeAttacked( CCharacter* Enemy ); + bool OnEnemyOnSight( CCharacter* Enemy ); + bool OnDie( ); + bool OnEnemyDie( CCharacter* Enemy ); + bool OnSpawn( bool ); + bool OnAlmostDie( ); + bool OnFar( ); +}; +#endif diff --git a/Src/World Server/WorldServer.h b/Src/World Server/WorldServer.h new file mode 100644 index 0000000..d9fb8f2 --- /dev/null +++ b/Src/World Server/WorldServer.h @@ -0,0 +1,458 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + + some change from the original eich source + + parts copied from/based on work from ExJem +*/ +//#define debugging +#ifndef __ROSE_SERVERS__ +#define __ROSE_SERVERS__ +#include "datatypes.h" +#include "sockets.h" +#include "worldmonster.h" +#include "quest/CRoseArray.hpp" +#include "quest/CRoseFile.hpp" +#include "quest/CStrStb.hpp" +#include "quest/CIntStb.hpp" +#include "quest/QuestTrigger.h" +#include "aip/Aip.h" +#include "worldmap.h" +#include "player.h" +#include "character.h" +#include "party.h" +#include "stbhandler.h" +#include "stlhandler.h" +#include "strhashing.h" + +#define MAXVISUALRANGE 90 +#define MINVISUALRANGE 80 +#define ClearItem(i) { i.appraised=false; i.count=0; i.durability=0; i.itemnum=0; i.itemtype=0; i.lifespan=0; i.refine=0; i.socketed=false; i.stats=0; i.gem=0; } + +#define WORLD_THREAD 0 +#define VISUALITY_THREAD 1 +#define SHUTDOWN_THREAD 2 + +#define MAX_MAPS 115 + +// Main loginserver server class +class CWorldServer : public CServerSocket +{ + public: + //------------------ MAIN (worldserver.cpp) + CWorldServer ( string ); + CClientSocket* CreateClientSocket( ); + ~CWorldServer( ); + bool OnReceivePacket( CClientSocket* thisclient, CPacket* P ); + void ReceivedISCPacket( CPacket* pak ); // Raven0123 + void SendISCServerInfo( ); + void OnClientDisconnect( CClientSocket* thisclient ); + void DeleteClientSocket( CClientSocket* thisclient ); + bool OnServerReady( ); + void SpawnMonsters( ); + bool Ping( ); + void LoadConfigurations( const char* ); + void LoadCommandLevels( void ); + void ServerLoop( ); + void LoadEncryption( ); + bool pakGiveItem( CPlayer* thisclient, unsigned char itemtype, unsigned short itemid, unsigned short count ); + bool pakGiveExp( CPlayer* thisclient, unsigned exp ); + bool pakGiveMoney( CPlayer* thisclient, unsigned amount ); + bool pakTeleport( CPlayer* thisclient, unsigned short map, float x, float y ); + bool pakLearnSkill( CPlayer* thisclient, unsigned short skillid ); + void DePopulateZone( CPlayer* thisclient, int zonex, int zoney ); + bool pakQuestTrigger( CPlayer* thisclient, CPacket* pak ); + + //------------------ WORLD PROCESS (worldprocess.cpp) + bool GiveExp( CMonster* thismon ); + + //------------------ BUFFS (buff.cpp) + CBValue GetBuffValue( CSkills* thisskill, CCharacter* character, UINT Evalue, UINT i, UINT up, UINT down, UINT CurrentValue, bool Buff=true, bool Status=false ); + //CBValue GetBuffValue( CSkills* thisskill, CCharacter* character, UINT Evalue, UINT i, UINT up, UINT down, UINT CurrentValue, bool Buff=true ); + bool CheckABuffs( CSkills* thisskill, CCharacter* character, int Evalue ,int i); + //bool CheckDBuffs( CSkills* thisskill, CCharacter* character, int Evalue ,int i); + bool AddBuffs( CSkills* thisskill, CCharacter* character, int Evalue , bool flag); + //bool AddDeBuffs( CSkills* thisskill, CCharacter* character, int Evalue ); + bool AddBuffs( CSkills* thisskill, CCharacter* character, int Evalue ); + bool InstantBuff( CSkills* thisskill, CCharacter* character, int Evalue, int i ); + + + //------------------ SERVER EXTRAS (extrafunctions.cpp) + bool IsValidItem(UINT type, UINT id ); + unsigned BuildItemShow(CItem thisitem); + UINT GetUIntValue( const char* s , void* var=NULL ); + int GetIntValue( const char* s , void* var=NULL ); + char* GetStrValue( const char* s , void* var=NULL ); + bool SendSysMsg( CPlayer* thisclient, string message ); + UINT RandNumber( UINT init, UINT range, UINT seed=0 ); + UINT GetColorExp( UINT playerlevel,UINT moblevel, UINT exp ); + bool CheckInventorySlot( CPlayer* thisclient, int slot ); + bool pakGMClass( CPlayer* thisclient, char* classid ); + bool pakGMKillInRange( CPlayer* thisclient, int range ); + bool pakGMHide( CPlayer* thisclient, int mode ); + void SendToVisible( CPacket* pak, CPlayer* thisclient, bool thisclients=true ); + void SendToVisible( CPacket* pak, CPlayer* thisclient, CPlayer* xotherclient ); + + void SendToVisible( CPacket* pak, class CCharacter* character, CDrop* thisdrop=NULL ); + + bool IsMonInCircle( CPlayer* thisclient, CMonster* thismon, float radius ); + bool IsPlayerInCircle( CPlayer* thisclient, CPlayer* otherclient, float radius ); + CUseInfo* GetUseItemInfo( CPlayer* thisclient, UINT slot ); + bool IsVisible( CPlayer* thisclient, CPlayer* otherclient ); + bool IsMonInCircle( fPoint center, fPoint position, float radius ); + bool IsVisible( CPlayer* thisclient, CMonster* thismon ); + bool IsVisible( CPlayer* thisclient, CDrop* thisdrop ); + CItem GetItemByHeadAndData( unsigned head, unsigned data ); + bool IsVisible( CPlayer* thisclient, CNPC* thisnpc ); + void SendToVisible( CPacket* pak, CMonster* thismon, CDrop* thisdrop=NULL ); + void SendToVisible( CPacket* pak, CDrop* thisdrop ); + CPlayer* GetClientByUserName( char *username ); + UINT BuildBuffs( CCharacter* player ); + UINT BuildUpBuffs( CCharacter* player ); + UINT BuildDeBuffs( CCharacter* player ); + void CheckForLevelUp ( CPlayer* thisclient ); + bool isSkillTargetFriendly( CSkills* thisskill ); + CRespawnPoint* GetRespawnByID( UINT id ); + CPlayer* GetClientByCID( UINT id, UINT map=0 ); + CPlayer* GetClientByCharName( char* name ); + CPlayer* GetClientByID( UINT id, UINT map=0 ); + CTeleGate* GetTeleGateByID( UINT id ); + UINT BuildItemHead( CItem thisitem ); + UINT BuildItemData( CItem thisitem ); + CMonster* GetMonsterByID( UINT id, UINT map ); + CSpawnArea* GetSpawnArea( UINT id, UINT map=0 ); +#ifdef USEIFO + CMobGroup* GetMobGroup( UINT id, UINT map=0 ); +#endif + bool DeleteSpawn( CSpawnArea* spawn ); + CNPCData* GetNPCDataByID( UINT id ); + void SendToMap( CPacket* pak, int mapid ); + unsigned BuildItemRefine(CItem thisitem ); + CMDrops* GetDropData( UINT id ); + CRespawnPoint* GetRespawnByMap( int id ); + CSkills* GetSkillByID( UINT id ); + CStatus* GetStatusByID( UINT id ); + void ClearClientID( UINT id ); + CDrop* GetDropByID( UINT id, UINT map=0 ); + CNPC* GetNPCByID( UINT id, UINT map=0 ); + CDrop* GetNewDrop( CMonster* thismon ); + CDrop* GetPYDrop( CMonster* thismon, UINT droptype ); + void SendToAll( CPacket* pak ); + UINT GetNewClientID( ); + void DisconnectAll(); + CPlayer* GetClientByUserID( UINT userid ); + UINT GetMatIDfromRecipe (UINT CraftLevel, UINT matType); + UINT GetFairyRange( UINT part ); + void RefreshFairy( ); + inline dword GetStorageFee(dword iBasePrice, dword iPriceRate, dword uiDupCnt) {return ((((iBasePrice*5) / 0x3E8)+1)*(iPriceRate+1)) * uiDupCnt;}; + + + //------------------ Fairies --------------------- + void DoFairyStuff( CPlayer* targetclient, int action ); + void DoFairyFree( int fairy ); + + //------------------ MATH (extrafunctions.cpp) + fPoint RandInCircle(fPoint center, float radius); + fPoint RandInPoly(fPoint p[], int pcount); + fPoint RandInTriangle(fPoint p1, fPoint p2, fPoint p3); + float AreaOfTriangle(fPoint p1, fPoint p2, fPoint p3); + float distance( fPoint pos1, fPoint pos2 ); + + //------------------ PACKETS (worldpackets.cpp) + bool pakItemMall( CPlayer* thisclient, CPacket* P ); + bool pakWeight( CPlayer* thisclient, CPacket* P ); + bool pakPrintscreen( CPlayer* thisclient, CPacket* P ); + bool pakModifiedItemDone( CPlayer* thisclient, CPacket* P ); + bool pakSpawnDrop( CPlayer* thisclient, CDrop* thisdrop ); + bool pakChangeStorage( CPlayer* thisclient, CPacket* P ); + bool pakModifiedItem( CPlayer* thisclient, CPacket* P ); + bool pakPartyManager( CPlayer* thisclient, CPacket* P ); + bool pakLevelUpSkill( CPlayer *thisclient, CPacket* P ); + bool pakChangeStance( CPlayer* thisclient, CPacket* P ); + bool pakCharDSClient( CPlayer* thisclient, CPacket* P ); + bool pakChangeEquip( CPlayer* thisclient, CPacket* P ); + bool pakStartAttack( CPlayer* thisclient, CPacket* P ); + bool pakSpawnNPC( CPlayer* thisclient, CNPC* thisnpc ); + bool pakRideRequest( CPlayer* thisclient, CPacket* P ); + bool pakPartyActions( CPlayer* thisclient, CPacket* P ); + bool pakPartyOption( CPlayer* thisclient, CPacket* P ); + bool pakTradeAction( CPlayer* thisclient, CPacket* P ); + bool pakChangeCart( CPlayer* thisclient, CPacket* P ); + bool pakSkillSelf ( CPlayer* thisclient, CPacket* P ); + bool pakDoIdentify( CPlayer *thisclient, CPacket *P ); + bool pakNormalChat( CPlayer* thisclient, CPacket* P ); + bool pakCharSelect( CPlayer* thisclient, CPacket* P ); + bool pakStartSkill( CPlayer* thisclient, CPacket* P ); + bool pakRepairHammer( CPlayer* thisclient, CPacket* P ); + bool pakRepairItem( CPlayer* thisclient, CPacket* P ); + bool pakMoveSkill( CPlayer* thisclient, CPacket* P ); + bool pakCloseShop( CPlayer* thisclient, CPacket* P ); + bool pakStoreZuly( CPlayer* thisclient, CPacket* P ); + bool pakPartyChat( CPlayer* thisclient, CPacket* P ); + bool pakGameGuard( CPlayer* thisclient, CPacket* P ); + bool pakOpenShop( CPlayer* thisclient, CPacket* P ); + bool pakShowShop( CPlayer* thisclient, CPacket* P ); + bool pakSellShop( CPlayer* thisclient, CPacket* P ); + bool pakMoveChar( CPlayer* thisclient, CPacket* P ); + bool pakStopChar( CPlayer* thisclient, CPacket* P ); + bool pakPickDrop( CPlayer* thisclient, CPacket* P ); + bool pakAddStats( CPlayer* thisclient, CPacket* P ); + bool pakShowHeal( CPlayer* thisclient, CPacket* P ); + bool pakSkillAOE( CPlayer* thisclient, CPacket* P ); + bool pakidentify( CPlayer* thisclient, CPacket* P ); + bool pakUserDied( CPlayer* thisclient, CPacket* P ); + bool pakSaveTown( CPlayer* thisclient, CPacket* P ); + bool pakTradeAdd( CPlayer* thisclient, CPacket* P ); + bool pakUseItem ( CPlayer* thisclient, CPacket* P ); + bool pakWhisper( CPlayer* thisclient, CPacket* P ); + bool pakEquipABC(CPlayer* thisclient, CPacket *P ); + bool pakStorage( CPlayer* thisclient, CPacket* P ); + bool pakBuyShop( CPlayer* thisclient, CPacket* P ); + bool pakDoEmote( CPlayer* thisclient, CPacket* P ); + bool pakNPCBuy( CPlayer* thisclient, CPacket* P ); + bool pakDoDrop( CPlayer* thisclient, CPacket* P ); + bool pakShout( CPlayer* thisclient, CPacket* P ); + bool pakCraft( CPlayer* thisclient, CPacket* P ); + bool pakRespawn( CPlayer* thisclient, CPacket* P ); + bool pakDoID( CPlayer* thisclient, CPacket* P ); + bool pakGate( CPlayer* thisclient, CPacket* P ); + bool pakExit( CPlayer* thisclient, CPacket* P ); + bool pakPing( CPlayer* thisclient, CPacket* P ); + bool SendLevelUPtoChar(CPlayer* thisclient ); + void pakPlayer( CPlayer *thisclient ); + void pakInventory( CPlayer *thisclient ); + void pakQuestData( CPlayer *thisclient ); + void pakClearUser( CPlayer* thisclient ); + bool LearnSkill( CPlayer* thisclient, UINT skill, bool cost = true ); + + //-------------------------- Clan functions + bool pakClanManager ( CPlayer* thisclient, CPacket* P ); + bool pakCreateClan ( CPlayer* thisclient, CPacket* P ); + + //------------------ GM COMMANDS (gmcmds.cpp) + bool pakGMMoveTo( CPlayer* thisclient, fPoint position ); + bool pakGMTeleAllHere( CPlayer* thisclient ); + bool GMShowTargetInfo( CPlayer* thisclient ); + bool pakGMServerInfo( CPlayer* thisclient ); + bool pakGMHeal( CPlayer* thisclient, int hpamount, int mpamount ); + bool pakGMStat( CPlayer* thisclient, const char* statname, int statvalue ); + bool pakGMItemtoplayer(CPlayer* thisclient, char* name , UINT itemid , UINT itemtype , UINT itemamount , UINT itemrefine , UINT itemls, UINT itemstats , UINT itemsocket) ; + bool pakGMItem( CPlayer* thisclient, UINT itemid , UINT itemtype , UINT itemamount , UINT itemrefine , UINT itemls, UINT itemstats , UINT itemsocket ); + bool pakGMTeleOtherPlayer( CPlayer *thisclient, char* name, int map, float x, float y ); + bool pakGMZuly( CPlayer* thisclient, int mode, int amount, char* charname ); + bool pakGMTele( CPlayer* thisclient, int map, float x, float y ); + bool pakGMMon( CPlayer* thisclient, int montype, int moncount, int monAI ); + bool pakGMZulygive(CPlayer* thisclient, char* name, int zuly); + bool pakGMFairyto(CPlayer* thisclient, char* name, int mode); + bool pakGMManageFairy(CPlayer* thisclient, int mode); + bool pakGMChangeFairyWait(CPlayer* thisclient, int newvalue); + bool pakGMChangeFairyStay(CPlayer* thisclient, int newvalue); + bool pakGMChangeFairyTestMode(CPlayer* thisclient, int mode); + bool pakGMTelePlayerHere( CPlayer* thisclient, char* name ); + bool pakGMLevel( CPlayer* thisclient, int level , char* name); + bool pakGMTeleToPlayer( CPlayer* thisclient, char* name ); + bool pakGMDoEmote( CPlayer* thisclient, int emotionid ); + bool pakGMCommand( CPlayer* thisclient, CPacket* P ); + bool ReloadMobSpawn( CPlayer* thisclient, int id ); + bool pakGMKick( CPlayer* thisclient, char* name ); + bool pakGMAnn( CPlayer* thisclient, CPacket *P ); + bool pakGMBan( CPlayer* thisclient, char* name ); + bool pakGMInfo(CPlayer* thisclient, char* name); + bool pakGMNpc(CPlayer* thisclient, int npcid, int dialogid); + bool pakGMGotomap( CPlayer* thisclient, int map ); + bool pakGMEffect( CPlayer* thisclient, int effect); + bool pakGMMute( CPlayer* thisclient, char* name, int time); + bool pakGMPartylvl( CPlayer* partyclient, int level ); + bool pakGMChangePlayerDmg(CPlayer* thisclient, int rate); + bool pakGMChangeMonsterDmg(CPlayer* thisclient, int rate); + bool pakGMChangeMSpeedModif(CPlayer* thisclient, int modif); + bool pakGMChangeCfmode(CPlayer* thisclient, int mode); + bool pakGMUnionPoints(CPlayer* thisclient, char* name, int nb_points); + + //------------------ CHAR SERVER COMMANDS (worldpackets.cpp) + bool TeleportTo ( CPlayer* thisclient, int map, fPoint position ); + bool pakCSCharSelect( CPlayer* thisclient, CPacket* P ); + bool pakCSReady( CPlayer* thisclient, CPacket* P ); + + //----------------- Server StartUp Functions (startup.cpp) + bool LoadSTBData( ); + bool LoadZoneData( ); + bool LoadConsItem( ); + bool LoadSellData( ); + bool LoadProductItem( ); + bool LoadPatItem( ); + bool LoadNaturalItem( ); + bool LoadJemItem( ); + bool LoadEquip( ); + bool LoadItemStats( ); + bool LoadTeleGateData( ); + bool LoadMonsterSpawn( ); + bool LoadMobGroups( ); + bool LoadRespawnData( ); + bool LoadWayPoints( ); + bool LoadBreak( ); + bool LoadGrades(); //LMA: loading refines (grades) bonuses. + + bool LoadNewDrops( ); + bool LoadPYDropsData( ); + bool LoadSkillBookDropsData( ); + bool LoadConfig( ); + bool LoadLTB( ); + + bool LoadStatusData( ); + bool LoadSkillData( ); + bool LoadMonsters( ); + bool LoadNPCData( ); + bool LoadUpgrade( ); + bool LoadNPCs( ); + bool LoadSTB( ); + bool LoadAIP( ); + bool CleanConnectedList( ); + + void LoadZones(); + + // QSD Fuctions + void ReadQSD(strings path, dword index); + void LoadQuestData(); + inline int round(double x) {return int(x > 0.0 ? x + 0.5 : x - 0.5);}; + // AIP Functions + void ReadAIP(strings path, dword index); + void LoadAipData(); + + // Server Functions + bool SendPM( CPlayer* thisclient, const char* Format, ... ); + bool SendGlobalMSG( CPlayer* thisclient, const char* Format, ... ); + //bool CWorldServer::NPCShout( CMonster* thismon, char msg[200], char npc[50] ); + bool NPCShout( int thismap, const char msg[200], const char npc[50] ); + bool NPCMessage( CMonster* thismon, const char msg[200], const char npc[50] ); + bool NPCWhisper( CPlayer* thisclient, CMonster* thismon, const char msg[200], const char npc[50] ); + bool NPCAnnounce( const char msg[200], const char npc[50] ); + UINT GetMaxPartyExp( UINT partylevel ); + bool DoSkillScript( CCharacter* character, CSkills* thisskill ); + + bool AddParty( CParty* ); + bool RemoveParty( CParty* ); + + UINT ClientIDList[0x10000]; // Clients List + UINT ClientIDIndex; // Index pointer for Client ID List + SOCKET csock; // Socket for accessing the char server + char* cct; // Encryption table for char server + + vector TeleGateList; // Telegates List + vector StatusList; // Status List + vector SkillList; // Skills List + vector MDropList; // Drops List + + map NpcNameList; //LMA: npc name map. + map STLNameList; //LMA: Name for all things taken from STL. + map STLCommentList; //LMA: Comment for all things taken from STL. + //IfoObject WarpGate; //LMA: Warp Gate for Union. + + CGrade GradeList[10]; + int maxGrades; //Nb grades (refines) + + vector SkillbookList; // Skillbook drop list + + vector NPCData; // NPC/Mobs List + //vector AIPData; // AIP filename list + vector PartyList; // Party List + vector FairyList; // Fairy List + std::map< unsigned, CNPCTrigger* > NPCActionList; + + struct UpgradeList + { + UINT BindRune_Level; + UINT Talisman_Level; + UINT BindRune_Success; + UINT Talisman_Success; + }; + + + CItemStas StatsList[500]; + UpgradeList upgrade[10]; + CEquipList EquipList[10]; + CJemList JemList; + CNaturalList NaturalList; + CPatList PatList; + CProductList ProductList; + CBreakList BreakList; + CSellList SellList; + CUseList UseList; + CMapList MapList; + + CSTBData STB_NPC; // NPC data + CSTBData STB_SKILL; // Skill data + CSTBData STB_STATUS; // Status Data + CSTBData STB_QUEST; // Quest data + CSTBData STB_ITEM[14]; // Item data + CSTBData STB_DROP; // Drop list + CSTBData STB_ZONE; // Zone data + CSTBData STB_PRODUCT; // Crafting data + CSTBData STB_BREAK; // Break data + CSTBData STB_SELL; // npc sell index + CSTBData ListGrade; // LMA: used to store the refine bonuses (% and +). + CSTBData ListUpGrade; // Bindrune and Tlisman upgrade data + + vector TriggerList; + fpQuestCond qstCondFunc[31]; + fpQuestRewd qstRewdFunc[51]; + vector AipList; + //vector AIPList; + UINT maxSpawnId; + fpAiCond aiCondFunc[35]; + fpAiAct aiActFunc[40]; + int ObjVar[2000][20]; //NPC variables used in AI + UINT ThisSession; //session ID from database + UINT ServerStartTime; //time the server was started + UINT ServerRunTime; //Hours the server has been running + CLTBstring Ltbstring[200]; + TowerDef WPList[100][100]; //Way point list for tower def. [mapid][wp id] + //int AIindex[500][6][20][1]; + + clock_t lastServerStep; // Last Update + clock_t LastUpdateTime; // Store the last update World Time + bool ServerOnline; + string filename; + + + pthread_t WorldThread[3]; + pthread_t MapThread[200]; + pthread_attr_t at; + pthread_mutex_t MapMutex,SQLMutex,PlayerMutex; + + CDatabase* DB; + + //Test thingies + unsigned ATTK_SPEED_MOD; + unsigned HIT_DELAY_MOD; + unsigned MOVE_SPEED_MOD; + +}; +extern class CWorldServer* GServer; + +PVOID VisibilityProcess( PVOID TS ); +PVOID MapProcess( PVOID TS ); +PVOID WorldProcess( PVOID TS ); +void ShutdownServer( PVOID TS ); + +void StartSignal( ); +void StopSignal( ); +void HandleSignal( int num ); +// ----------------------------------------------------------------------------------------- + +#endif diff --git a/Src/World Server/Worldprocess.cpp b/Src/World Server/Worldprocess.cpp new file mode 100644 index 0000000..499a39c --- /dev/null +++ b/Src/World Server/Worldprocess.cpp @@ -0,0 +1,165 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Give Exp the KTRose way +bool CWorldServer::GiveExp( CMonster* thismon ) +{ + //Log(MSG_DEBUG,"Awarding EXP"); + int tmpMult = 1; + if( thismon->owner != 0) // Summon + { + MapList.Index[thismon->Position->Map]->DeleteMonster( thismon ); + return true; + } + // Give Experience Drops and Quest Items + vector PartyExp; + vector PartyList; + for(UINT i=0;iPlayersDamage.size();i++) // this is the list of players who did damage to the monster who just died + { + MonsterDamage* thisplayer = thismon->PlayersDamage.at(i); + CPlayer* thisclient = GetClientByCID( thisplayer->charid, thismon->Position->Map ); + if( thisplayer->damage > 0 && thisclient != NULL ) //player did some damage. just making sure + { + //Log(MSG_DEBUG,"Player did %i damage max = %i",thisplayer->damage,thismon->Stats->MaxHP); + float MyPercent = (float)thisplayer->damage / thismon->Stats->MaxHP; //Figure out how much of the actual kill I am responsible for + if (MyPercent > 1.5) + MyPercent = 1.5; //set overkill ceiling + if( thisclient->Battle->target == thismon->clientid ) + { + ClearBattle( thisclient->Battle ) + thisclient->Position->destiny = thisclient->Position->current; + //Log(MSG_DEBUG,"(GiveExp) Destiny set to current position X: %f Y: %f.",thisclient->Position->current.x,thisclient->Position->current.y); + } + if( thismon->MonsterDrop->firsthit == thisclient->CharInfo->charid ) + { + for( int q=0;q<10;q++) + { + // Give Quest Item + if( thisclient->quest.quests[q].QuestID != 0 ) + { + Log(MSG_DEBUG,"Giving quest reward item for quest %i",thisclient->quest.quests[q].QuestID); + BEGINPACKET( pak, 0x731 ) + ADDWORD ( pak, thismon->montype ); + thisclient->client->SendPacket( &pak ); + break; + } + } + } + //assign my own exp for monsters that I personally damaged + unsigned int exp = (unsigned int)floor(thismon->thisnpc->exp * MyPercent); + //unsigned int exp = (unsigned int)ceil((double)((thismon->thisnpc->exp * thisplayer->damage) / (thismon->thisnpc->hp*thismon->thisnpc->level))); + exp = exp * Config.EXP_RATE * tmpMult; //calculate base exp for this client. No medals or stuff accounted for yet + //Log(MSG_DEBUG,"MaxXP: %i config rate: %i percent: %f Total XP: %i", thismon->thisnpc->exp, Config.EXP_RATE, MyPercent, exp); + thisclient->CharInfo->Pending_Exp += (exp * thisclient->Stats->xprate); //store exp into thisclient's pending_exp using personal xprate adjustments + + if( thisclient->Party->party != NULL ) //player is in a party so scan the party members + { + //Log(MSG_DEBUG,"Player is in a party"); + CParty* party = thisclient->Party->party; //assign a party + if(party == NULL) + return true; + //Log(MSG_DEBUG,"party found. Counted = %i", party->counted); + for(UINT p=0;pMembers.size();p++) //loop through all the members in the party + { + //Log(MSG_DEBUG,"member %i being parsed", p); + if(!party->counted) + { + //Log(MSG_DEBUG,"party added to list. level = %i", party->PartyLevel); + party->counted = true; //tag the party so we don't add it to the list twice + PartyList.push_back( party ); //we will need this list later to convert pending exp to real exp + } + CPlayer* thismember = party->Members.at(p); //find a party member + if(thismember == NULL) + return true; + float RawTmpExp = (exp * (party->PartyLevel + 25) / 50); + unsigned int tempxp =(unsigned int)(floor)(RawTmpExp); + if (tempxp < 1)tempxp = 1; + //Log(MSG_DEBUG,"member %i pending exp = %i tempxp: %i", p, thismember->CharInfo->Pending_Exp, tempxp); + thismember->CharInfo->Pending_Exp += tempxp; // add a percentage of thisclient's non-adjusted exp to all party members including himself + //Log(MSG_DEBUG,"member %i pending exp (after tempxp) = %i", p, thismember->CharInfo->Pending_Exp); + } + if(party->PartyLevel < 50) //only give party exp if party level is under 50 + { + party->Pending_Exp += exp; //add thisclient's non-adjusted xp to the pending exp of the party + } + } + else //not in a party so deal with all the exp now + { + //SendPM(thisclient, "You receive %i EXP",thisclient->CharInfo->Pending_Exp); + thisclient->CharInfo->Exp += thisclient->CharInfo->Pending_Exp; + thisclient->CharInfo->Pending_Exp = 0; + if(!thisclient->CheckPlayerLevelUP()) + { + BEGINPACKET( pak, 0x79b ); + ADDDWORD ( pak, thisclient->CharInfo->Exp ); + ADDWORD ( pak, thisclient->CharInfo->stamina ); + ADDWORD ( pak, 0 ); + thisclient->client->SendPacket( &pak ); + } + } + //Log(MSG_DEBUG,"Player rewarded %i experience after all mods",thisexp); + } + } + + //new way PY + //Log(MSG_DEBUG,"Parsing party list now"); + for(int p=0;pExp, thisparty->Pending_Exp ); + thisparty->counted = false; //reset the boolean for next time + for(UINT i=0;iMembers.size();i++) //loop through all the members in the party + { + CPlayer* thismember = thisparty->Members.at(i); //find a party member + if(thismember == NULL) + return true; + thismember->CharInfo->Exp += thismember->CharInfo->Pending_Exp; + //Log(MSG_DEBUG,"Added pending exp %i to regular exp %i for member %i", thismember->CharInfo->Pending_Exp, thismember->CharInfo->Exp, i); + thismember->CharInfo->Pending_Exp = 0; + if(!thismember->CheckPlayerLevelUP( )) + { + BEGINPACKET( pak, 0x79b ); + ADDDWORD ( pak, thismember->CharInfo->Exp ); + ADDWORD ( pak, thismember->CharInfo->stamina ); + ADDWORD ( pak, 0 ); + thismember->client->SendPacket( &pak ); + } + } + thisparty->Exp += thisparty->Pending_Exp; + thisparty->Pending_Exp = 0; + if( thisparty->Exp > GetMaxPartyExp(thisparty->PartyLevel)) //level up the party + { + thisparty->PartyLevel++; + thisparty->Exp -= GetMaxPartyExp(thisparty->PartyLevel-1); + } + BEGINPACKET( pak, 0x7d4 ); + ADDBYTE ( pak, thisparty->PartyLevel ); + ADDDWORD ( pak, thisparty->Exp ); + thisparty->SendToMembers( &pak ); + } + return true; +} diff --git a/Src/World Server/aip/AiActions.cpp b/Src/World Server/aip/AiActions.cpp new file mode 100644 index 0000000..f2e20a1 --- /dev/null +++ b/Src/World Server/aip/AiActions.cpp @@ -0,0 +1,1344 @@ +// Props to ExJam for this code :D +#include "../WorldServer.h" + +// extern CRandomMersenne rg; + +//Stop all actions +AIACT(000) +{ + //SetCMD_Stop (Stop all actions i guess?) + //entity->StopAll(); + CMonster* monster = reinterpret_cast(entity); + ClearBattle( monster->Battle ); + monster->Position->destiny = monster->Position->current; + return AI_SUCCESS; +} + +//Do Action +AIACT(001) +{ + //byte cAction; //Pos: 0x00 + //CObjMOB::Set_EMOTION (pak 781) + GETAIACTDATA(001); + BEGINPACKET( pak, 0x781 ); + ADDWORD ( pak, data->cAction ); + ADDWORD ( pak, 0); + ADDWORD ( pak, entity->clientid ); + GServer->SendToVisible(&pak, entity,NULL ); + return AI_SUCCESS; +} + +//Say LTB String +AIACT(002) +{ + //Client side say text + //Log(MSG_DEBUG, "Say LTB String"); + //dword iStrID; Pos: 0x00 + GETAIACTDATA(002); + + //Think this is crashing the server. Taken out for the time being. + //lets have the NPC also say the LTB string over the regular message system. This will show up green ^-^ + CMonster* thisMonster = reinterpret_cast(entity); + //if(thisMonster->CharType == TNPC) // check if it is an NPC and not a normal monster. + //{ + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, GServer->Ltbstring[data->iStrID].NPCname ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, GServer->Ltbstring[data->iStrID].LTBstring ); + ADDBYTE( pak, 0 ); + GServer->SendToVisible(&pak, thisMonster); + //} + return AI_SUCCESS; +} + +//Move (walk or run) to a random location within iDistance of my present location +AIACT(003) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //move randomly within iDistance use randincircle + GETAIACTDATA(003); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;// * 2;//Get it to our coord system! + entity->Position->destiny = GServer->RandInCircle(entity->Position->current, iDist); + //int randDis = rand()%(iDist << 1); + //float nX = (entity->Position->current.x + randDis) - iDist; + //entity->Position->destiny.x=nX; + //randDis = rand()%(iDist << 1); + //float nY = (entity->Position->current.y + randDis) - iDist; + //entity->Position->destiny.y = nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + entity->Position->lastMoveTime = clock(); + entity->Battle->atktype=0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, entity->Position->destiny.x*100 ); + ADDFLOAT ( pak, entity->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; + +} + +//Move (walk or run) to a random location within iDistance of my spawn location +AIACT(004) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //move randomly within iDistance of spawn position + GETAIACTDATA(004); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;//Get it to our coord system! + entity->Position->destiny = GServer->RandInCircle(entity->Position->source, iDist); + //int randDis = rand()%(iDist << 1); + //float nX = (monster->Position->source.x + randDis) - iDist; + //monster->Position->destiny.x=nX; + //randDis = rg.IRandom(0, iDist << 1); + //randDis = rand()%(iDist << 1); + //float nY = (monster->Position->source.y + randDis) - iDist; + //monster->Position->destiny.y=nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + monster->Position->lastMoveTime = clock(); + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + + return AI_SUCCESS; +} + +//Move (walk or run) to a random location within iDistance of my findChar location +AIACT(005) +{ + //byte cSpeed; //Pos: 0x00 + //move randomly within 200 of "m_pFindCHAR" + GETAIACTDATA(005); + if(entity->findChar == NULL) + return AI_FAILURE; + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + entity->findChar->UpdatePosition(); + + int iDist = 2;//Get it to our coord system! + entity->Position->destiny = GServer->RandInCircle(entity->findChar->Position->current, iDist); + + //int randDis = rand()%(iDist << 1); + //float nX = (entity->findChar->Position->current.x + randDis) - iDist; + //entity->Position->destiny.x=nX; + + + //randDis = rand()%(iDist << 1); + //float nY = (entity->findChar->Position->current.y + randDis) - iDist; + //entity->Position->destiny.y=nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + + return AI_SUCCESS; +} + +//Run and Attack aiobj within iDistance that has the lowest or greatest cAbType +AIACT(006) +{ + //dword iDistance; //Pos: 0x00 + //byte cAbType; //Pos: 0x04 + //byte cMoreLess; //Pos: 0x05 + //Run and Attack aiobj within iDistance that has the lowest or greatest cAbType + //cAbType + //0 = Level + //1 = Attack + //2 = Defense + //3 = "Res"? Magic Resistance? + //4 = HP + //5 = Charm + GETAIACTDATA(006); + + dword eCount = 0; + int highestAB = -9999999; + // CWorldEntity* highestEntity = NULL; + int lowestAB = 9999999; + // CWorldEntity* lowestEntity = NULL; + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + return AI_FAILURE; //monster has no agression value so cannot attack a player + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + CMap* map= GServer->MapList.Index[entity->Position->Map]; + CCharacter* Target = NULL; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + if(GServer->IsMonInCircle( monster->Position->current, player->Position->current, data->iDistance)) + { + switch(data->cAbType) + { + case 0: //level + if(data->cMoreLess == 0) + { + if(player->Stats->Level > lowestAB) + { + lowestAB = player->Stats->Level; + Target = player; + }else continue; + }else{ + if(player->Stats->Level < highestAB) + { + highestAB = player->Stats->Level; + Target = player; + }else continue; + } + break; + case 1: //Attack power + if(data->cMoreLess == 0) + { + if(player->Stats->Attack_Power > lowestAB) + { + lowestAB = player->Stats->Attack_Power; + Target = player; + }else continue; + }else{ + if(player->Stats->Attack_Power < highestAB) + { + highestAB = player->Stats->Attack_Power; + Target = player; + }else continue; + } + break; + case 2: //defense + if(data->cMoreLess == 0) + { + if(player->Stats->Defense> lowestAB) + { + lowestAB = player->Stats->Defense; + Target = player; + }else continue; + }else{ + if(player->Stats->Defense < highestAB) + { + highestAB = player->Stats->Defense; + Target = player; + }else continue; + } + break; + case 3: //Magic Def + if(data->cMoreLess == 0) + { + if(player->Stats->Magic_Defense> lowestAB) + { + lowestAB = player->Stats->Magic_Defense; + Target = player; + }else continue; + }else{ + if(player->Stats->Magic_Defense < highestAB) + { + highestAB = player->Stats->Magic_Defense; + Target = player; + }else continue; + } + break; + case 4: // HP + if(data->cMoreLess == 0) + { + if(player->Stats->HP> lowestAB) + { + lowestAB = player->Stats->HP; + Target = player; + }else continue; + }else{ + if(player->Stats->HP < highestAB) + { + highestAB = player->Stats->HP; + Target = player; + }else continue; + } + break; + case 5: // Charm + if(data->cMoreLess == 0) + { + if(player->Attr->Cha > lowestAB) + { + lowestAB = player->Attr->Cha; + Target = player; + }else continue; + }else{ + if(player->Attr->Cha < highestAB) + { + highestAB = player->Attr->Cha; + Target = player; + }else continue; + } + break; + default: + break; + } + } + } + // OK we found the new Target player. now attack him. + monster->Stats->stance = 1; + monster->SetStats(); + monster->StartAction( Target, NORMAL_ATTACK, 0 ); + + return AI_SUCCESS; +} + +//Special attack +AIACT(007) +{ + //CObjCHAR::Special_ATTACK + //Log(MSG_DEBUG, "AIACT(007)"); + return AI_SUCCESS; +} + +//Move (4) +AIACT(008) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //move towards target. Stop at a specified precentage away from target + //Log(MSG_DEBUG, "AIACT(008)"); + GETAIACTDATA(008); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + { + return AI_FAILURE; + } + target->UpdatePosition(); + int iDist = data->iDistance;//Get it to our coord system! + float xDist = target->Position->current.x - monster->Position->current.x; + float yDist = target->Position->current.y - monster->Position->current.y; + float fDistance = GServer->distance( entity->Position->current, target->Position->current); + float nX = monster->Position->current.x - (iDist * xDist / fDistance); + float nY = monster->Position->current.y - (iDist * yDist / fDistance); + monster->Position->destiny.x = nX; + monster->Position->destiny.y = nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + monster->Position->lastMoveTime = clock(); + ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + //entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} + +//Convert Monster to new monster (1) +AIACT(009) +{ + //word wMonster; //Pos: 0x00 + //Transform into wMonster + GETAIACTDATA(009); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); + int oldmon = monster->montype; + map->ConverToMonster(monster, data->wMonster,1); + return AI_SUCCESS; +} + +//Spawn Monster (2) +AIACT(010) +{ + //word wMonster; //Pos: 0x00 + //Spawn wMonster at current X,Y + GETAIACTDATA(010); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + fPoint position; + position = GServer->RandInCircle( entity->Position->current, 1 ); + map->AddMonster( data->wMonster, position, 0, 0, 1 ); + return AI_SUCCESS; +} + +//Call for backup +AIACT(011) +{ + //dword iDistance; //Pos: 0x00 + //dword iNumOfMonster; //Pos: 0x04 + //force iNumOfMonster of same team within iDistance to attack my target + //CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + //if(target == NULL) return AI_FAILURE; + //Log(MSG_DEBUG, "AIACT(011)"); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + GETAIACTDATA(011); + + int chrCount = 0; + dword eCount = 0; + int searchDistance = data->iDistance; + + CMap* map = GServer->MapList.Index[entity->Position->Map]; + dword entityCount = map->MonsterList.size(); + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + if(other->CharType != TMONSTER) continue; + + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + chrCount++; + other->Battle->target = entity->Battle->target; + other->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 ); + + // other->Attack(target); + + if(chrCount >= data->iNumOfMonster) return AI_SUCCESS; + } + + return AI_SUCCESS; +} + +//start attack against closest player +AIACT(012) +{ + //Log(MSG_DEBUG, "AIACT(012)"); + //Run and attack "m_pNearCHAR" Nearest Character + // entity->Attack(entity->nearChar); + // Log(MSG_DEBUG, "AIACT(012)"); + if(entity->nearChar == NULL) + { + Log(MSG_DEBUG,"No target found in entity->nearchar. Now why is that?"); + return AI_FAILURE; + } + //entity->Battle->target=entity->nearChar->clientid; + CMonster* monster = reinterpret_cast(entity); + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + monster->Stats->stance = 1; + monster->SetStats(); + monster->StartAction( (CCharacter*) entity->nearChar, NORMAL_ATTACK, 0 ); + // Log(MSG_DEBUG, "AIACT(012)%i",entity->nearChar->clientid); + return AI_SUCCESS; +} + +//run and attack findChar +AIACT(013) +{ + //Log(MSG_DEBUG, "AIACT(013)"); + //Run and attack "m_pFindCHAR" Character found from aiobj loop things + // entity->Attack(entity->findChar); + if(entity->findChar == NULL) + { + Log(MSG_DEBUG,"No target found in entity->findchar. Now why is that?"); + return AI_FAILURE; + } + //entity->Battle->target=entity->findChar->clientid; + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + return AI_FAILURE; //monster has no agression value so cannot attack a player + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + monster->Stats->stance = 1; + monster->SetStats(); + monster->Battle->target = entity->findChar->clientid; + monster->StartAction( (CCharacter*) entity->findChar, NORMAL_ATTACK, 0 ); + return AI_SUCCESS; +} + +//get help from other monsters of the same monster type +AIACT(014) +{ + //dword iDistance; //Pos: 0x00 + //find aiobj within iDistance and force them to attack my target + // CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + // if(target == NULL) return AI_FAILURE; + GETAIACTDATA(014); + CMonster* monster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + return AI_FAILURE; + int monstercount = 0; + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* othermon = map->MonsterList.at(j); + if(othermon == NULL) + continue; + if(othermon == monster) + continue; + if(othermon->montype != monster->montype) + continue; + if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance)) + { + othermon->StartAction( target, NORMAL_ATTACK, 0); + } + } + return AI_SUCCESS; +} + +//Retaliate against the last character that hit me +AIACT(015) +{ + //Run and attack "m_pDestCHAR" Blah? + //Log(MSG_DEBUG, "AIACT(015)"); + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as monster has no aggression"); + return AI_FAILURE; //monster has no agression value so cannot attack a player + } + if(monster->IsOnBattle()) + { + //already in battle so we don't want it switching all the time. maybe 75% of the time it will do nothing? + if(GServer->RandNumber(0,100) > 25) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as we got a really bad random number %i"); + return AI_FAILURE; //denied!!! + } + if(monster->Battle->hitby == monster->Battle->target) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as monster is already in combat with this player %i",monster->Battle->target); + return AI_FAILURE; //no need to initiate combat. I am already fighting this player + } + } + // otherwise change target to the person who just hit me + monster->Stats->stance = 1; + monster->SetStats(); + monster->Battle->target = monster->Battle->hitby; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + { + //Log(MSG_DEBUG, "I seem to have lost my target so I can't attack any more"); + return AI_FAILURE; + } + //Log(MSG_DEBUG, "Starting normal attack against opponent %i",target->clientid); + monster->StartAction( target, NORMAL_ATTACK, 0 ); + return AI_SUCCESS; +} + +//Run away to a point within distance iDistance of my spawn point +AIACT(016) +{ + //dword iDistance; + //Run Away! + //Log(MSG_DEBUG, "AIACT(016)"); + GETAIACTDATA(016); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;//Get it to our coord system! + entity->Position->destiny = GServer->RandInCircle(entity->Position->source, iDist); + //float radius = data->iDistance; + //float angle = rand()%(360); + // float angle = rg.IRandom(0,360); + // entity->UpdatePosition(); + // entity->MoveTo(entity->basic.pos.x + (radius * cos(angle)), entity->basic.pos.y + (radius * sin(angle))); + //float nX = (entity->Position->source.x + (radius * cos(angle))); + //entity->Position->destiny.x=nX; + //float nY = (entity->Position->source.y + (radius * cos(angle))); + //entity->Position->destiny.y=nY; + + monster->Stats->stance = 1; + monster->SetStats(); + + entity->Position->lastMoveTime = clock(); + //ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, entity->Position->destiny.x*100 ); + ADDFLOAT ( pak, entity->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, 0x01 ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} + +//Drop Item +AIACT(017) +{ + //word item0; Pos: 0x00 + //word item1; Pos: 0x02 + //word item2; Pos: 0x04 + //word item3; Pos: 0x06 + //word item4; Pos: 0x08 + //word item5; pos: 0x0a dummy as a spacer + //dword btToOwner; //Pos: 0x0c + //Random drop one of item 1-5 + GETAIACTDATA(017); + //srand(time(NULL)); + int itemRand = rand()%(5); + //int itemRand = rg.IRandom(0,360); + int nItem = data->items[itemRand]; + if (nItem == 0)return AI_FAILURE; + CMonster* monster = reinterpret_cast(entity); + //CItem dropItem; + CDrop* newdrop = new (nothrow) CDrop; + newdrop->clientid = GServer->GetNewClientID( ); + newdrop->posMap = entity->Position->Map; + newdrop->pos = GServer->RandInCircle( entity->Position->current, 3 ); + newdrop->droptime = time(NULL); + if(data->btToOwner == 1) + { + newdrop->owner = monster->MonsterDrop->firsthit; + newdrop->thisparty = monster->thisparty; + } + else + { + newdrop->owner = 0; + newdrop->thisparty = 0; + } + ClearItem(newdrop->item); + newdrop->amount = 1; + newdrop->type = 2; + newdrop->item.itemnum = nItem % 1000; + newdrop->item.itemtype = nItem / 1000; + if(newdrop->item.IsStackable()) + { + newdrop->item.count = 1; + } + else + { + newdrop->item.stats = 0; + newdrop->item.durability = 40; + newdrop->item.lifespan = 1000; + newdrop->item.socketed = 0; + newdrop->item.appraised = 1; + newdrop->item.refine = 0; + } + //Log(MSG_DEBUG, "item type= %i item num = %i", newdrop->item.itemtype, newdrop->item.itemnum); + newdrop->item.count = 1; + + + CMap* map = GServer->MapList.Index[newdrop->posMap]; + map->AddDrop( newdrop ); + // entity->DropEntityItem(&dropItem); + + return AI_SUCCESS; +} + +//Make nearby monsters attack my target +AIACT(018) +{ + //word cMonster; //Pos: 0x00 + //word wHowMany; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + //make wHowMany monsters of type cMonster within iDistance attack my target + // CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + // if(target == NULL) return AI_FAILURE; + GETAIACTDATA(018); + CMonster* monster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + return AI_FAILURE; + int monstercount = 0; + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* othermon = map->MonsterList.at(j); + if(othermon == NULL) + continue; + if(othermon == monster) + continue; + if(othermon->montype != data->cMonster) + continue; + if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance)) + { + othermon->StartAction( target, NORMAL_ATTACK, 0); + monstercount++; + if(monstercount >= data->wHowMany) + return AI_SUCCESS; + } + } + return AI_SUCCESS; +} + +//Start attack +AIACT(019) +{ + //Run and attack "m_pNearCHAR" //Identical to 012 + return F_AI_ACT_012(server, entity, raw); +} + +//Spawn Monster (5) +AIACT(020) +{ + //word cMonster; //Pos: 0x00 + //byte btPos; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + + //Spawn cMonster within iDistance of btPos + + //btPos 0 = my pos + //btPos 1 = "m_pDestCHAR" pos + //btPos 2 = targets pos + GETAIACTDATA(020); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + if(monster == NULL) return AI_FAILURE; + + fPoint position; + + if(data->btPos == 0) + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + } + else if(data->btPos == 1) + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + //dest char + } + else if(data->btPos == 2) + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + position = GServer->RandInCircle( target->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + } + //Log(MSG_DEBUG, "AIACT(020)option spawn %i distance %i monstertype %i",data->btPos,data->iDistance,data->cMonster); + return AI_SUCCESS; +} + +//Unknown +AIACT(021) +{ + //Does absolutely nothing + return AI_SUCCESS; +} + +//Unknown +AIACT(022) +{ + //Does absolutely nothing + return AI_SUCCESS; +} + +//ghostseeds and ghosts and player summons commit suicide +AIACT(023) +{ + //Commit suicide + entity->Stats->HP = -1; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, entity->clientid ); + ADDDWORD ( pak, 0x8005 ); + GServer->SendToVisible( &pak, entity ); + //Log(MSG_DEBUG, "AIACT(023) suicide monstertype %i",thisMonster->montype); + //thisMonster->DoAi(thisMonster->thisnpc->AI, 5); + //now release the id in the monsterlist + + //map->DeleteMonster( thisMonster); + + // something weird going on here. Client ids not being relinquished. Deletemonster does not help. it crashes the server. + + return AI_SUCCESS; +} + +//Do Skill +AIACT(024) +{ + //byte btTarget; //Pos: 0x00 + //word nSkill; //Pos: 0x02 + //word nMotion; //Pos: 0x04 //8 = SKILL_ATTACK 6 = magic + + //Use nSkill & nMotion? + //btTarget 0 = m_pFindCHAR + //btTarget 1 = target + //btTarget 2 = self skill + GETAIACTDATA(024); + // Log(MSG_DEBUG, "Do Skill btTarget %i nSkill %i nMotion %i",data->btTarget,data->nSkill,data->nMotion); + // Log(MSG_DEBUG, "entity %i",entity->clientid); + // Log(MSG_DEBUG, "entity->findChar %i",entity->findChar->clientid); + // Log(MSG_DEBUG, "entity->nearChar %i", entity->nearChar->clientid); + CSkills* thisskill = GServer->GetSkillByID( data->nSkill ); + if(thisskill == NULL)return AI_FAILURE; + CCharacter* monster = entity; + //if(monster->Battle->atktype != 0) + //{ + // Log(MSG_DEBUG,"Cannot apply battle attacktype. Monster already has one"); + // return AI_FAILURE; + //} + //Log(MSG_DEBUG,"AIACT(024)skill attack id: %i target: %i type: %i btTarget: %i nMotion: %i", data->nSkill, thisskill->target, thisskill->skilltype, data->btTarget, data->nMotion); + switch (thisskill->skilltype) + { + case 3: //damage action + case 6: //magic attack + { + CCharacter* target = entity->GetCharTarget( ); + //Log(MSG_DEBUG,"SKILL_ATTACK or DAMAGE_ACTION selected"); + if(target==NULL) + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + //Log(MSG_DEBUG,"SKILL_ATTACK selected. target: %i",target->clientid); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + break; + case 7: //AOE magic attack + { + CCharacter* target = entity->GetCharTarget( ); + //Log(MSG_DEBUG,"AOE_TARGET selected"); + if(target == NULL) + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + //Log(MSG_DEBUG,"AOE_TARGET selected. target: %i",target->clientid); + monster->StartAction( (CCharacter*) target, AOE_TARGET, data->nSkill ); + } + break; + case 8: //self buff + { + //Log(MSG_DEBUG,"BUFF_SELF selected."); + monster->StartAction( NULL, BUFF_SELF, data->nSkill ); + } + break; + case 9: //skill buff + case 13://skill buff + case 11: //heal + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) //some skills always return a NULL target here + { + if(entity->nearChar == NULL) // maybe there really is no possible target. + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + else //so we are going to have them select a NEW target (if possible) rather than return AI_FAILURE + { + //Log(MSG_DEBUG,"SKILL_BUFF type 13 selected. NEW target: %i",entity->nearChar->clientid); + CCharacter* newtarget = entity->nearChar; + if(newtarget == NULL) + return AI_FAILURE; + monster->StartAction( (CCharacter*) newtarget, SKILL_BUFF, data->nSkill ); + } + } + else + { + //Log(MSG_DEBUG,"SKILL_BUFF type %i selected. target: %i",thisskill->skilltype, target->clientid); + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + break; + case 10: //heal AOE centered on self + //Log(MSG_DEBUG,"BUFF_AOE selected. Skill id %i", data->nSkill); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + break; + case 17: //skill AOE centered on self + { + //Log(MSG_DEBUG,"SKILL_AOE selected. Skill id %i", data->nSkill); + monster->StartAction( NULL, SKILL_AOE, data->nSkill ); + } + break; + default: + { + //Log(MSG_WARNING,"Skill type not recognized. type: %i",thisskill->skilltype); + } + break; + } + /* + if (data->btTarget == 0) + { + //CCharacter* target = entity->GetCharTarget(); + //if(target==NULL)return AI_FAILURE; + //CCharacter* monster = entity; //defined twice???? + Log(MSG_DEBUG,"BUFF_AOE selected"); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + } + else if (data->btTarget == 1) + { + CCharacter* target = entity->GetCharTarget( ); + if(target==NULL)return AI_FAILURE; + if(data->nMotion == 8) + { + Log(MSG_DEBUG,"SKILL_ATTACK selected"); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + else + { + Log(MSG_DEBUG,"SKILL_BUFF selected"); + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + else if (data->btTarget == 2) + { + if(thisskill->target == tYourself) + { + Log(MSG_DEBUG,"BUFF_SELF selected"); + monster->StartAction( entity, BUFF_SELF, data->nSkill ); + } + else if (thisskill->target == tAlly) + { + Log(MSG_DEBUG,"BUFF_AOE selected"); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + } + else if (thisskill->target == tHostileCharacter) + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL)return AI_FAILURE; + if(data->nMotion == 8) + { + Log(MSG_DEBUG,"SKILL_ATTACK selected"); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + else + { + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + }*/ + return AI_SUCCESS; +} + +//Set Variable (1) +AIACT(025) +{ + // if(entity->_EntityType != ENTITY_NPC) return AI_FAILURE; + //byte btVarIDX Pos: 0x00 + //dword iValue Pos: 0x04 + //byte btOp Pos: 0x08 + GETAIACTDATA(025); + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return AI_FAILURE; + if(data->btVarIDX > 19) return AI_FAILURE; + short tempval = GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX]; + switch(data->btOp) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + break; + case 9: + tempval++; + break; + default: + return AI_FAILURE; + break; + } + if(tempval < 0)tempval = 0; + GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX] = tempval; + if(data->btVarIDX == 0) //changing index 0 changes event variable at client too. Possible others too but for now just 0 + { + //Log(MSG_DEBUG,"AIACT(025) eventID has changed from %i to %i",monster->thisnpc->eventid,tempval); + monster->thisnpc->eventid = tempval; + } + return AI_SUCCESS; +} + +//Set Variable (2) +AIACT(026) +{ + //Set WorldVAR + //word nVarNo; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + GETAIACTDATA(026); + //PY: we don't know how to use worldVar so here are some fun things we can do with custom AI + switch(data->nVarNo) + { + case 1: //change world exp rate in the config + GServer->Config.EXP_RATE = data->iValue; + return AI_SUCCESS; + break; + default: + return AI_SUCCESS; + break; + } +} + +//Set Economy Variable. Could have some fun with tis in the future +AIACT(027) +{ + //Set EconomyVAR + //word nVarNo; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + return AI_SUCCESS; +} + +//Shout/Ann LTB String +AIACT(028) +{ + GETAIACTDATA(028); + //byte btMsgType Pos: 0x00 + //dword iStrID Pos: 0x04 + dword npcId = 0; + //if(entity->_EntityType == ENTITY_NPC) + CMonster* thisMonster = reinterpret_cast(entity); + switch(data->btMsgType) + { + case 0: //current field + // don't think this is possible. No way to get a character to send it to?? + break; + case 1: //shout to map + //Log(MSG_INFO,"AI -- NPC Shouts to map %i",entity->Position->Map); + GServer->NPCShout(entity->Position->Map,GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname); + break; + case 2: //announce to server. + GServer->NPCAnnounce(GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname); + break; + default: + break; + } + + //npcId = reinterpret_cast(entity)->NpcID; + // }else if(entity->_EntityType == ENTITY_MONSTER){ + // npcId = reinterpret_cast(entity)->MonID; + // }else{ + // return AI_FAILURE; + // } + // std::map::iterator triggerITR = server->NpcNames.find(npcId); + // if(triggerITR == server->NpcNames.end()) return AI_FAILURE; + + // char* npcName = triggerITR->second; + // char* sayStr = server->lngAi->Data(data->iStrID, 1); + // if(sayStr == NULL) return AI_FAILURE; + // if(data->btMsgType == 0){ + //Chat + // CPacket pakout(0x783); + // pakout.Add(entity->_ClientID); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // }else if(data->btMsgType == 1){ + //Shout + // CPacket pakout(0x785); + // pakout.Add(npcName); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // }else if(data->btMsgType == 2){ + //Announce + // CPacket pakout(0x702); + // pakout.AddBytes((byte*)npcName, strlen(npcName)); + // pakout.Add('>'); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // } + // delete [] sayStr; + //Log(MSG_DEBUG, "AIACT(028)"); + return AI_SUCCESS; +} + +//Move to my owner's location +AIACT(029) +{ + //Move within 20% of the distance of my "CALLER" + GETAIACTDATA(029); + // if(entity->_EntityType != ENTITY_MONSTER) return AI_FAILURE; + // CMonster* thisMonster = reinterpret_cast(entity); + // CWorldEntity* caller = thisMonster->thisZone->GetEntity(thisMonster->_CallerID); + // if(caller == NULL) return AI_FAILURE; + // caller->UpdatePosition(); + // thisMonster->UpdatePosition(); + if(!entity->IsMonster( )) return AI_FAILURE; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL) return AI_FAILURE; + caller->UpdatePosition(); + thisMonster->UpdatePosition(); + thisMonster->Stats->stance = 1; + thisMonster->SetStats(); + thisMonster->Position->source = caller->Position->current; + int iDist = GServer->RandNumber(5, 20); //move to between 5% and 20% of owners position + float xDist = thisMonster->Position->current.x - thisMonster->Position->source.x; + float yDist = thisMonster->Position->current.y - thisMonster->Position->source.y; + float nX = thisMonster->Position->source.x + (xDist * iDist / 100); + float nY = thisMonster->Position->source.y + (yDist * iDist / 100); + thisMonster->Position->destiny.x = nX; + thisMonster->Position->destiny.y = nY; + thisMonster->Position->lastMoveTime = clock(); + ClearBattle( thisMonster->Battle );// this also seems to clear the attackers battle?? + //entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, thisMonster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, thisMonster->Position->destiny.x*100 ); + ADDFLOAT ( pak, thisMonster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, thisMonster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + + return AI_SUCCESS; +} + +//Do Trigger +AIACT(030) +{ + //word lenszTrigger; //Pos: 0x00 + //char* szTrigger; //Pos: 0x02 + GETAIACTDATA(030); + char* tempName = reinterpret_cast(&data->szTrigger) - 2; + dword hash = MakeStrHash(tempName); + //Log(MSG_INFO, "Execute Quest Trigger %s[%d] [%08x]", tempName, data->lenszTrigger, hash); + //make a dummy player from this monster + CClientSocket* thisclient = new CClientSocket; + CPlayer* player = new CPlayer( thisclient ); + if(player == NULL) + { + Log(MSG_INFO, "Player not found in AIACT 30"); + return AI_FAILURE; + } + thisclient->player = (void*) player; + player->Position = entity->Position; + + int quest = player->ExecuteQuestTrigger(hash); + if(quest == QUEST_SUCCESS) + return AI_SUCCESS; + return AI_FAILURE; +} + +//set monster's attack target to the same as the owner +AIACT(031) +{ + //Log(MSG_DEBUG, "AIACT(031)"); + //Attack my "CALLER"(s target) if he is not an ally (must be caller's target...) + if(!entity->IsMonster( )) return AI_FAILURE; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL) return AI_FAILURE; + thisMonster->Position->source = caller->Position->current; + CCharacter* target = map->GetCharInMap(caller->Battle->target); + if(target == NULL) return AI_FAILURE; + // need to prevent the summon from attacking other summons of the same player + CMonster* otherMonster = reinterpret_cast(target); + if(otherMonster->owner == thisMonster->owner) return AI_FAILURE; + thisMonster->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 ); + //Log(MSG_DEBUG, "AIACT(031) summon attacking player's target"); + + return AI_SUCCESS; +} + +//Set PK flag for specified zone +AIACT(032) +{ + //word nZoneNo; //Pos: 0x00 + //byte btOnOff; //Pos: 0x02 + //Set PK Flag (btOnOff) in nZoneNo + GETAIACTDATA(032); + //Log(MSG_DEBUG, "AIACT(032) zone (1)"); + return AI_SUCCESS; +} + +//Set regen mode for specified zone +AIACT(033) +{ + //word nZoneNo //if 0, current map + //word nNewValue + //if nNewValue == 2 -> toggle + //else set regen system nNewValue (0 = off, 1 = on) + GETAIACTDATA(033); + //Log(MSG_DEBUG, "AIACT(033) zone (2)"); + return AI_SUCCESS; +} + +//Item (?) +AIACT(034) +{ + //word nItemNum; //Pos: 0x00 + //word nCount; //Pos: 0x02 + //Give item to "CALLER" + GETAIACTDATA(034); + if(!entity->IsMonster( )) return AI_SUCCESS; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CPlayer* caller = map->GetPlayerInMap( thisMonster->owner); + if(caller == NULL) return AI_SUCCESS; + int nItem = data->nItemNum; + if (nItem == 0)return AI_SUCCESS; + char itemtypes[15] = {4,0,0,0,0,0,0,0,0,0,1,2,2,4,3}; + CItem thisitem; + thisitem.Clear(); + thisitem.itemtype = nItem / 1000; + thisitem.itemnum = nItem % 1000; + thisitem.count = data->nCount; + if( itemtypes[thisitem.itemtype] == 0 ) + { + thisitem.appraised = true; + thisitem.lifespan = 100; + thisitem.gem = 0; + thisitem.durability = GServer->STB_ITEM[thisitem.itemtype-1].rows[thisitem.itemnum][29]; + } + int thisslot = caller->AddItem( thisitem ); + //Log(MSG_DEBUG,"Item type = %i item ID = %i Count = %i Slot = %i",thisitem.itemtype,thisitem.itemnum,thisitem.count,thisslot); + if (thisslot == 0xffff) // Fail + { + BEGINPACKET( pak, 0x7a7); + ADDWORD(pak, 0x00); + ADDBYTE(pak, 0x03); + ADDBYTE(pak, 0x00); + caller->client->SendPacket(&pak); + return AI_SUCCESS; + } + else // Success + { + BEGINPACKET( pak, 0x71f); + ADDBYTE(pak, 0x01); + ADDBYTE(pak, thisslot); + ADDWORD(pak, caller->items[thisslot].GetPakHeader( ) ); + ADDDWORD(pak, caller->items[thisslot].GetPakData( ) ); + caller->client->SendPacket(&pak); + } + //Log(MSG_DEBUG, "AIACT(034) Item (?)"); + return AI_SUCCESS; +} + +//Set Variable (4) +AIACT(035) +{ + //Set AiVAR + //word nVarIDX; Pos: 0x00 + //dword iValue; Pos: 0x04 + //byte btOp; Pos: 0x08 + GETAIACTDATA(035); + CMonster* thisMonster = reinterpret_cast(entity); + if(data->nVarIDX >19)return AI_SUCCESS; + int tempval = thisMonster->AIVar[data->nVarIDX]; + switch(data->btOp) + { + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + if(tempval <= 0) + { + thisMonster->AIVar[data->nVarIDX] = 0; + return AI_SUCCESS; + } + break; + case 9: + tempval++; + break; + default: + return AI_SUCCESS; + break; + } + thisMonster->AIVar[data->nVarIDX] = tempval; + //thisMonster->thisnpc->Objvar->SetVar(data->btOp, data->iValue); //code not working + return AI_SUCCESS; +} + +//Monster (1) +AIACT(036) +{ + //word nMonster; //Pos: 0x00 + //byte btMaster; //Pos: 0x02 + //Spawn nMonster at my position (has btMaster)? + GETAIACTDATA(036); + // CWorldEntity* master = NULL; + // if(data->btMaster) master = entity; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + CMap* map = GServer->MapList.Index[entity->Position->Map]; + int owner = 0; + if(data->btMaster == 1) + owner = monster->clientid; + map->AddMonster( data->nMonster, entity->Position->current, 0, monster->Status->spawnid, 10 ); + //Log(MSG_DEBUG, "AIACT(36) monster (1) no code here"); + return AI_SUCCESS; +} + +//Monster (2) +AIACT(037) +{ + //word nMonster; //Pos: 0x00 + //word nPos; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + //byte btMaster; //Pos: 0x08 + + //Spawn nMonster at nPos within iDistance (has btMaster)? + //nPos = 0 my pos + //nPos = 1 m_pDestCHAR pos + //nPos = 2 target pos + GETAIACTDATA(037); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + if(monster == NULL) return AI_FAILURE; + int owner = 0; + if(data->btMaster == 1) + owner = monster->clientid; + fPoint position; + + if(data->nPos == 0) //spawn monster in a circle with radius iDistance around my current position + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + } + else if(data->nPos == 1) //spawn monster in a circle with radius iDistance around my destiny position + { + position = GServer->RandInCircle( entity->Position->destiny, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + //dest char + } + else if(data->nPos == 2) //spawn monster in a circle with radius iDistance around my target's current position + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + position = GServer->RandInCircle( target->Position->current, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + } + return AI_SUCCESS; +} + +//TDEF code. move to next waypoint +AIACT(038) +{ + //byte cSpeed pos 0x00 + GETAIACTDATA(038); + CMonster* monster = reinterpret_cast(entity); + monster->NextWayPoint ++; + monster->Position->destiny.x = GServer->WPList[monster->Position->Map][monster->NextWayPoint].X; + monster->Position->destiny.y = GServer->WPList[monster->Position->Map][monster->NextWayPoint].Y; + monster->Stats->stance = data->cSpeed; + entity->Position->lastMoveTime = clock(); + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} diff --git a/Src/World Server/aip/AiActions.cpp.bak b/Src/World Server/aip/AiActions.cpp.bak new file mode 100644 index 0000000..556660d --- /dev/null +++ b/Src/World Server/aip/AiActions.cpp.bak @@ -0,0 +1,1472 @@ +// Props to ExJam for this code :D +#include "../WorldServer.h" + +// extern CRandomMersenne rg; + +//Stop all actions +AIACT(000) +{ + //SetCMD_Stop (Stop all actions i guess?) + //entity->StopAll(); + CMonster* monster = reinterpret_cast(entity); + ClearBattle( monster->Battle ); + monster->Position->destiny = monster->Position->current; + return AI_SUCCESS; +} + +//Do Action +AIACT(001) +{ + //byte cAction; //Pos: 0x00 + //CObjMOB::Set_EMOTION (pak 781) + GETAIACTDATA(001); + BEGINPACKET( pak, 0x781 ); + ADDWORD ( pak, data->cAction ); + ADDWORD ( pak, 0); + ADDWORD ( pak, entity->clientid ); + GServer->SendToVisible(&pak, entity,NULL ); + return AI_SUCCESS; +} + +//Say LTB String +AIACT(002) +{ + //Client side say text + //Log(MSG_DEBUG, "Say LTB String"); + //dword iStrID; Pos: 0x00 + GETAIACTDATA(002); + + //Think this is crashing the server. Taken out for the time being. + //lets have the NPC also say the LTB string over the regular message system. This will show up green ^-^ + //CMonster* thisMonster = reinterpret_cast(entity); + //if(thisMonster->CharType == TNPC) // check if it is an NPC and not a normal monster. + //{ + // BEGINPACKET( pak, 0x0784 ); + // ADDSTRING( pak, GServer->Ltbstring[data->iStrID].NPCname ); + // ADDBYTE( pak, 0 ); + // ADDSTRING( pak, GServer->Ltbstring[data->iStrID].LTBstring ); + // ADDBYTE( pak, 0 ); + // GServer->SendToVisible(&pak, thisMonster); + //} + return AI_SUCCESS; +} + +//Move (1) +AIACT(003) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //move randomly within iDistance (individual x and y so its within a square) + GETAIACTDATA(003); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;// * 2;//Get it to our coord system! + //srand(time(NULL)); + int randDis = rand()%(iDist << 1); + float nX = (entity->Position->current.x + randDis) - iDist; + entity->Position->destiny.x=nX; + //srand(time(NULL)); + randDis = rand()%(iDist << 1); + float nY = (entity->Position->current.y + randDis) - iDist; + entity->Position->destiny.y = nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + entity->Position->lastMoveTime = clock(); + //ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + entity->Battle->atktype=0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, entity->Position->destiny.x*100 ); + ADDFLOAT ( pak, entity->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + //Log(MSG_DEBUG, "move1 stance %i",data->cSpeed); + //Log(MSG_DEBUG, "move(1)"); + return AI_SUCCESS; +} + +//Move (2) +AIACT(004) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //move randomly within iDistance of spawn position (square) Position->source + GETAIACTDATA(004); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;//Get it to our coord system! + int randDis = rand()%(iDist << 1); + float nX = (monster->Position->source.x + randDis) - iDist; + monster->Position->destiny.x=nX; + //randDis = rg.IRandom(0, iDist << 1); + randDis = rand()%(iDist << 1); + float nY = (monster->Position->source.y + randDis) - iDist; + monster->Position->destiny.y=nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + monster->Position->lastMoveTime = clock(); + //ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + //Log(MSG_DEBUG, "move2 stance %i",data->cSpeed); + + return AI_SUCCESS; +} + +//Move (3) +AIACT(005) +{ + //byte cSpeed; //Pos: 0x00 + //move randomly within 200 of "m_pFindCHAR" (square) + GETAIACTDATA(005); + if(entity->findChar == NULL) + return AI_FAILURE; + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + entity->findChar->UpdatePosition(); + + int iDist = 2;//Get it to our coord system! + + + int randDis = rand()%(iDist << 1); + float nX = (entity->findChar->Position->current.x + randDis) - iDist; + entity->Position->destiny.x=nX; + + + randDis = rand()%(iDist << 1); + float nY = (entity->findChar->Position->current.y + randDis) - iDist; + entity->Position->destiny.y=nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + //entity->MoveTo(nX, nY); + //entity->Position->lastMoveTime = clock(); + //ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + // Log(MSG_DEBUG, "move3 stance %i",data->cSpeed); + // Log(MSG_DEBUG, "move (3)"); + // RESETPACKET(pak, 0x783); + // ADDWORD(pak,entity->findChar->clientid);//entity->clientid); + // ADDSTRING(pak,"hello there"); + // ADDBYTE(pak,0); + // GServer->SendToVisible(&pak, entity); + // server->SendPacketToZone(entity, &pakout); + + return AI_SUCCESS; +} + +//Move (?) +AIACT(006) +{ + //dword iDistance; //Pos: 0x00 + //byte cAbType; //Pos: 0x04 + //byte cMoreLess; //Pos: 0x05 + //Run and Attack aiobj within iDistance that has the lowest or greatest cAbType + //cAbType + //0 = Level + //1 = Attack + //2 = Defense + //3 = "Res"? Magic Resistance? + //4 = HP + //5 = Charm + GETAIACTDATA(006); + + dword eCount = 0; + int highestAB = -9999999; + // CWorldEntity* highestEntity = NULL; + int lowestAB = 9999999; + // CWorldEntity* lowestEntity = NULL; + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + return AI_FAILURE; //monster has no agression value so cannot attack a player + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + CMap* map= GServer->MapList.Index[entity->Position->Map]; + CCharacter* Target = NULL; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + if(GServer->IsMonInCircle( monster->Position->current, player->Position->current, data->iDistance)) + { + switch(data->cAbType) + { + case 0: //level + if(data->cMoreLess == 0) + { + if(player->Stats->Level > lowestAB) + { + lowestAB = player->Stats->Level; + Target = player; + }else continue; + }else{ + if(player->Stats->Level < highestAB) + { + highestAB = player->Stats->Level; + Target = player; + }else continue; + } + break; + case 1: //Attack power + if(data->cMoreLess == 0) + { + if(player->Stats->Attack_Power > lowestAB) + { + lowestAB = player->Stats->Attack_Power; + Target = player; + }else continue; + }else{ + if(player->Stats->Attack_Power < highestAB) + { + highestAB = player->Stats->Attack_Power; + Target = player; + }else continue; + } + break; + case 2: //defense + if(data->cMoreLess == 0) + { + if(player->Stats->Defense> lowestAB) + { + lowestAB = player->Stats->Defense; + Target = player; + }else continue; + }else{ + if(player->Stats->Defense < highestAB) + { + highestAB = player->Stats->Defense; + Target = player; + }else continue; + } + break; + case 3: //Magic Def + if(data->cMoreLess == 0) + { + if(player->Stats->Magic_Defense> lowestAB) + { + lowestAB = player->Stats->Magic_Defense; + Target = player; + }else continue; + }else{ + if(player->Stats->Magic_Defense < highestAB) + { + highestAB = player->Stats->Magic_Defense; + Target = player; + }else continue; + } + break; + case 4: // HP + if(data->cMoreLess == 0) + { + if(player->Stats->HP> lowestAB) + { + lowestAB = player->Stats->HP; + Target = player; + }else continue; + }else{ + if(player->Stats->HP < highestAB) + { + highestAB = player->Stats->HP; + Target = player; + }else continue; + } + break; + case 5: // Charm + if(data->cMoreLess == 0) + { + if(player->Attr->Cha > lowestAB) + { + lowestAB = player->Attr->Cha; + Target = player; + }else continue; + }else{ + if(player->Attr->Cha < highestAB) + { + highestAB = player->Attr->Cha; + Target = player; + }else continue; + } + break; + default: + break; + } + } + } + // OK we found the new Target player. now attack him. + monster->Stats->stance = 1; + monster->SetStats(); + monster->StartAction( Target, NORMAL_ATTACK, 0 ); + + // int searchDistance = data->iDistance * 100; + + // CWorldEntity** entityList = entity->thisZone->GetEntityList(); + // dword entityCount = entity->thisZone->GetEntityCount(); + // for(dword i = 1; i < MAX_ZONE_CLIENTID; i++){ + // CWorldEntity* other = entityList[i]; + // if(eCount >= entityCount) break; + // if(other == NULL) continue; + // eCount++; + // if(other == entity) continue; + // if(other->_EntityType == ENTITY_NO_TYPE) continue; + // if(other->_EntityType == ENTITY_DROP) continue; + + // int dX = (int)abs((int)other->curBlock.x - (int)entity->curBlock.x); + // if(dX > 1) continue; + // int dY = (int)abs((int)other->curBlock.y - (int)entity->curBlock.y); + // if(dY > 1) continue; + + // int iDistance = other->basic.pos.distance(entity->basic.pos); + // if(iDistance > searchDistance) continue; + + // int value = AI_GetAbility(other, data->cAbType); + + // if(value < lowestAB){ + // lowestAB = value; + // lowestEntity = other; + // } + // if(value > highestAB){ + // highestAB = value; + // highestEntity = other; + // } + // } + // if(data->cMoreLess){ + // if(lowestEntity){ + //SetCmdRUN_nATTACK + // entity->Attack(lowestEntity); + // } + // }else{ + // if(highestEntity){ + //SetCmdRUN_nATTACK + // entity->Attack(highestEntity); + // } + // } + //Log(MSG_DEBUG, "move (?)"); + return AI_SUCCESS; +} + +//Unknown +AIACT(007) +{ + //CObjCHAR::Special_ATTACK + //Log(MSG_DEBUG, "AIACT(007)"); + return AI_SUCCESS; +} + +//Move (4) +AIACT(008) +{ + //dword iDistance; //Pos: 0x00 + //byte cSpeed; //Pos: 0x04 + //Some weird shit going on here + //move within some sort of distance of target (i think possibly % iDistance of distance between target)(run away?) + //Log(MSG_DEBUG, "AIACT(008)"); + GETAIACTDATA(008); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + int iDist = data->iDistance;//Get it to our coord system! + float xDist = monster->Position->current.x - monster->Position->source.x; + float yDist = monster->Position->current.y - monster->Position->source.y; + float nX = monster->Position->source.x + (xDist * iDist / 100); + float nY = monster->Position->source.y + (yDist * iDist / 100); + monster->Position->destiny.x = nX; + monster->Position->destiny.y = nY; + monster->Stats->stance = data->cSpeed; + monster->SetStats(); + monster->Position->lastMoveTime = clock(); + ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + //entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, monster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} + +//Convert Monster to new monster (1) +AIACT(009) +{ + //word wMonster; //Pos: 0x00 + //Transform into wMonster + GETAIACTDATA(009); + CMap* map= GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); + int oldmon = monster->montype; + map->ConverToMonster(monster,data->wMonster,1); + //Log(MSG_DEBUG, "AIACT(009)convert monster type %i to %i",oldmon,data->wMonster); + return AI_SUCCESS; +} + +//Spawn Monster (2) +AIACT(010) +{ + //word wMonster; //Pos: 0x00 + //Spawn wMonster at current X,Y + GETAIACTDATA(010); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + fPoint position; + //CMonster* newmonster; + position = GServer->RandInCircle( entity->Position->current, 1 ); + map->AddMonster( data->wMonster, position, 0, 0, 1 ); + // entity->thisZone->SpawnMonster(data->wMonster, entity->basic.pos, entity->basic.map); + //Log(MSG_DEBUG, "AIACT(010)spawn %i",data->wMonster); + return AI_SUCCESS; +} + +//Call for backup +AIACT(011) +{ + //dword iDistance; //Pos: 0x00 + //dword iNumOfMonster; //Pos: 0x04 + //force iNumOfMonster of same team within iDistance to attack my target + //CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + //if(target == NULL) return AI_FAILURE; + //Log(MSG_DEBUG, "AIACT(011)"); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + GETAIACTDATA(011); + + int chrCount = 0; + dword eCount = 0; + int searchDistance = data->iDistance; + + CMap* map = GServer->MapList.Index[entity->Position->Map]; + dword entityCount = map->MonsterList.size(); + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + if(other->CharType != TMONSTER) continue; + + // CWorldEntity** entityList = entity->thisZone->GetEntityList(); + // dword entityCount = entity->thisZone->GetEntityCount(); + // for(dword i = 1; i < MAX_ZONE_CLIENTID; i++){ + // CWorldEntity* other = entityList[i]; + // if(eCount >= entityCount) break; + // if(other == NULL) continue; + // eCount++; + // if(other == entity) continue; + // if(other->_EntityType == ENTITY_MONSTER) continue; + // if(other->team != entity->team) continue; + // int dX = (int)abs((int)other->curBlock.x - (int)entity->curBlock.x); + // if(dX > 1) continue; + // int dY = (int)abs((int)other->curBlock.y - (int)entity->curBlock.y); + // if(dY > 1) continue; + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + // int iDistance = other->basic.pos.distance(entity->basic.pos); + if(iDistance > searchDistance) continue; + chrCount++; + other->Battle->target = entity->Battle->target; + other->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 ); + + // other->Attack(target); + + if(chrCount >= data->iNumOfMonster) return AI_SUCCESS; + } + + return AI_SUCCESS; +} + +//start attack +AIACT(012) +{ + //Log(MSG_DEBUG, "AIACT(012)"); + //Run and attack "m_pNearCHAR" Nearest Character + // entity->Attack(entity->nearChar); + // Log(MSG_DEBUG, "AIACT(012)"); + if(entity->nearChar == NULL) + { + Log(MSG_DEBUG,"No target found in entity->nearchar. Now why is that?"); + return AI_FAILURE; + } + //entity->Battle->target=entity->nearChar->clientid; + CMonster* monster = reinterpret_cast(entity); + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + monster->Stats->stance = 1; + monster->SetStats(); + monster->StartAction( (CCharacter*) entity->nearChar, NORMAL_ATTACK, 0 ); + // Log(MSG_DEBUG, "AIACT(012)%i",entity->nearChar->clientid); + return AI_SUCCESS; +} + +//start attack +AIACT(013) +{ + //Log(MSG_DEBUG, "AIACT(013)"); + //Run and attack "m_pFindCHAR" Character found from aiobj loop things + // entity->Attack(entity->findChar); + if(entity->findChar == NULL) + { + Log(MSG_DEBUG,"No target found in entity->findchar. Now why is that?"); + return AI_FAILURE; + } + //entity->Battle->target=entity->findChar->clientid; + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + return AI_FAILURE; //monster has no agression value so cannot attack a player + int aggro = GServer->RandNumber(1,20); + if(aggro > monster->thisnpc->aggresive) + return AI_FAILURE; // failed aggresiveness check + monster->Stats->stance = 1; + monster->SetStats(); + monster->Battle->target = entity->findChar->clientid; + monster->StartAction( (CCharacter*) entity->findChar, NORMAL_ATTACK, 0 ); + //Log(MSG_DEBUG, "AIACT(013) Monster type %i initiated combat with player %i",monster->montype, entity->findChar->clientid); + return AI_SUCCESS; +} + +//get help from other monsters +AIACT(014) +{ + //dword iDistance; //Pos: 0x00 + //find aiobj within iDistance and force them to attack my target + // CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + // if(target == NULL) return AI_FAILURE; + GETAIACTDATA(014); + CMonster* monster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + return AI_FAILURE; + int monstercount = 0; + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* othermon = map->MonsterList.at(j); + if(othermon == NULL) + continue; + if(othermon == monster) + continue; + if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance)) + { + othermon->StartAction( target, NORMAL_ATTACK, 0); + } + } + // dword eCount = 0; + // int searchDistance = data->iDistance * 100; + + // CWorldEntity** entityList = entity->thisZone->GetEntityList(); + // dword entityCount = entity->thisZone->GetEntityCount(); + // for(dword i = 1; i < MAX_ZONE_CLIENTID; i++){ + // CWorldEntity* other = entityList[i]; + // if(eCount >= entityCount) break; + // if(other == NULL) continue; + // eCount++; + // if(other == entity) continue; + // if(other->_EntityType == ENTITY_NO_TYPE) continue; + // if(other->_EntityType == ENTITY_DROP) continue; + // if(other->team != entity->team) continue; + + // int dX = (int)abs((int)other->curBlock.x - (int)entity->curBlock.x); + // if(dX > 1) continue; + // int dY = (int)abs((int)other->curBlock.y - (int)entity->curBlock.y); + // if(dY > 1) continue; + + // int iDistance = other->basic.pos.distance(entity->basic.pos); + // if(iDistance > searchDistance) continue; + + // other->Attack(target); + // return AI_SUCCESS; + // } + //Log(MSG_DEBUG, "AIACT(014)"); + return AI_SUCCESS; +} + +//Retaliate against the last character that hit me +AIACT(015) +{ + //Run and attack "m_pDestCHAR" Blah? + //Log(MSG_DEBUG, "AIACT(015)"); + CMonster* monster = reinterpret_cast(entity); + if(monster->thisnpc->aggresive == 0) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as monster has no aggression"); + return AI_FAILURE; //monster has no agression value so cannot attack a player + } + if(monster->IsOnBattle()) + { + //already in battle so we don't want it switching all the time. maybe 75% of the time it will do nothing? + if(GServer->RandNumber(0,100) > 25) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as we got a really bad random number %i"); + return AI_FAILURE; //denied!!! + } + if(monster->Battle->hitby == monster->Battle->target) + { + //Log(MSG_DEBUG, "AIACT(015) returned false as monster is already in combat with this player %i",monster->Battle->target); + return AI_FAILURE; //no need to initiate combat. I am already fighting this player + } + } + // otherwise change target to the person who just hit me + monster->Stats->stance = 1; + monster->SetStats(); + monster->Battle->target = monster->Battle->hitby; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + { + //Log(MSG_DEBUG, "I seem to have lost my target so I can't attack any more"); + return AI_FAILURE; + } + //Log(MSG_DEBUG, "Starting normal attack against opponent %i",target->clientid); + monster->StartAction( target, NORMAL_ATTACK, 0 ); + return AI_SUCCESS; +} + +//Run away +AIACT(016) +{ + //dword iDistance; + //Run Away! + //Log(MSG_DEBUG, "AIACT(016)"); + GETAIACTDATA(016); + CMonster* monster = reinterpret_cast(entity); + monster->UpdatePosition( ); + + float radius = data->iDistance; + float angle = rand()%(360); + // float angle = rg.IRandom(0,360); + // entity->UpdatePosition(); + // entity->MoveTo(entity->basic.pos.x + (radius * cos(angle)), entity->basic.pos.y + (radius * sin(angle))); + float nX = (entity->Position->source.x + (radius * cos(angle))); + entity->Position->destiny.x=nX; + float nY = (entity->Position->source.y + (radius * cos(angle))); + entity->Position->destiny.y=nY; + + monster->Stats->stance = 1; + monster->SetStats(); + + entity->Position->lastMoveTime = clock(); + //ClearBattle( entity->Battle );// this also seems to clear the attackers battle?? + entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, entity->Position->destiny.x*100 ); + ADDFLOAT ( pak, entity->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, 0x01 ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} + +//Drop Item +AIACT(017) +{ + //word item0; Pos: 0x00 + //word item1; Pos: 0x02 + //word item2; Pos: 0x04 + //word item3; Pos: 0x06 + //word item4; Pos: 0x08 + //word item5; pos: 0x0a dummy as a spacer + //dword btToOwner; //Pos: 0x0c + //Random drop one of item 1-5 + GETAIACTDATA(017); + //srand(time(NULL)); + int itemRand = rand()%(5); + //int itemRand = rg.IRandom(0,360); + int nItem = data->items[itemRand]; + if (nItem == 0)return AI_FAILURE; + CMonster* monster = reinterpret_cast(entity); + //CItem dropItem; + CDrop* newdrop = new (nothrow) CDrop; + newdrop->clientid = GServer->GetNewClientID( ); + newdrop->posMap = entity->Position->Map; + newdrop->pos = GServer->RandInCircle( entity->Position->current, 3 ); + newdrop->droptime = time(NULL); + if(data->btToOwner == 1) + { + newdrop->owner = monster->MonsterDrop->firsthit; + newdrop->thisparty = monster->thisparty; + } + else + { + newdrop->owner = 0; + newdrop->thisparty = 0; + } + ClearItem(newdrop->item); + newdrop->amount = 1; + newdrop->type = 2; + newdrop->item.itemnum = nItem % 1000; + newdrop->item.itemtype = nItem / 1000; + if(newdrop->item.IsStackable()) + { + newdrop->item.count = 1; + } + else + { + newdrop->item.stats = 0; + newdrop->item.durability = 40; + newdrop->item.lifespan = 1000; + newdrop->item.socketed = 0; + newdrop->item.appraised = 1; + newdrop->item.refine = 0; + } + //Log(MSG_DEBUG, "item type= %i item num = %i", newdrop->item.itemtype, newdrop->item.itemnum); + newdrop->item.count = 1; + + + CMap* map = GServer->MapList.Index[newdrop->posMap]; + map->AddDrop( newdrop ); + // entity->DropEntityItem(&dropItem); + + return AI_SUCCESS; +} + +//Make nearby monsters attack my target +AIACT(018) +{ + //word cMonster; //Pos: 0x00 + //word wHowMany; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + //make wHowMany monsters of type cMonster within iDistance attack my target + // CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + // if(target == NULL) return AI_FAILURE; + GETAIACTDATA(018); + CMonster* monster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + return AI_FAILURE; + int monstercount = 0; + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* othermon = map->MonsterList.at(j); + if(othermon == NULL) + continue; + if(othermon == monster) + continue; + if(othermon->montype != data->cMonster) + continue; + if(GServer->IsMonInCircle( monster->Position->current, othermon->Position->current, data->iDistance)) + { + othermon->StartAction( target, NORMAL_ATTACK, 0); + monstercount++; + if(monstercount >= data->wHowMany) + return AI_SUCCESS; + } + } + // int chrCount = 0; + // dword eCount = 0; + // int nearestDistance = 9999999; + // int searchDistance = data->iDistance * 100; + + // CWorldEntity** entityList = entity->thisZone->GetEntityList(); + // dword entityCount = entity->thisZone->GetEntityCount(); + // for(dword i = 1; i < MAX_ZONE_CLIENTID; i++){ + // CWorldEntity* other = entityList[i]; + // if(eCount >= entityCount) break; + // if(other == NULL) continue; + // eCount++; + // if(other == entity) continue; + // if(other->_EntityType != ENTITY_MONSTER) continue; + // if(other->team != entity->team) continue; + // if(reinterpret_cast(other)->MonID != data->cMonster) continue; + + // int dX = (int)abs((int)other->curBlock.x - (int)entity->curBlock.x); + // if(dX > 1) continue; + // int dY = (int)abs((int)other->curBlock.y - (int)entity->curBlock.y); + // if(dY > 1) continue; + + // int iDistance = other->basic.pos.distance(entity->basic.pos); + // if(iDistance > searchDistance) continue; + // chrCount++; + + //Run and Attack target + // other->Attack(target); + + // if(chrCount >= data->wHowMany) return AI_SUCCESS; + // } + //Log(MSG_DEBUG, "AIACT(018)"); + return AI_SUCCESS; +} + +//Start attack +AIACT(019) +{ + //Run and attack "m_pNearCHAR" //Identical to 012 + return F_AI_ACT_012(server, entity, raw); +} + +//Spawn Monster (5) +AIACT(020) +{ + //word cMonster; //Pos: 0x00 + //byte btPos; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + + //Spawn cMonster within iDistance of btPos + + //btPos 0 = my pos + //btPos 1 = "m_pDestCHAR" pos + //btPos 2 = targets pos + GETAIACTDATA(020); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + if(monster == NULL) return AI_FAILURE; + + fPoint position; + + if(data->btPos == 0) + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + } + else if(data->btPos == 1) + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + //dest char + } + else if(data->btPos == 2) + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + position = GServer->RandInCircle( target->Position->current, data->iDistance ); + map->AddMonster( data->cMonster, position, 0, monster->Status->spawnid, 1 ); + } + //Log(MSG_DEBUG, "AIACT(020)option spawn %i distance %i monstertype %i",data->btPos,data->iDistance,data->cMonster); + return AI_SUCCESS; +} + +//Unknown +AIACT(021) +{ + //Does absolutely nothing + return AI_SUCCESS; +} + +//Unknown +AIACT(022) +{ + //Does absolutely nothing + return AI_SUCCESS; +} + +//ghostseeds and ghosts and player summons commit suicide +AIACT(023) +{ + //Commit suicide + entity->Stats->HP = -1; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, entity->clientid ); + ADDDWORD ( pak, 0x8005 ); + GServer->SendToVisible( &pak, entity ); + //Log(MSG_DEBUG, "AIACT(023) suicide monstertype %i",thisMonster->montype); + //thisMonster->DoAi(thisMonster->thisnpc->AI, 5); + //now release the id in the monsterlist + + //map->DeleteMonster( thisMonster); + + // something weird going on here. Client ids not being relinquished. Deletemonster does not help. it crashes the server. + + return AI_SUCCESS; +} + +//Do Skill +AIACT(024) +{ + //byte btTarget; //Pos: 0x00 + //word nSkill; //Pos: 0x02 + //word nMotion; //Pos: 0x04 //8 = SKILL_ATTACK 6 = magic + + //Use nSkill & nMotion? + //btTarget 0 = m_pFindCHAR + //btTarget 1 = target + //btTarget 2 = self skill + GETAIACTDATA(024); + // Log(MSG_DEBUG, "Do Skill btTarget %i nSkill %i nMotion %i",data->btTarget,data->nSkill,data->nMotion); + // Log(MSG_DEBUG, "entity %i",entity->clientid); + // Log(MSG_DEBUG, "entity->findChar %i",entity->findChar->clientid); + // Log(MSG_DEBUG, "entity->nearChar %i", entity->nearChar->clientid); + CSkills* thisskill = GServer->GetSkillByID( data->nSkill ); + if(thisskill == NULL)return AI_FAILURE; + CCharacter* monster = entity; + //if(monster->Battle->atktype != 0) + //{ + // Log(MSG_DEBUG,"Cannot apply battle attacktype. Monster already has one"); + // return AI_FAILURE; + //} + //Log(MSG_DEBUG,"AIACT(024)skill attack id: %i target: %i type: %i btTarget: %i nMotion: %i", data->nSkill, thisskill->target, thisskill->skilltype, data->btTarget, data->nMotion); + switch (thisskill->skilltype) + { + case 3: //damage action + case 6: //magic attack + { + CCharacter* target = entity->GetCharTarget( ); + //Log(MSG_DEBUG,"SKILL_ATTACK or DAMAGE_ACTION selected"); + if(target==NULL) + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + //Log(MSG_DEBUG,"SKILL_ATTACK selected. target: %i",target->clientid); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + break; + case 7: //AOE magic attack + { + CCharacter* target = entity->GetCharTarget( ); + //Log(MSG_DEBUG,"AOE_TARGET selected"); + if(target == NULL) + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + //Log(MSG_DEBUG,"AOE_TARGET selected. target: %i",target->clientid); + monster->StartAction( (CCharacter*) target, AOE_TARGET, data->nSkill ); + } + break; + case 8: //self buff + { + //Log(MSG_DEBUG,"BUFF_SELF selected."); + monster->StartAction( NULL, BUFF_SELF, data->nSkill ); + } + break; + case 9: //skill buff + case 13://skill buff + case 11: //heal + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) //some skills always return a NULL target here + { + if(entity->nearChar == NULL) // maybe there really is no possible target. + { + //Log(MSG_DEBUG,"No target can be found for this skill"); + return AI_FAILURE; + } + else //so we are going to have them select a NEW target (if possible) rather than return AI_FAILURE + { + //Log(MSG_DEBUG,"SKILL_BUFF type 13 selected. NEW target: %i",entity->nearChar->clientid); + CCharacter* newtarget = entity->nearChar; + if(newtarget == NULL) + return AI_FAILURE; + monster->StartAction( (CCharacter*) newtarget, SKILL_BUFF, data->nSkill ); + } + } + else + { + //Log(MSG_DEBUG,"SKILL_BUFF type %i selected. target: %i",thisskill->skilltype, target->clientid); + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + break; + case 10: //heal AOE centered on self + //Log(MSG_DEBUG,"BUFF_AOE selected. Skill id %i", data->nSkill); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + break; + case 17: //skill AOE centered on self + { + //Log(MSG_DEBUG,"SKILL_AOE selected. Skill id %i", data->nSkill); + monster->StartAction( NULL, SKILL_AOE, data->nSkill ); + } + break; + default: + { + //Log(MSG_WARNING,"Skill type not recognized. type: %i",thisskill->skilltype); + } + break; + } + /* + if (data->btTarget == 0) + { + //CCharacter* target = entity->GetCharTarget(); + //if(target==NULL)return AI_FAILURE; + //CCharacter* monster = entity; //defined twice???? + Log(MSG_DEBUG,"BUFF_AOE selected"); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + } + else if (data->btTarget == 1) + { + CCharacter* target = entity->GetCharTarget( ); + if(target==NULL)return AI_FAILURE; + if(data->nMotion == 8) + { + Log(MSG_DEBUG,"SKILL_ATTACK selected"); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + else + { + Log(MSG_DEBUG,"SKILL_BUFF selected"); + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + else if (data->btTarget == 2) + { + if(thisskill->target == tYourself) + { + Log(MSG_DEBUG,"BUFF_SELF selected"); + monster->StartAction( entity, BUFF_SELF, data->nSkill ); + } + else if (thisskill->target == tAlly) + { + Log(MSG_DEBUG,"BUFF_AOE selected"); + monster->StartAction( NULL, BUFF_AOE, data->nSkill ); + } + else if (thisskill->target == tHostileCharacter) + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL)return AI_FAILURE; + if(data->nMotion == 8) + { + Log(MSG_DEBUG,"SKILL_ATTACK selected"); + monster->StartAction( (CCharacter*) target, SKILL_ATTACK, data->nSkill ); + } + else + { + monster->StartAction( (CCharacter*) target, SKILL_BUFF, data->nSkill ); + } + } + }*/ + return AI_SUCCESS; +} + +//Set Variable (1) +AIACT(025) +{ + // if(entity->_EntityType != ENTITY_NPC) return AI_FAILURE; + //byte btVarIDX Pos: 0x00 + //dword iValue Pos: 0x04 + //byte btOp Pos: 0x08 + GETAIACTDATA(025); + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return AI_FAILURE; + if(data->btVarIDX > 19) return AI_FAILURE; + short tempval = GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX]; + switch(data->btOp) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + break; + case 9: + tempval++; + break; + default: + return AI_FAILURE; + break; + } + if(tempval < 0)tempval = 0; + GServer->ObjVar[monster->thisnpc->refNPC][data->btVarIDX] = tempval; + if(data->btVarIDX == 0) //changing index 0 changes event variable at client too. Possible others too but for now just 0 + { + //Log(MSG_DEBUG,"AIACT(025) eventID has changed from %i to %i",monster->thisnpc->eventid,tempval); + monster->thisnpc->eventid = tempval; + } + return AI_SUCCESS; +} + +//Set Variable (2) +AIACT(026) +{ + //Set WorldVAR + //word nVarNo; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + GETAIACTDATA(026); + switch(data->nVarNo) + { + case 1: + GServer->Config.EXP_RATE = data->iValue; + return AI_SUCCESS; + break; + default: + return AI_SUCCESS; + break; + } +} + +//Set Variable (3) +AIACT(027) +{ + //Set EconomyVAR + //word nVarNo; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + return AI_SUCCESS; +} + +//Shout/Ann LTB String +AIACT(028) +{ + GETAIACTDATA(028); + //byte btMsgType Pos: 0x00 + //dword iStrID Pos: 0x04 + dword npcId = 0; + //if(entity->_EntityType == ENTITY_NPC) + CMonster* thisMonster = reinterpret_cast(entity); + switch(data->btMsgType) + { + case 0: //whisper to client + // don't think this is possible. No way to get a character to send it to?? + break; + case 1: //shout to map + //Log(MSG_INFO,"AI -- NPC Shouts to map %i",entity->Position->Map); + GServer->NPCShout(entity->Position->Map,GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname); + break; + case 2: //announce to server. + GServer->NPCAnnounce(GServer->Ltbstring[data->iStrID].LTBstring,GServer->Ltbstring[data->iStrID].NPCname); + break; + default: + break; + } + + //npcId = reinterpret_cast(entity)->NpcID; + // }else if(entity->_EntityType == ENTITY_MONSTER){ + // npcId = reinterpret_cast(entity)->MonID; + // }else{ + // return AI_FAILURE; + // } + // std::map::iterator triggerITR = server->NpcNames.find(npcId); + // if(triggerITR == server->NpcNames.end()) return AI_FAILURE; + + // char* npcName = triggerITR->second; + // char* sayStr = server->lngAi->Data(data->iStrID, 1); + // if(sayStr == NULL) return AI_FAILURE; + // if(data->btMsgType == 0){ + //Chat + // CPacket pakout(0x783); + // pakout.Add(entity->_ClientID); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // }else if(data->btMsgType == 1){ + //Shout + // CPacket pakout(0x785); + // pakout.Add(npcName); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // }else if(data->btMsgType == 2){ + //Announce + // CPacket pakout(0x702); + // pakout.AddBytes((byte*)npcName, strlen(npcName)); + // pakout.Add('>'); + // pakout.Add(sayStr); + // server->SendPacketToZone(entity, &pakout); + // } + // delete [] sayStr; + //Log(MSG_DEBUG, "AIACT(028)"); + return AI_SUCCESS; +} + +//Move to my owner's location +AIACT(029) +{ + //Move within 20% of the distance of my "CALLER" + GETAIACTDATA(029); + // if(entity->_EntityType != ENTITY_MONSTER) return AI_FAILURE; + // CMonster* thisMonster = reinterpret_cast(entity); + // CWorldEntity* caller = thisMonster->thisZone->GetEntity(thisMonster->_CallerID); + // if(caller == NULL) return AI_FAILURE; + // caller->UpdatePosition(); + // thisMonster->UpdatePosition(); + if(!entity->IsMonster( )) return AI_FAILURE; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL) return AI_FAILURE; + caller->UpdatePosition(); + thisMonster->UpdatePosition(); + thisMonster->Stats->stance = 1; + thisMonster->SetStats(); + thisMonster->Position->source = caller->Position->current; + int iDist = GServer->RandNumber(5, 20); //move to between 5% and 20% of owners position + float xDist = thisMonster->Position->current.x - thisMonster->Position->source.x; + float yDist = thisMonster->Position->current.y - thisMonster->Position->source.y; + float nX = thisMonster->Position->source.x + (xDist * iDist / 100); + float nY = thisMonster->Position->source.y + (yDist * iDist / 100); + thisMonster->Position->destiny.x = nX; + thisMonster->Position->destiny.y = nY; + thisMonster->Position->lastMoveTime = clock(); + ClearBattle( thisMonster->Battle );// this also seems to clear the attackers battle?? + //entity->Battle->atktype = 0; + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, thisMonster->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, thisMonster->Position->destiny.x*100 ); + ADDFLOAT ( pak, thisMonster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, thisMonster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + + return AI_SUCCESS; +} + +//Do Trigger +AIACT(030) +{ + //word lenszTrigger; //Pos: 0x00 + //char* szTrigger; //Pos: 0x02 + GETAIACTDATA(030); + char* tempName = reinterpret_cast(&data->szTrigger) - 2; + dword hash = MakeStrHash(tempName); + //Log(MSG_INFO, "Execute Quest Trigger %s[%d] [%08x]", tempName, data->lenszTrigger, hash); + //make a dummy player from this monster + CClientSocket* thisclient = new CClientSocket; + CPlayer* player = new CPlayer( thisclient ); + if(player == NULL) + { + Log(MSG_INFO, "Player not found in AIACT 30"); + return AI_FAILURE; + } + thisclient->player = (void*) player; + player->Position = entity->Position; + + int quest = player->ExecuteQuestTrigger(hash); + if(quest == QUEST_SUCCESS) + return AI_SUCCESS; + return AI_FAILURE; +} + +//set monster's attack target to the same as the owner +AIACT(031) +{ + //Log(MSG_DEBUG, "AIACT(031)"); + //Attack my "CALLER"(s target) if he is not an ally (must be caller's target...) + if(!entity->IsMonster( )) return AI_FAILURE; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL) return AI_FAILURE; + thisMonster->Position->source = caller->Position->current; + CCharacter* target = map->GetCharInMap(caller->Battle->target); + if(target == NULL) return AI_FAILURE; + // need to prevent the summon from attacking other summons of the same player + CMonster* otherMonster = reinterpret_cast(target); + if(otherMonster->owner == thisMonster->owner) return AI_FAILURE; + thisMonster->StartAction( (CCharacter*) target, NORMAL_ATTACK, 0 ); + //Log(MSG_DEBUG, "AIACT(031) summon attacking player's target"); + + return AI_SUCCESS; +} + +//Zone (1) +AIACT(032) +{ + //word nZoneNo; //Pos: 0x00 + //byte btOnOff; //Pos: 0x02 + //Set PK Flag (btOnOff) in nZoneNo + GETAIACTDATA(032); + //Log(MSG_DEBUG, "AIACT(032) zone (1)"); + return AI_SUCCESS; +} + +//Zone (2) +AIACT(033) +{ + //word nZoneNo //if 0, current map + //word nNewValue + //if nNewValue == 2 -> toggle + //else set regen system nNewValue (0 = off, 1 = on) + GETAIACTDATA(033); + //Log(MSG_DEBUG, "AIACT(033) zone (2)"); + return AI_SUCCESS; +} + +//Item (?) +AIACT(034) +{ + //word nItemNum; //Pos: 0x00 + //word nCount; //Pos: 0x02 + //Give item to "CALLER" + GETAIACTDATA(034); + if(!entity->IsMonster( )) return AI_FAILURE; + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CPlayer* caller = map->GetPlayerInMap( thisMonster->owner); + if(caller == NULL) return AI_FAILURE; + int nItem = data->nItemNum; + if (nItem == 0)return AI_FAILURE; + char itemtypes[15] = {4,0,0,0,0,0,0,0,0,0,1,2,2,4,3}; + CItem thisitem; + thisitem.Clear(); + thisitem.itemtype = nItem / 1000; + thisitem.itemnum = nItem % 1000; + thisitem.count = data->nCount; + if( itemtypes[thisitem.itemtype] == 0 ) + { + thisitem.appraised = true; + thisitem.lifespan = 100; + thisitem.gem = 0; + thisitem.durability = GServer->STB_ITEM[thisitem.itemtype-1].rows[thisitem.itemnum][29]; + } + int thisslot = caller->AddItem( thisitem ); + //Log(MSG_DEBUG,"Item type = %i item ID = %i Count = %i Slot = %i",thisitem.itemtype,thisitem.itemnum,thisitem.count,thisslot); + if (thisslot == 0xffff) // Fail + { + BEGINPACKET( pak, 0x7a7); + ADDWORD(pak, 0x00); + ADDBYTE(pak, 0x03); + ADDBYTE(pak, 0x00); + caller->client->SendPacket(&pak); + return AI_FAILURE; + } + else // Success + { + BEGINPACKET( pak, 0x71f); + ADDBYTE(pak, 0x01); + ADDBYTE(pak, thisslot); + ADDWORD(pak, caller->items[thisslot].GetPakHeader( ) ); + ADDDWORD(pak, caller->items[thisslot].GetPakData( ) ); + caller->client->SendPacket(&pak); + } + //Log(MSG_DEBUG, "AIACT(034) Item (?)"); + return AI_SUCCESS; +} + +//Set Variable (4) +AIACT(035) +{ + //Set AiVAR + //word nVarIDX; Pos: 0x00 + //dword iValue; Pos: 0x04 + //byte btOp; Pos: 0x08 + GETAIACTDATA(035); + CMonster* thisMonster = reinterpret_cast(entity); + if(data->nVarIDX >19)return AI_FAILURE; + int tempval = thisMonster->AIVar[data->nVarIDX]; + switch(data->btOp) + { + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + if(tempval <= 0) + { + thisMonster->AIVar[data->nVarIDX] = 0; + return AI_FAILURE; + } + break; + case 9: + tempval++; + break; + default: + return AI_FAILURE; + break; + } + thisMonster->AIVar[data->nVarIDX] = tempval; + //thisMonster->thisnpc->Objvar->SetVar(data->btOp, data->iValue); //code not working + return AI_SUCCESS; +} + +//Monster (1) +AIACT(036) +{ + //word nMonster; //Pos: 0x00 + //byte btMaster; //Pos: 0x02 + //Spawn nMonster at my position (has btMaster)? + GETAIACTDATA(036); + // CWorldEntity* master = NULL; + // if(data->btMaster) master = entity; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + CMap* map = GServer->MapList.Index[entity->Position->Map]; + int owner = 0; + if(data->btMaster == 1) + owner = monster->clientid; + map->AddMonster( data->nMonster, entity->Position->current, 0, monster->Status->spawnid, 10 ); + //Log(MSG_DEBUG, "AIACT(36) monster (1) no code here"); + return AI_SUCCESS; +} + +//Monster (2) +AIACT(037) +{ + //word nMonster; //Pos: 0x00 + //word nPos; //Pos: 0x02 + //dword iDistance; //Pos: 0x04 + //byte btMaster; //Pos: 0x08 + + //Spawn nMonster at nPos within iDistance (has btMaster)? + //nPos = 0 my pos + //nPos = 1 m_pDestCHAR pos + //nPos = 2 target pos + GETAIACTDATA(037); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + CMonster* monster = reinterpret_cast(entity); //need this to get the spawnid of the entity + if(monster == NULL) return AI_FAILURE; + int owner = 0; + if(data->btMaster == 1) + owner = monster->clientid; + fPoint position; + + if(data->nPos == 0) //spawn monster in a circle with radius iDistance around my current position + { + position = GServer->RandInCircle( entity->Position->current, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + } + else if(data->nPos == 1) //spawn monster in a circle with radius iDistance around my destiny position + { + position = GServer->RandInCircle( entity->Position->destiny, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + //dest char + } + else if(data->nPos == 2) //spawn monster in a circle with radius iDistance around my target's current position + { + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + position = GServer->RandInCircle( target->Position->current, data->iDistance ); + map->AddMonster( data->nMonster, position, 0, monster->Status->spawnid, 10 ); + } + return AI_SUCCESS; +} + +//TDEF code. move to next waypoint +AIACT(038) +{ + //byte cSpeed pos 0x00 + GETAIACTDATA(038); + CMonster* monster = reinterpret_cast(entity); + monster->NextWayPoint ++; + monster->Position->destiny.x = GServer->WPList[monster->Position->Map][monster->NextWayPoint].X; + monster->Position->destiny.y = GServer->WPList[monster->Position->Map][monster->NextWayPoint].Y; + monster->Stats->stance = data->cSpeed; + entity->Position->lastMoveTime = clock(); + BEGINPACKET( pak, 0x797 ); + ADDWORD ( pak, entity->clientid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, entity->Stats->Move_Speed ); //speed + ADDFLOAT ( pak, monster->Position->destiny.x*100 ); + ADDFLOAT ( pak, monster->Position->destiny.y*100 ); + ADDWORD ( pak, 0xcdcd ); + ADDBYTE ( pak, monster->Stats->stance ); + GServer->SendToVisible(&pak, entity); + return AI_SUCCESS; +} diff --git a/Src/World Server/aip/AiActions.h b/Src/World Server/aip/AiActions.h new file mode 100644 index 0000000..fde258a --- /dev/null +++ b/Src/World Server/aip/AiActions.h @@ -0,0 +1,247 @@ +// Props to ExJam for this code :D +#define AIACT(act) int F_AI_ACT_ ## act (class CWorldServer* server, class CCharacter* entity, byte* raw) +typedef int (*fpAiAct)(class CWorldServer*, class CCharacter*, byte*); +#define GETAIACTDATA(act) STR_AI_ACT_ ## act * data = (STR_AI_ACT_ ## act *)raw; + + +AIACT(000);AIACT(001);AIACT(002); +AIACT(003);AIACT(004);AIACT(005); +AIACT(006);AIACT(007);AIACT(008); +AIACT(009);AIACT(010);AIACT(011); +AIACT(012);AIACT(013);AIACT(014); +AIACT(015);AIACT(016);AIACT(017); +AIACT(018);AIACT(019);AIACT(020); +AIACT(021);AIACT(022);AIACT(023); +AIACT(024);AIACT(025);AIACT(026); +AIACT(027);AIACT(028);AIACT(029); +AIACT(030);AIACT(031);AIACT(032); +AIACT(033);AIACT(034);AIACT(035); +AIACT(036);AIACT(037);AIACT(038); + +#define AI_ACT_COUNT 38 + +struct STR_AI_ACT_000 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_001 { + //Do Action (0x04 bytes) + byte cAction; //Pos: 0x00 +}; + +struct STR_AI_ACT_002 { + //Say LTB String (0x04 bytes) + dword iStrID; //Pos: 0x00 +}; + +struct STR_AI_ACT_003 { + //Move (1) (0x08 bytes) + dword iDistance; //Pos: 0x00 + byte cSpeed; //Pos: 0x04 +}; + +struct STR_AI_ACT_004 { + //Move (2) (0x08 bytes) + dword iDistance; //Pos: 0x00 + byte cSpeed; //Pos: 0x04 +}; + +struct STR_AI_ACT_005 { + //Move (3) (0x04 bytes) + byte cSpeed; //Pos: 0x00 +}; + +struct STR_AI_ACT_006 { + //Move (?) (0x08 bytes) + dword iDistance; //Pos: 0x00 + byte cAbType; //Pos: 0x04 + byte cMoreLess; //Pos: 0x05 +}; + +struct STR_AI_ACT_007 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_008 { + //Move (4) (0x08 bytes) + dword iDistance; //Pos: 0x00 + byte cSpeed; //Pos: 0x04 +}; + +struct STR_AI_ACT_009 { + //Spawn Monster (1) (0x04 bytes) + word wMonster; //Pos: 0x00 +}; + +struct STR_AI_ACT_010 { + //Spawn Monster (2) (0x04 bytes) + word wMonster; //Pos: 0x00 +}; + +struct STR_AI_ACT_011 { + //Spawn Monster (3) (0x08 bytes) + int iDistance; //Pos: 0x00 + int iNumOfMonster; //Pos: 0x04 +}; + +struct STR_AI_ACT_012 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_013 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_014 { + //Unknown (0x04 bytes) + int iDistance; //Pos: 0x00 +}; + +struct STR_AI_ACT_015 { + //Retaliate ? (0x00 bytes) +}; + +struct STR_AI_ACT_016 { + //Unknown (0x04 bytes) + int iDistance; //Pos: 0x00 +}; + +struct STR_AI_ACT_017 { + //Drop Item (0x10 bytes) + word items[6]; + byte btToOwner; //Pos: 0x0c +}; + +struct STR_AI_ACT_018 { + //Spawn Monster (4) (0x08 bytes) + short cMonster; //Pos: 0x00 + short wHowMany; //Pos: 0x02 + int iDistance; //Pos: 0x04 +}; + +struct STR_AI_ACT_019 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_020 { + //Spawn Monster (5) (0x08 bytes) + word cMonster; //Pos: 0x00 + byte btPos; //Pos: 0x02 + int iDistance; //Pos: 0x04 +}; + +struct STR_AI_ACT_021 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_022 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_023 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_024 { + //Do Skill (0x08 bytes) + byte btTarget; //Pos: 0x00 + word nSkill; //Pos: 0x02 + word nMotion; //Pos: 0x04 +}; + +struct STR_AI_ACT_025 +{ + //Set Variable (1) (0x0c bytes) + union + { + byte btVarIDX; //Pos: 0x00 + char cVarIdx[4]; + }; + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_ACT_026 { + //Set Variable (2) (0x00 bytes) + union{word nVarNo; //Pos: 0x00 + char cVarNo[4];}; + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_ACT_027 { + //Set Variable (3) (0x00 bytes) + union{word nVarNo; //Pos: 0x00 + char cVarNo[4];}; + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_ACT_028 +{ + //Shout/Ann LTB String (0x08 bytes) + union + { + byte btMsgType; + int iMsgType; + }; + dword iStrID; //Pos: 0x04 +}; + +struct STR_AI_ACT_029 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_030 { + //Do Trigger (0x0d bytes) + word lenszTrigger; //Pos: 0x00 + char* szTrigger; //Pos: 0x02 +}; + +struct STR_AI_ACT_031 { + //Unknown (0x00 bytes) +}; + +struct STR_AI_ACT_032 { + //Zone (1) (0x04 bytes) + word nZoneNo; //Pos: 0x00 + byte btOnOff; //Pos: 0x02 +}; + +struct STR_AI_ACT_033 { + //Zone (2) (0x00 bytes) +}; + +struct STR_AI_ACT_034 { + //Item (?) (0x04 bytes) + word nItemNum; //Pos: 0x00 + word nCount; //Pos: 0x02 +}; + +struct STR_AI_ACT_035 { + //Set Variable (4) (0x0c bytes) + union{word nVarIDX; //Pos: 0x00 + char cVarIdx[4];}; + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_ACT_036 { + //Monster (1) (0x04 bytes) + word nMonster; //Pos: 0x00 + byte btMaster; //Pos: 0x02 +}; + +struct STR_AI_ACT_037 { + //Monster (2) (0x0c bytes) + word nMonster; //Pos: 0x00 + byte nPos; //Pos: 0x02 + dword iDistance; //Pos: 0x04 + byte btMaster; //Pos: 0x08 +}; + +struct STR_AI_ACT_038 +{ + //TDEF set destiny position + byte cSpeed; +}; diff --git a/Src/World Server/aip/AiConditions.cpp b/Src/World Server/aip/AiConditions.cpp new file mode 100644 index 0000000..55889c8 --- /dev/null +++ b/Src/World Server/aip/AiConditions.cpp @@ -0,0 +1,1078 @@ +// Props to ExJam for this code :D +#include "../WorldServer.h" + +//extern CRandomMersenne rg; + +int AI_GetAbility(CCharacter* entity, byte btAbType) +{ + switch(btAbType) + { + case 0: + return entity->Stats->Level; + break; + case 1: + return entity->Stats->Attack_Power; + break; + case 2: + return entity->Stats->Defense; + break; + case 3: + return entity->Stats->Magic_Defense; + break; + case 4: + return entity->Stats->HP; + break; + case 5: + return 0; + //Log(MSG_WARNING, "Don't have monsters charm yet..."); + //return -1; + break; + default: + //Log(MSG_ERROR, "Unknown cAbType [%d]", btAbType); + return -1; + break; + } +} + +//Hold or Attack +AICOND(000) +{ + //Always False + //Log(MSG_DEBUG, "AICOND(000) called"); + return AI_FAILURE; +} + +//(Damage > iDamage) == true iDamage cRecvOrGive +AICOND(001) +{ + //Log(MSG_DEBUG, "AICOND(001) called"); + //dword iDamage //Pos: 0x00 + //byte cRecvOrGive //Pos: 0x04 + GETAICONDDATA(001); + //Log(MSG_DEBUG, "AIcond(001) %i %i %i",data->iDamage,data->cRecvOrGive,entity->damagecounter); + if(data->cRecvOrGive == 0) + { + if(entity->damagecounter > data->iDamage) + { + entity->damagecounter = 0; + //Log(MSG_DEBUG, "AIcond(001) Success "); + return AI_SUCCESS; + } + } + else + { + //PY this is wrong. if data->cRecvOrGive is 1 then it should return FALSE + /*if(entity->damagecounter < data->iDamage) + { + entity->damagecounter = 0; + //Log(MSG_DEBUG, "AIcond(001) Success "); + return AI_SUCCESS; + }*/ + return AI_FAILURE; + } + return AI_FAILURE; + +} + +//Check Near (1) +AICOND(002) +{ + /* + dword iDistance; //Pos: 0x00 + byte btIsAllied; //Pos: 0x04 + word nLevelDiff; //Pos: 0x06 + word nLevelDiff2; //Pos: 0x08 + word wChrNum; //Pos: 0x0a + */ + //count how many aiobj between nLevelDiff and nLevelDiff2 who is btIsAllied within iDistance + //must be >= wChrNum + + ////Log(MSG_DEBUG, "AICOND(002) called"); + GETAICONDDATA(002); + dword chrCount = 0; + dword eCount = 0; + int nearestDistance = 9999999; + int searchDistance = data->iDistance; + CMap* map = GServer->MapList.Index[entity->Position->Map]; + ////Log(MSG_DEBUG, "Check Near (1)iDistance %i btIsAllied %i nLevelDiff %i nLevelDiff2 %i wChrNum %i", data->iDistance, data->btIsAllied, data->nLevelDiff, data->nLevelDiff2, data->wChrNum); + if(data->btIsAllied == 1) + { + dword entityCount = map->MonsterList.size(); + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + + int levelDiff = (entity->Stats->Level - other->Stats->Level); + if(levelDiff < data->nLevelDiff) continue; + if(levelDiff > data->nLevelDiff2) continue; + int iDistance = (int) GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + + chrCount++; + if(iDistance < nearestDistance) + { + nearestDistance = iDistance; + entity->nearChar = other; + } + if(chrCount >= data->wChrNum) + { + entity->findChar = other; + return AI_SUCCESS; + } + } + } + else + { + dword entityCount = map->PlayerList.size(); + ////Log(MSG_DEBUG, "entityCount %i", entityCount); + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* other = map->PlayerList.at(j); + ////Log(MSG_DEBUG, "player %i", other->clientid); + if(eCount >= entityCount) break; + ////Log(MSG_INFO, "1"); + if(other == NULL) continue; + ////Log(MSG_INFO, "2"); + eCount++; + + int levelDiff = (entity->Stats->Level - other->Stats->Level); + ////Log(MSG_DEBUG, "levelDiff %i < %i", levelDiff,data->nLevelDiff); + if(levelDiff < data->nLevelDiff) continue; + ////Log(MSG_INFO, "3"); + ////Log(MSG_DEBUG, "levelDiff %i > %i", levelDiff,data->nLevelDiff2); + if(levelDiff > data->nLevelDiff2) continue; + ////Log(MSG_INFO, "4"); + int iDistance = (int) GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + ////Log(MSG_INFO, "5"); + + chrCount++; + if(iDistance < nearestDistance) + { + nearestDistance = iDistance; + entity->nearChar = other; + ////Log(MSG_INFO, "6"); + } + if(chrCount >= data->wChrNum) + { + entity->findChar = other; + ////Log(MSG_INFO, "7"); + return AI_SUCCESS; + } + } + } + return AI_FAILURE; + + //count how many aiobj between nLevelDiff and nLevelDiff2 who is btIsAllied within iDistance + //must be >= wChrNum + //doesn't seem to work that way though + //it would seem that LevelDiff is a maximum level and LevelDiff2 is a minimum level. + //just subtract each from 65535 to get the level range. No difference involved at all. PY + //leveldiff2 often uses only the first byte so it is taken at face value. + + //Log(MSG_DEBUG, "AICOND(002) called"); + /* + GETAICONDDATA(002); + int maxlevel = 0; + int minlevel = 0; + CMonster* monster = reinterpret_cast(entity); + //Log(MSG_INFO,"Minlevel: %i Maxlevel: %i",data->nLevelDiff2,data->nLevelDiff); + + if(data->nLevelDiff2 > 255) + minlevel = 65535 - data->nLevelDiff2; + else + minlevel = data->nLevelDiff2; + if(data->nLevelDiff > 255) + maxlevel = 65535 - data->nLevelDiff; + else + maxlevel = data->nLevelDiff; + + if(minlevel > maxlevel) //Sometimes min and max are reversed. After all we just need to know if someting is between the two + { + int tmpmax = maxlevel; + int tmpmin = minlevel; + minlevel = tmpmax; + maxlevel = tmpmin; + } + //Log(MSG_INFO,"Minlevel: %i Maxlevel: %i",minlevel,maxlevel); + UINT chrCount = 0; + UINT eCount = 0; + int nearestDistance = 9999999; + int searchDistance = data->iDistance; + CMap* map = GServer->MapList.Index[entity->Position->Map]; + //Log(MSG_DEBUG, "COND(002) Check Near (1)iDistance %i btIsAllied %i level %i to %i wChrNum %i Monid %i", data->iDistance, data->btIsAllied, minlevel, maxlevel, data->wChrNum, monster->montype); + if(data->btIsAllied == 1) + { + dword entityCount = map->MonsterList.size(); + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + + if(other->Stats->Level > maxlevel) continue; + if(other->Stats->Level < minlevel) continue; + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + + chrCount++; + if(iDistance < nearestDistance) + { + nearestDistance = iDistance; + entity->nearChar = other; + } + if(chrCount >= data->wChrNum) + { + entity->findChar = other; + return AI_SUCCESS; + } + } + } + else + { + dword entityCount = map->PlayerList.size(); + //Log(MSG_DEBUG, "entityCount %i", entityCount); + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* other = map->PlayerList.at(j); + //Log(MSG_DEBUG, "player %i", other->clientid); + if(eCount >= entityCount) break; + //Log(MSG_INFO, "1"); + if(other == NULL) continue; + //Log(MSG_INFO, "2"); + eCount++; + + if(other->Stats->Level > maxlevel) continue; + if(other->Stats->Level < minlevel) continue; + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + //Log(MSG_INFO, "5"); + + chrCount++; + if(iDistance < nearestDistance) + { + nearestDistance = iDistance; + entity->nearChar = other; + //Log(MSG_INFO, "6"); + } + if(chrCount >= data->wChrNum) + { + entity->findChar = other; + //Log(MSG_INFO, "7"); + return AI_SUCCESS; + } + } + } + return AI_FAILURE;*/ +} + +//Check Distance (1) +AICOND(003) +{ + //dword iDistance; //Pos: 0x00 + //CObjCHAR::Get_MoveDISTANCE > iDistance == true + //distance from spawn point? + //Log(MSG_DEBUG, "AICOND(003) called"); + GETAICONDDATA(003); + + entity->UpdatePosition(); + int moveDistance = (int)GServer->distance( entity->Position->current, entity->Position->source ); + if(moveDistance > (data->iDistance)) + { + return AI_SUCCESS; + } + return AI_FAILURE; +} + +//Check Distance (2) +AICOND(004) +{ + //dword iDistance; //Pos: 0x00 + //byte cMoreLess; //Pos: 0x04 + //distance to target (cMoreLess == 0 then >= else <=) iDistance + //Log(MSG_DEBUG, "AICOND(004) called"); + GETAICONDDATA(004); + //Log(MSG_DEBUG, "AICOND(004) retrieved data"); + CCharacter* target = entity->GetCharTarget( ); + //CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + if(target == NULL) + { + //Log(MSG_DEBUG, "AICOND(004) found no valid target"); + return AI_FAILURE; + } + target->UpdatePosition(); + entity->UpdatePosition(); + int distance = (int)GServer->distance( entity->Position->current, target->Position->current ); + //int distance = entity->basic.pos.distance(target->basic.pos); + if(data->cMoreLess == 0) + { + if(distance >= data->iDistance) + { + //Log(MSG_DEBUG, "Check Distance (2) This distance: %i > %i", distance, data->iDistance); + return AI_SUCCESS; + } + } + else + { + if(distance <= data->iDistance) + { + //Log(MSG_DEBUG, "Check Distance (2) This distance: %i < %i", distance, data->iDistance); + return AI_SUCCESS; + } + } + return AI_FAILURE; +} + +//Check Ability of my Target +AICOND(005) +{ + /* + byte cAbType; //Pos: 0x00 + dword iDiff; //Pos: 0x04 + byte cMoreLess; //Pos: 0x08 + */ + //Log(MSG_DEBUG, "AICOND(005) called"); + GETAICONDDATA(005); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + { + //Log(MSG_DEBUG, "AICOND(004) found no valid target"); + return AI_FAILURE; + } + //cAbType + //0 = Level + //1 = Attack + //2 = Defense + //3 = "Res"? Magic Resistance? + //4 = HP + //5 = Charm + + //Check if "ability" (cMoreLess == 0 then >= else <=) iDiff + //Log(MSG_DEBUG, "Check AB (1)"); + int value = AI_GetAbility(target, data->cAbType); //PY This should be checking my target's ability NOT MY OWN. Corrected now + //Log(MSG_DEBUG, "Check AB (1) type %i diff %i", data->cAbType, data->iDiff); + if(value < 0) return AI_FAILURE; + if(data->cMoreLess == 0) + { + if(value > data->iDiff) + { + //Log(MSG_DEBUG, "Check AB (1) > %i", data->iDiff); + return AI_SUCCESS; + } + } + else + { + if(value < data->iDiff) + { + //Log(MSG_DEBUG, "Check AB (1) < %i", data->iDiff); + return AI_SUCCESS; + } + } + return AI_FAILURE; +} + +//Check HP +AICOND(006) +{ + //dword wHP; //Pos: 0x00 + //byte cMoreLess; //Pos: 0x04 + //Check if hp% (cMoreLess == 0 then >= else <=) wHP + //Log(MSG_DEBUG, "AICOND(000) called"); + GETAICONDDATA(006); + dword value = (dword)(((float)entity->Stats->HP/ (float)entity->Stats->MaxHP) * 100.0f); + //Log(MSG_DEBUG, "Check HP %i %i", data->cMoreLess, value); + if(data->cMoreLess == 0) + { + if(value >= data->wHP) return AI_SUCCESS; + } + else + { + if(value <= data->wHP) return AI_SUCCESS; + } + return AI_FAILURE; +} + +//Random Chance +AICOND(007) +{ + //byte cPercent; //Pos: 0x00 + //Random number 0->100 if random number < cPercent then return true + //Log(MSG_DEBUG, "AICOND(007) called"); + GETAICONDDATA(007); + //byte rand = rg.IRandom(0, 100) & 0xFF; + //srand(time(NULL)); + byte brand = rand()%100; + //Log(MSG_DEBUG, "brand = %i chance = %i", brand, data->cPercent); + if(brand <= data->cPercent) return AI_SUCCESS; + return AI_FAILURE; +} + +//Check Near (2) +AICOND(008) +{ + //dword iDistance; //Pos: 0x00 + //word nLevelDiff; //Pos: 0x04 + //word nLevelDiff2; //Pos: 0x06 + //byte btIsAllied; //Pos: 0x08 + + //find atleast 1 aiobj between nLevelDiff and nLevelDiff2 who is btIsAllied within iDistance + + //Log(MSG_DEBUG, "AICOND(008) called"); + GETAICONDDATA(008); + + dword eCount = 0; + int searchDistance = data->iDistance;// * 10; + CMap* map = GServer->MapList.Index[entity->Position->Map]; + + + if(data->btIsAllied == 1) + { + dword entityCount = map->MonsterList.size(); + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + if(other->CharType != TMONSTER) continue; + + int levelDiff = abs(entity->Stats->Level - other->Stats->Level); + if(levelDiff < data->nLevelDiff) continue; + if(levelDiff > data->nLevelDiff2) continue; + + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + + entity->findChar = other; + entity->nearChar = other; + return AI_SUCCESS; + } + } + else + { + dword entityCount = map->PlayerList.size(); + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* other = map->PlayerList.at(j); + + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + if(other->CharType != TPLAYER) continue; + + int levelDiff = abs(entity->Stats->Level - other->Stats->Level); + if(levelDiff < data->nLevelDiff) continue; + if(levelDiff > data->nLevelDiff2) continue; + + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + + entity->findChar = other; + entity->nearChar = other; + return AI_SUCCESS; + } + } + return AI_FAILURE; +} + +//check target +//PY This is actually incorrect. Should be comparing my target with my destination character. TRUE if they are different +//To be corrected!!! +AICOND(009) +{ + //Do i have a target? + //CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + //if(target == NULL){entity->_TargetID = 0; return AI_FAILURE;} + //return AI_SUCCESS; + + //TO BE RECODED + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + return AI_SUCCESS; +} + +//Check AB (2) +AICOND(010) +{ + //byte cAbType; //Pos: 0x00 + //byte cMoreLess; //Pos: 0x01 + + //Is my ability >= or <= than my targets + //PY This is wrong too. Should be comparing ability of my target against the one that just hit me + //NOT against MY ability + + //TO BE RECODED + GETAICONDDATA(010); + //CWorldEntity* target = entity->thisZone->GetEntity(entity->_TargetID); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) return AI_FAILURE; + //Log(MSG_DEBUG, "Check AB (2) type %i ", data->cAbType); + int myValue = AI_GetAbility(entity, data->cAbType); + if(myValue < 0) return AI_FAILURE; + + int targetValue = AI_GetAbility(target, data->cAbType); + if(targetValue < 0) return AI_FAILURE; + + if(data->cMoreLess == 0) + { + if(myValue > targetValue) return AI_SUCCESS; + } + else + { + if(myValue < targetValue) return AI_SUCCESS; + } + return AI_FAILURE; +} + +//Check AB (3) +//PY Again this has to be my Attacker's ability. NOT my own or even my Target's +AICOND(011) +{ + //byte cAbType; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte cMoreLess; //Pos: 0x08 + //check ability of "m_pDestCHAR" <= or >= iValue + GETAICONDDATA(011); + dword tValue = data->iValue; + //if(tValue == 0)tValue = AI_GetAbility(entity, data->cAbType); + CCharacter* target = entity->GetCharTarget( ); + if(target == NULL) + { + //Log(MSG_DEBUG, "AICOND(004) found no valid target"); + return AI_FAILURE; + } + int myValue = AI_GetAbility(target, data->cAbType); + //CCharacter* target = entity->GetCharTarget( ); + //if(target == NULL) + //{ + // Log(MSG_DEBUG,"AICOND(011) could not find a valid target"); + // return AI_FAILURE; + //} + //myValue = AI_GetAbility(target, data->cAbType); + //Log(MSG_DEBUG, "AICOND(011) myValue: %i <> iValue: %i moreless: %i type: %i", myValue, data->iValue, data->cMoreLess, data->cAbType); + if(data->cMoreLess == 0) //0 = more + { + if(myValue >= data->iValue) + { + //Log(MSG_DEBUG, "AICOND(011) myValue: %i <= tValue: %i ",myValue,tValue); + return AI_SUCCESS; + } + } + else // 1 = less + { + if(myValue <= data->iValue) + { + //Log(MSG_DEBUG, "AICOND(011) myValue: %i <= tValue: %i ",myValue,tValue); + return AI_SUCCESS; + } + } + return AI_FAILURE; +} + +//Check Time (1) +AICOND(012) +{ + //byte cWhen; //Pos: 0x00 + //Is it night or day? cWhen: ?1 = night 0 = day? + GETAICONDDATA(012); + CMap* map = GServer->MapList.Index[entity->Position->Map]; + if (data->cWhen == 1) + { + if (map->CurrentTime > 2) // night + return AI_SUCCESS; + } + else + { + if (map->CurrentTime < 3) // day + return AI_SUCCESS; + } + return AI_FAILURE; +} + +//Check Target for buffs(1) +AICOND(013) +{ + //byte btCheckTarget; //Pos: 0x00 //0 = self 1 = target + //byte btStatusType; //Pos: 0x01 // + //byte btHave; //Pos: 0x02 + + //Does my target btHave MagicSTATUS (btStatusType) + //btCheckTarget does Get_MagicSTATUS but uses differ AND masks + //btCheckTarget = 0 AND EAX,1EAAA84 + //btCheckTarget = 1 AND EAX,1E155570 + //btCheckTarget = 2 normal + CCharacter* target = NULL; + UINT buffs=0; + GETAICONDDATA(013); + if(data->btCheckTarget == 1) //check enemy + { + target = entity->GetCharTarget( ); + if(target == NULL)return AI_FAILURE; + } + else if(data->btCheckTarget == 0) //check self + { + target = entity; + } + else //should never be anything other than 0 or 1 + { + //Log(MSG_DEBUG, "AICOND(013) Check Target (1) btCheckTarget %i ", data->btCheckTarget); + return AI_FAILURE; + } + if (target == NULL) //target not found + { + //Log(MSG_DEBUG, "AICOND(013) Check Target (1) not found btCheckTarget %i ", data->btCheckTarget); + return AI_FAILURE; + } + + switch(data->btStatusType) + { + case 0: //debuff + buffs = GServer->BuildDeBuffs(target); + break; + case 1: //buff + buffs = GServer->BuildUpBuffs(target); + break; + case 2: //effect such as burning + //not sure how to handle this so i will come back to it. PY + break; + } + //Log(MSG_DEBUG, "Target: %i btStutusType: %i buffs: %i btHave: %i", target->clientid, data->btStatusType, buffs, data->btHave); + if (data->btHave == 1) //True if HAS buffs + { + if(buffs != 0)return AI_SUCCESS; + return AI_FAILURE; + } + else // true if NOT HAS buffs + { + if(buffs != 0)return AI_FAILURE; + return AI_SUCCESS; + } + //CCharacter* target = entity->GetCharTarget( ); + //Log(MSG_DEBUG, "Check Target (1) shouldn't be able to get this far"); + return AI_SUCCESS; + //return AI_FAILURE; +} + +//Check Variable (1) (ObjVar) +AICOND(014) +{ + GETAICONDDATA(014); + //Log(MSG_DEBUG,"Checking Object Variable using NPC ref number AICOND(015)"); + //Log(MSG_DEBUG,"btVarIDX = %i iValue = %i btOp = %i",data->btVarIDX, data->iValue,data->btOp); + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return AI_FAILURE; + if(data->btVarIDX > 19) return AI_FAILURE; + int refNPC = monster->thisnpc->refNPC; + int ObjvarIndex = data->btVarIDX; + + int tempval = GServer->ObjVar[refNPC][ObjvarIndex]; + //Log(MSG_DEBUG,"ObjVar value [%i][%i] = %i", refNPC,ObjvarIndex,tempval); + switch(data->btOp) + { + case 0: + if(tempval == data->iValue) + return AI_SUCCESS; + break; + case 1: + if(tempval > data->iValue) + return AI_SUCCESS; + break; + case 2: + if(tempval >= data->iValue) + return AI_SUCCESS; + break; + case 3: + if(tempval < data->iValue) + return AI_SUCCESS; + break; + case 4: + if(tempval <= data->iValue) + return AI_SUCCESS; + break; + case 10: + if(tempval != data->iValue) + return AI_SUCCESS; + break; + default: + return AI_FAILURE; + break; + } + + + //if(entity->_EntityType != ENTITY_NPC) return AI_FAILURE; + //CNpc* thisNpc = reinterpret_cast(entity); + //thisNpc = thisNpc->SelectedNpc; + //if(thisNpc == NULL) return AI_FAILURE; + //if(OperateValues(data->btOp, &VarValue, data->iValue & 0xFFFF)) return AI_SUCCESS; + return AI_FAILURE; +} + +//Check Variable (2) (WorldVAR) //PY should work just like NPC ObjVar. To Be Coded later +AICOND(015) +{ + //word nVarIDX; //Pos: 0x00 + //byte btOp; //Pos: 0x08 + //dword iValue; //Pos: 0x04 + + //Check WorldVAR + return AI_FAILURE; +} + +//Check Variable (3) (EconomyVar) +AICOND(016) +{ + //word nVarIDX; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + + //Check EconomyVar + return AI_FAILURE; +} + +//Select ObjVar reference +AICOND(017) +{ + GETAICONDDATA(017); + //Log(MSG_DEBUG,"Setting NPC ref number AICOND(017)"); + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return AI_FAILURE; + monster->thisnpc->refNPC = data->iNpcNo; // sets the reference variable for the correct ObjVar + //Log(MSG_DEBUG,"Set NPC ref number %i successfully", monster->thisnpc->refNPC); + return AI_SUCCESS; +} + +//Check Distance (3) +AICOND(018) +{ + //dword iDistance; //Pos: 0x00 + //byte btOp; //Pos: 0x04 + + //Distance to "CALLER" ??Possibly the one who summoned the monster?? + GETAICONDDATA(018); + if(!entity->IsMonster( )) return AI_FAILURE; + //Log(MSG_DEBUG, "AICOND(018 1)"); + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL ) return AI_FAILURE; // I think this is wrong. It should return true if there is no owner. Otherwise orphaned bonfires will live forever + if(thisMonster->owner == 0) return AI_FAILURE; //Monster has no owner + //Log(MSG_DEBUG, "AICOND(018 2)"); + //if(entity->_EntityType != ENTITY_MONSTER) return AI_FAILURE; + //CMonster* thisMonster = reinterpret_cast(entity); + //CWorldEntity* caller = thisMonster->thisZone->GetEntity(thisMonster->_CallerID); + //if(caller == NULL) return AI_FAILURE; + caller->UpdatePosition(); + thisMonster->UpdatePosition(); + int distance = (int)GServer->distance( caller->Position->current, thisMonster->Position->current ); + //int distance = thisMonster->basic.pos.distance(caller->basic.pos); + //Log(MSG_DEBUG, "AICOND(018 3)"); + if(OperateValues(data->btOp, &distance, data->iDistance)) return AI_SUCCESS; + //Log(MSG_DEBUG, "AICOND(018 4)"); + return AI_FAILURE; +} + +//Check Time (2) +AICOND(019) +{ + //dword ulTime; //Pos: 0x00 + //dword ulEndTime; //Pos: 0x04 + + //Get_ZoneTIME between ulTime and ulEndTime + GETAICONDDATA(019); + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + int StartTime = int(data->ulTime); + int EndTime = int(data->ulEndTime); + if(map->ZoneTime >= StartTime && map->ZoneTime <= EndTime) + return AI_SUCCESS; + return AI_FAILURE; +} + +//Check My AB (4) NOT target's +AICOND(020) +{ + //byte btAbType; //Pos: 0x00 + //dword iValue; //Pos: 0x04 + //byte btOp; //Pos: 0x08 + + + //cAbType + //0 = Level + //1 = Attack + //2 = Defense + //3 = "Res"? Magic Resistance? + //4 = HP + //5 = Charm + GETAICONDDATA(020); + //Log(MSG_DEBUG, "Check AB (4) type %i iValue %i", data->btAbType, data->iValue); + int value = AI_GetAbility(entity, data->btAbType); + if(value < 0) return AI_FAILURE; + if(OperateValues(data->btOp, &value, data->iValue)) return AI_SUCCESS; + //Log(MSG_INFO,"check ab (4) successfull"); + return AI_FAILURE; +} + +//Do I have a caller +//PY. Needs to return AI_SUCCESS if I have no owner +AICOND(021) +{ + //Do i have a "CALLER" ??Possibly the one who summoned the monster?? + // more like Am I an orphan? + GETAICONDDATA(021); + + if(!entity->IsMonster())return AI_SUCCESS; + CMonster* thisMonster = reinterpret_cast(entity); + if(thisMonster->owner == 0) return AI_SUCCESS; + //Log(MSG_DEBUG, "AICOND(021) Yes I Have no owner"); + return AI_FAILURE; +} + +//Does my caller have a target +AICOND(022) +{ + //Does my "CALLER" have a target? + //Log(MSG_DEBUG, "AICOND(022) Does my `CALLER` have a target?"); + GETAICONDDATA(022); + + if(!entity->IsMonster( )) return AI_FAILURE; + //Log(MSG_DEBUG, "AICOND(022 1)"); + CMonster* thisMonster = reinterpret_cast(entity); + CMap* map = GServer->MapList.Index[thisMonster->Position->Map]; + CCharacter* caller = map->GetCharInMap( thisMonster->owner ); + if(caller == NULL) return AI_FAILURE; + //Log(MSG_DEBUG, "AICOND(022 2)"); + CCharacter* target = map->GetCharInMap(caller->Battle->target); + if(target == entity) return AI_FAILURE; + if(target != NULL) return AI_SUCCESS; + //Log(MSG_DEBUG, "AICOND(022 3)"); + return AI_FAILURE; +} + +//Check Time (3) (World time?) +AICOND(023) +{ + //dword ulTime; //Pos: 0x00 + //dword ulEndTime; //Pos: 0x04 + + //Get_WorldTIME between ulTime and ulEndTime + return AI_FAILURE; +} + +//Check Date Time (4) +AICOND(024) +{ + //Log(MSG_INFO,"AIP Cdt 024 for aipId %i",AipId); + GETAICONDDATA(024); + //byte btDate Pos: 0x00 + //byte btHour1 Pos: 0x01 + //byte btMin1 Pos: 0x02 + //byte btHour2 Pos: 0x03 + //byte btMin2 Pos: 0x04 + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + + if(data->btDate != 0) + { + if(sTIME.wDay != data->btDate) + return AI_FAILURE; + } + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin >= wFrom && wMin <= wTo) + return AI_SUCCESS; + return AI_FAILURE; +} + +//Check Weekday Time (5) +AICOND(025) +{ + GETAICONDDATA(025); + //byte btWweekDay Pos: 0x00 + //byte btHour1 Pos: 0x01 + //byte btMin1 Pos: 0x02 + //byte btHour2 Pos: 0x03 + //byte btMin2 Pos: 0x04 + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + + if(data->btWeekDay != sTIME.wDayOfWeek) + return AI_FAILURE; + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin > wFrom && wMin < wTo) + return AI_SUCCESS; + return AI_FAILURE; +} + +//Check Server/Channel +AICOND(026) +{ + GETAICONDDATA(026); + //channel >= min && channel <= max + //return AI_SUCCESS; + return AI_FAILURE; +} + +//Check Near Character +AICOND(027 +){ + //dword iDistance; //Pos: 0x00 + //byte btIsAllied; //Pos: 0x04 + //word nLevelDiff; //Pos: 0x06 + //word nLevelDiff2; //Pos: 0x08 + //word wChrNum; //Pos: 0x0a + //byte btOp; //Pos: 0x0c + + //count how many aiobj between nLevelDiff and nLevelDiff2 who is btIsAllied within iDistance + //must be (btOp) wChrNum + GETAICONDDATA(027); + + short chrCount = 0; + dword eCount = 0; + int nearestDistance = 9999999; + int searchDistance = data->iDistance;// * 10; + + CMap* map = GServer->MapList.Index[entity->Position->Map]; + dword entityCount = map->MonsterList.size(); + + if(data->btIsAllied == 1) + { + for(UINT j=0;jMonsterList.size();j++) + { + CMonster* other = map->MonsterList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + int levelDiff = (entity->Stats->Level - other->Stats->Level); + if(levelDiff < data->nLevelDiff) continue; + if(levelDiff > data->nLevelDiff2) continue; + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + chrCount++; + if(iDistance < nearestDistance) + { + nearestDistance = iDistance; + entity->nearChar = other; + } + } + entity->findChar = entity->nearChar; + if(OperateValues(data->btOp, &chrCount, data->wChrNum)) return AI_SUCCESS; + } + else + { + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* other = map->PlayerList.at(j); + if(eCount >= entityCount) break; + if(other == NULL) continue; + eCount++; + if(other == entity) continue; + int levelDiff = (entity->Stats->Level - other->Stats->Level); + if(levelDiff < data->nLevelDiff) continue; + if(levelDiff > data->nLevelDiff2) continue; + int iDistance = (int)GServer->distance( other->Position->current, entity->Position->current ); + if(iDistance > searchDistance) continue; + chrCount++; + if(iDistance < nearestDistance){ + nearestDistance = iDistance; + entity->nearChar = other; + } + } + entity->findChar = entity->nearChar; + if(OperateValues(data->btOp, &chrCount, data->wChrNum)) return AI_SUCCESS; + } + + ////Log(MSG_INFO,"AICOND(027) failure"); + return AI_FAILURE; +} + +//Check Variable (4) +AICOND(028) +{ + //word nVarIDX; //Pos: 0x00 + //byte btOp; //Pos: 0x08 + //dword iValue; //Pos: 0x04 + + //Get_AiVAR + GETAICONDDATA(028); + CMonster* thisMonster = reinterpret_cast(entity); + if(data->nVarIDX >19)return AI_FAILURE; + UINT tempval = thisMonster->AIVar[data->nVarIDX]; + switch(data->btOp) + { + case 0: + if(tempval == data->iValue) + return AI_SUCCESS; + break; + case 1: + if(tempval > data->iValue) + return AI_SUCCESS; + break; + case 2: + if(tempval >= data->iValue) + return AI_SUCCESS; + break; + case 3: + if(tempval < data->iValue) + return AI_SUCCESS; + break; + case 4: + if(tempval <= data->iValue) + return AI_SUCCESS; + break; + case 10: + if(tempval != data->iValue) + return AI_SUCCESS; + break; + default: + return AI_FAILURE; + break; + } + return AI_FAILURE; +} + +//Check Target (2) +AICOND(029) +{ + //byte btTargetType; //Pos: 0x00 + //UNKNOWN :@ + GETAICONDDATA(029); + //Log(MSG_DEBUG,"aicon 029 check target type %i",data->btTargetType); + return AI_FAILURE; +} + +//Timer since spawn (seconds). +AICOND(030) +{ + GETAICONDDATA(030); + //dword Timer; //Pos: 0x00 timer. + //Timer is stored in seconds so we need to multiply it by 1000 + CMonster* thisMonster = reinterpret_cast(entity); + clock_t etime = clock() - thisMonster->SpawnTime; + if(etime > (data->Timer * 1000)) + return AI_SUCCESS; + return AI_FAILURE; +} + +//from here on this is 100% server sided. AIP scripts with these elements in should not be uploaded to the client + +// TDEF code. Check current position against Destination +AICOND(031) +{ + + return AI_FAILURE; +} + + diff --git a/Src/World Server/aip/AiConditions.h b/Src/World Server/aip/AiConditions.h new file mode 100644 index 0000000..7086469 --- /dev/null +++ b/Src/World Server/aip/AiConditions.h @@ -0,0 +1,266 @@ +// Props to ExJam for this code :D +#define AICOND(cond) int F_AI_COND_ ## cond (class CWorldServer* server, class CCharacter* entity, byte* raw) +typedef int (*fpAiCond)(class CWorldServer*, class CCharacter*, byte*); +#define GETAICONDDATA(cond) STR_AI_COND_ ## cond * data = (STR_AI_COND_ ## cond *)raw; + +AICOND(000);AICOND(001);AICOND(002); +AICOND(003);AICOND(004);AICOND(005); +AICOND(006);AICOND(007);AICOND(008); +AICOND(009);AICOND(010);AICOND(011); +AICOND(012);AICOND(013);AICOND(014); +AICOND(015);AICOND(016);AICOND(017); +AICOND(018);AICOND(019);AICOND(020); +AICOND(021);AICOND(022);AICOND(023); +AICOND(024);AICOND(025);AICOND(026); +AICOND(027);AICOND(028);AICOND(029); +AICOND(030);AICOND(031);AICOND(032); + +#define AI_COND_COUNT 33 + +struct STR_AI_COND_000 +{ + //Hold or Attack (0x04 bytes) + byte cNotfightOrDelay; //Pos: 0x00 +}; + +struct STR_AI_COND_001 +{ + //Damage (0x08 bytes) + dword iDamage; //Pos: 0x00 + byte cRecvOrGive; //Pos: 0x04 +}; + +struct STR_AI_COND_002 +{ + //Check Near (1) (0x0c bytes) + int iDistance; //Pos: 0x00 + union + { + byte btIsAllied; //Pos: 0x04 + word nIsAllied; + }; + short nLevelDiff; //Pos: 0x06 + short nLevelDiff2; //Pos: 0x08 + word wChrNum; //Pos: 0x0a +}; + +struct STR_AI_COND_003 +{ + //Check Distance (1) (0x04 bytes) + int iDistance; //Pos: 0x00 +}; + +struct STR_AI_COND_004 +{ + //Check Distance (2) (0x08 bytes) + int iDistance; //Pos: 0x00 + byte cMoreLess; //Pos: 0x04 +}; + +struct STR_AI_COND_005 +{ + //Check AB (1) (0x0c bytes) + byte cAbType; //Pos: 0x00 + int iDiff; //Pos: 0x04 + byte cMoreLess; //Pos: 0x08 +}; + +struct STR_AI_COND_006 +{ + //Check HP (0x08 bytes) + dword wHP; //Pos: 0x00 + byte cMoreLess; //Pos: 0x04 +}; + +struct STR_AI_COND_007 +{ + //Random Chance (0x04 bytes) + byte cPercent; //Pos: 0x00 +}; + +struct STR_AI_COND_008 +{ + //Check Near (2) (0x0c bytes) + dword iDistance; //Pos: 0x00 + short nLevelDiff; //Pos: 0x04 + short nLevelDiff2; //Pos: 0x06 + byte btIsAllied; //Pos: 0x08 +}; + +struct STR_AI_COND_009 +{ + //Unknown (0x00 bytes) +}; + +struct STR_AI_COND_010 +{ + //Check AB (2) (0x04 bytes) + byte cAbType; //Pos: 0x00 + byte cMoreLess; //Pos: 0x01 +}; + +struct STR_AI_COND_011 { + //Check AB (3) (0x0c bytes) + byte cAbType; //Pos: 0x00 + word tBuffer; //Pos: 0x02 + dword iValue; //Pos: 0x04 + byte cMoreLess; //Pos: 0x08 +}; + +struct STR_AI_COND_012 +{ + //Check Time (1) (0x04 bytes) + byte cWhen; //Pos: 0x00 +}; + +struct STR_AI_COND_013 +{ + //Check Target (1) (0x04 bytes) + byte btCheckTarget; //Pos: 0x00 + byte btStatusType; //Pos: 0x01 + byte btHave; //Pos: 0x02 +}; + +struct STR_AI_COND_014 +{ + //Check Variable (1) (0x0c bytes) + byte btVarIDX; //Pos: 0x00 + word tBuffer; //Pos: 0x02 + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_COND_015 +{ + //Check Variable (2) (0x0c bytes) + word nVarIDX; //Pos: 0x00 + word tBuffer; //Pos: 0x02 + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_COND_016 +{ + //Check Variable (3) (0x00 bytes) + word nVarIDX; //Pos: 0x00 + word tBuffer; //Pos: 0x02 + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_COND_017 +{ + //Select NPC (0x04 bytes) + dword iNpcNo; //Pos: 0x00 +}; + +struct STR_AI_COND_018 +{ + //Check Distance (3) (0x08 bytes) + dword iDistance; //Pos: 0x00 + byte btOp; //Pos: 0x04 +}; + +struct STR_AI_COND_019 +{ + //Check Time (2) (0x08 bytes) + dword ulTime; //Pos: 0x00 + dword ulEndTime; //Pos: 0x04 +}; + +struct STR_AI_COND_020 +{ + //Check AB (4) (0x0c bytes) + union + { + byte btAbType; //Pos: 0x00 + dword iAbType; + }; + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_COND_021 +{ + //Unknown (0x00 bytes) +}; + +struct STR_AI_COND_022 +{ + //Unknown (0x00 bytes) +}; + +struct STR_AI_COND_023 +{ + //Check Time (3) (0x08 bytes) + dword ulTime; //Pos: 0x00 + dword ulEndTime; //Pos: 0x04 +}; + +struct STR_AI_COND_024 +{ + //Check Date Time (4) (0x08 bytes) + byte btDate; //Pos: 0x00 + byte btHour1; //Pos: 0x01 + byte btMin1; //Pos: 0x02 + byte btHour2; //Pos: 0x03 + byte btMin2; //Pos: 0x04 +}; + +struct STR_AI_COND_025 +{ + //Check Weekday Time (5) (0x08 bytes) + byte btWeekDay; //Pos: 0x00 + byte btHour1; //Pos: 0x01 + byte btMin1; //Pos: 0x02 + byte btHour2; //Pos: 0x03 + byte btMin2; //Pos: 0x04 +}; + +struct STR_AI_COND_026 +{ + //Check Position (0x04 bytes) + word nMin; //Pos: 0x00 + word nMax; //Pos: 0x02 +}; + +struct STR_AI_COND_027 +{ + //Check Near Character (0x10 bytes) + int iDistance; //Pos: 0x00 + byte btIsAllied; //Pos: 0x04 + short nLevelDiff; //Pos: 0x06 + short nLevelDiff2; //Pos: 0x08 + short wChrNum; //Pos: 0x0a + byte btOp; //Pos: 0x0c +}; + +struct STR_AI_COND_028 +{ + //Check Variable (4) (0x0c bytes) + word nVarIDX; //Pos: 0x00 + dword iValue; //Pos: 0x04 + byte btOp; //Pos: 0x08 +}; + +struct STR_AI_COND_029 +{ + //Check Target (2) (0x04 bytes) + byte btTargetType; //Pos: 0x00 +}; + +struct STR_AI_COND_030 +{ + //Unknown (0x04 bytes) + dword Timer; //Pos: 0x00 +}; + +struct STR_AI_COND_031 +{ + //no data needed here +}; + +struct STR_AI_COND_032 +{ + //no data needed here +}; diff --git a/Src/World Server/aip/Aip.cpp b/Src/World Server/aip/Aip.cpp new file mode 100644 index 0000000..9be2ded --- /dev/null +++ b/Src/World Server/aip/Aip.cpp @@ -0,0 +1,167 @@ +// Props to ExJam for this code :D +#include "../WorldServer.h" + +void CWorldServer::ReadAIP(strings path, dword index) +{ + CRoseFile* fh = new CRoseFile(path, FM_READ | FM_BINARY); + + if(fh->IsOpen()) + { // goto done; + //Log(MSG_LOAD, "Loading %s ", path); + dword BlockCount = fh->Get(); + dword mintime = fh->Get(); + dword mindamage = fh->Get(); + dword titlestringlength = fh->Get(); + strings title = new char[titlestringlength+1]; + strings title2 = new char[32+1]; + fh->Read(title, titlestringlength, 1); + for(dword i = 0; i < BlockCount; i++) + { + titlestringlength = 32; + fh->Read(title2, titlestringlength, 1);//on spawn-idle-attack-attacked-killed target-dead + dword RecordCount = fh->Get(); + + for(dword j = 0; j < RecordCount; j++) + { + CAip* script = new CAip(); + fh->Read(title2, titlestringlength, 1);// 00 ============================== + script->ConditionCount = fh->Get(); + if(script->ConditionCount > 0) + { + script->Conditions = new CAip::SAipDatum*[script->ConditionCount]; + for(dword k = 0; k < script->ConditionCount; k++) + { + CAip::SAipDatum* data = new CAip::SAipDatum(); + data->size = fh->Get(); + data->opcode = fh->Get() - 0x04000001; + data->data = new byte[data->size - 8]; + fh->Read(data->data, data->size - 8, 1); + script->Conditions[k] = data; + } + } + else + { + script->Conditions = NULL; + } + script->ActionCount = fh->Get(); + + if(script->ActionCount > 0) + { + script->Actions = new CAip::SAipDatum*[script->ActionCount]; + for(dword k = 0; k < script->ActionCount; k++) + { + CAip::SAipDatum* data = new CAip::SAipDatum(); + data->size = fh->Get(); + data->opcode = fh->Get() - 0x0B000001; + data->data = new byte[data->size - 8]; + fh->Read(data->data, data->size - 8, 1); + script->Actions[k] = data; + } + } + else + { + script->Actions = NULL; + } + script->AInumber = index; + script->AipID=((index*0x10000)+(i*0x100)+j); + script->minDamage = mindamage; + script->minTime = mintime; + script->recordcount[i] = RecordCount; + AipList.push_back( script ); + } + } + } + else + Log( MSG_ERROR, "AIP File: '%s' failed to load properly", path ); + fh->Close(); + delete fh; +} + +void CWorldServer::LoadAipData() +{ + CStrStb* stbAip = new CStrStb("3DDATA\\STB\\FILE_AI.STB"); + Log(MSG_INFO, "Loading AIP files"); + for(dword i = 1; i < stbAip->Rows(); i++) + { + if(stbAip->Data(i, 0)) + { + GServer->ReadAIP(stbAip->Data(i, 0),i); + } + } +//Log(MSG_INFO, "%i aip entrys",AipList.size()); + Log(MSG_INFO, "Finished loading aip data "); + + aiCondFunc[0] = &F_AI_COND_000; + aiCondFunc[1] = &F_AI_COND_001; + aiCondFunc[2] = &F_AI_COND_002; + aiCondFunc[3] = &F_AI_COND_003; + aiCondFunc[4] = &F_AI_COND_004; + aiCondFunc[5] = &F_AI_COND_005; + aiCondFunc[6] = &F_AI_COND_006; + aiCondFunc[7] = &F_AI_COND_007; + aiCondFunc[8] = &F_AI_COND_008; + aiCondFunc[9] = &F_AI_COND_009; + aiCondFunc[10] = &F_AI_COND_010; + aiCondFunc[11] = &F_AI_COND_011; + aiCondFunc[12] = &F_AI_COND_012; + aiCondFunc[13] = &F_AI_COND_013; + aiCondFunc[14] = &F_AI_COND_014; + aiCondFunc[15] = &F_AI_COND_015; + aiCondFunc[16] = &F_AI_COND_016; + aiCondFunc[17] = &F_AI_COND_017; + aiCondFunc[18] = &F_AI_COND_018; + aiCondFunc[19] = &F_AI_COND_019; + aiCondFunc[20] = &F_AI_COND_020; + aiCondFunc[21] = &F_AI_COND_021; + aiCondFunc[22] = &F_AI_COND_022; + aiCondFunc[23] = &F_AI_COND_023; + aiCondFunc[24] = &F_AI_COND_024; + aiCondFunc[25] = &F_AI_COND_025; + aiCondFunc[26] = &F_AI_COND_026; + aiCondFunc[27] = &F_AI_COND_027; + aiCondFunc[28] = &F_AI_COND_028; + aiCondFunc[29] = &F_AI_COND_029; + aiCondFunc[30] = &F_AI_COND_030; + + aiActFunc[0] = &F_AI_ACT_000; + aiActFunc[1] = &F_AI_ACT_001; + aiActFunc[2] = &F_AI_ACT_002; + aiActFunc[3] = &F_AI_ACT_003; + aiActFunc[4] = &F_AI_ACT_004; + aiActFunc[5] = &F_AI_ACT_005; + aiActFunc[6] = &F_AI_ACT_006; + aiActFunc[7] = &F_AI_ACT_007; + aiActFunc[8] = &F_AI_ACT_008; + aiActFunc[9] = &F_AI_ACT_009; + aiActFunc[10] = &F_AI_ACT_010; + aiActFunc[11] = &F_AI_ACT_011; + aiActFunc[12] = &F_AI_ACT_012; + aiActFunc[13] = &F_AI_ACT_013; + aiActFunc[14] = &F_AI_ACT_014; + aiActFunc[15] = &F_AI_ACT_015; + aiActFunc[16] = &F_AI_ACT_016; + aiActFunc[17] = &F_AI_ACT_017; + aiActFunc[18] = &F_AI_ACT_018; + aiActFunc[19] = &F_AI_ACT_019; + aiActFunc[20] = &F_AI_ACT_020; + aiActFunc[21] = &F_AI_ACT_021; + aiActFunc[22] = &F_AI_ACT_022; + aiActFunc[23] = &F_AI_ACT_023; + aiActFunc[24] = &F_AI_ACT_024; + aiActFunc[25] = &F_AI_ACT_025; + aiActFunc[26] = &F_AI_ACT_026; + aiActFunc[27] = &F_AI_ACT_027; + aiActFunc[28] = &F_AI_ACT_028; + aiActFunc[29] = &F_AI_ACT_029; + aiActFunc[30] = &F_AI_ACT_030; + aiActFunc[31] = &F_AI_ACT_031; + aiActFunc[32] = &F_AI_ACT_032; + aiActFunc[33] = &F_AI_ACT_033; + aiActFunc[34] = &F_AI_ACT_034; + aiActFunc[35] = &F_AI_ACT_035; + aiActFunc[36] = &F_AI_ACT_036; + aiActFunc[37] = &F_AI_ACT_037; + + delete stbAip; +} + diff --git a/Src/World Server/aip/Aip.h b/Src/World Server/aip/Aip.h new file mode 100644 index 0000000..48a23df --- /dev/null +++ b/Src/World Server/aip/Aip.h @@ -0,0 +1,29 @@ +// Props to ExJam for this code :] +class CAip +{ + public: + struct SAipDatum + { + int size; + int opcode; + byte* data; + }; + + dword AInumber; + dword AipID; + dword minDamage; + dword minTime; + byte recordcount[6]; + + dword ConditionCount; + SAipDatum** Conditions; + + dword ActionCount; + SAipDatum** Actions; + int AipVar; + private: +}; +#define AI_FAILURE 8 +#define AI_SUCCESS 7 +#include "AiConditions.h" +#include "AiActions.h" diff --git a/Src/World Server/battle.cpp b/Src/World Server/battle.cpp new file mode 100644 index 0000000..79d7db4 --- /dev/null +++ b/Src/World Server/battle.cpp @@ -0,0 +1,975 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +void CCharacter::DoAttack( ) +{ + //if(Status->Sleep <= 0xff) + //{ + // ClearBattle( Battle ); + // return; // can't take any action while we are asleep can we? + //} + //if(IsPlayer()) + //{ + // Log(MSG_INFO,"DoAttack Attack type %i selected. Chartype = %i casting = %i",Battle->atktype, CharType, Battle->iscasting); + //} + //if(CharType = 4) //it's an npc + //{ + // Log(MSG_DEBUG,"NPC battle sequence"); + //} + if(Battle->iscasting == 1) + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + //Log(MSG_DEBUG,"Iscasting detected as true."); + if(CanAttack()) + { + if(IsPlayer()) + { + //Log(MSG_DEBUG,"Should reach here once on normal attack. iscasting state = %i attack type = %i",Battle->iscasting, Battle->atktype); + + if(Battle->atktype == 3) //buff + { + + ClearBattle( Battle ); + return; + } + else // if not buffing then resume normal attack. + { + //ClearBattle( Battle ); + StartAction(Enemy, NORMAL_ATTACK, 0); + } + } + else //monsters should always resume normal attack + { + StartAction(Enemy, NORMAL_ATTACK, 0); + } + } + else + { + return; + } + return; + } + if(!CanAttack()) + { + return; + } + if(IsSummon()) + { + //Log(MSG_DEBUG,"Summon attacking in function DoAttack"); + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) // || (Battle->atktype != SKILL_AOE && Battle->atktype != BUFF_AOE)) + { + Log(MSG_DEBUG,"No Enemy found"); + ClearBattle( Battle ); + return; + } + if(this == Enemy) //summoned monster is attacking itself. It has been observed to happen + { + //Log(MSG_INFO,"Clearing Battle for this summon"); + ClearBattle( Battle ); + return; + } + if(Enemy->IsSummon()) + { + CMonster* thisMonster = reinterpret_cast(this); + if(thisMonster == NULL) + { + ClearBattle( Battle ); + return; + } + CMonster* otherMonster = reinterpret_cast(Enemy); + if(otherMonster == NULL) + { + ClearBattle( Battle ); + return; + } + if(thisMonster->owner == otherMonster->owner) + { + //Log(MSG_INFO,"Clearing Battle for this summon"); + ClearBattle( Battle ); + return; + } + } + } + + CMap* map = GServer->MapList.Index[Position->Map]; + //if(IsPlayer()) + //{ + // Log(MSG_INFO,"DoAttack Battle Attack type = %i",Battle->atktype); + //} + switch(Battle->atktype) + { + case NORMAL_ATTACK://normal attack + { + //if(!Status->CanAttack) + // return; //can't attack right now. + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + if(Enemy == this) + { + //Log(MSG_INFO,"WTF?? I AM trying to attack myself"); + ClearBattle( Battle ); + } + if(IsTargetReached( Enemy )) + { + //if(IsMonster()) + // Log(MSG_INFO,"Monster is trying to hit the player"); + NormalAttack( Enemy ); + if (Enemy->IsMonster()) // do monster AI script when monster is attacked. + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + //monster->DoAi(monster->thisnpc->AI, 3); + monster->DoAi(monster->monAI, 3); + } + } + } + break; + case SKILL_ATTACK://skill attack + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + if(IsTargetReached( Enemy, skill )) + { + //Log(MSG_DEBUG,"Skill Attack reached target and started"); + SkillAttack( Enemy, skill ); + if (Enemy->IsMonster()) + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + monster->DoAi(monster->monAI, 3); + Battle->atktype = NORMAL_ATTACK; + } + } + //else + //{ + //Log(MSG_DEBUG,"Skill Attack failed to reach target"); + //} + } + break; + case SKILL_BUFF://buffs + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + //Log(MSG_DEBUG,"this target is NULL"); + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //Log(MSG_DEBUG,"this skill is NULL"); + return; + } + + if(IsTargetReached( Enemy, skill )) + { + //Log(MSG_DEBUG,"Skill successfully cast"); + BuffSkill( Enemy, skill ); + } + } + break; + case SKILL_AOE: + //case SKILL_SELF: //this is impossible. it can never be set + case AOE_TARGET: + { + + + //Log(MSG_DEBUG,"Called skill type %i",Battle->atktype); + CCharacter* Enemy = NULL; + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + ClearBattle( Battle ); + return; + } + if(Battle->atktype == AOE_TARGET) + { + float distance = GServer->distance( Position->current, Position->destiny ); + if(distance <= skill->range) + { + Position->destiny = Position->current; + AoeSkill( skill, Enemy ); + } + } + else + { + Position->skilltargetpos = Position->current; + AoeSkill( skill, NULL ); + } + } + break; + case BUFF_SELF: + { + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + BuffSkill( this, skill ); + } + break; + case BUFF_AOE: + { + //Log(MSG_INFO,"BUFF AOE selected"); + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + return; + } + AoeBuff( skill ); + } + break; + default: + return; + break; + } +} + + +// do normal attack +void CCharacter::NormalAttack( CCharacter* Enemy ) +{ + Enemy->OnBeAttacked( this ); + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(NormalAttack) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + + + // Even newer formula based on Jayce's idea + //dodge. gives a percentage chance + unsigned int hitpower = 0; + bool critical = false; + unsigned int totalchance = Stats->Accury + Enemy->Stats->Dodge; + double acdm = Stats->Accury * 100 / totalchance; + unsigned int hitchance = (unsigned int)floor(acdm); + if(GServer->RandNumber( 0, 100 ) > hitchance) + { + hitpower = 0; // dodged + //Log(MSG_DEBUG,"accuracy %i dodge %i",Stats->Accury,Enemy->Stats->Dodge); + //Log(MSG_DEBUG,"Dodged total %i mod %f hit chance %i",totalchance,acdm,hitchance); + } + else + { + //level adjustment. Not yet implemented + unsigned int leveltotal = Stats->Level + Enemy->Stats->Level; + double levelmod = Stats->Level * 200 / leveltotal; + + //calculate damage + if(Stats->magicattack == 1) //magic attacks such as wands and some monsters + { + unsigned int totalpower = Stats->Attack_Power + Enemy->Stats->Magic_Defense; + double hitmod = Stats->Attack_Power * 100 / totalpower + Stats->Level; //percentage of hitpower to use * 100 + hitpower = (unsigned int)floor((double)(hitmod * Stats->Attack_Power / 100)); + } + else + { + unsigned int totalpower = Stats->Attack_Power + Enemy->Stats->Defense; + double hitmod = Stats->Attack_Power * 100 / totalpower + Stats->Level; //percentage of hitpower to use * 100 + hitpower = (unsigned int)floor((double)(Stats->Attack_Power * hitmod / 100)); + //Log(MSG_DEBUG,"Normal Attack: AP %i DEF %i",Stats->Attack_Power,Enemy->Stats->Defense); + //Log(MSG_DEBUG,"Normal Attack: hit total %i hitmod %f hit power %i",totalpower,hitmod,hitpower); + } + //add some randomness. + or - 5% of hitpower + int min = (int)floor((double)(hitpower * 0.85)); + int max = (int)floor((double)(hitpower * 1.15)); + int dif = max - min; + int mod = GServer->RandNumber( 0, dif ); + hitpower = min + mod; + //Log(MSG_DEBUG,"min %i max %i dif %i mod %i modified hit power %i", min, max, dif, mod, hitpower); + if(hitpower <= 5) + { + hitpower = 5; + } + else + { + if(GServer->RandNumber(0,300)Critical) + { + hitpower = (long int)floor(hitpower * 1.5); + critical = true; + //Log(MSG_DEBUG,"CRITICAL hit power %i", hitpower); + } + } + if (hitpower > 0x7ff)//2047 packet size limit. + hitpower = 0x7ff; + //Log(MSG_DEBUG,"Successful hit hit chance %i damage %i",hitchance, hitpower); + } + + + //end jayce formula + Battle->atktarget = Battle->target; + //if(IsMonster()) + //{ + //Log(MSG_INFO,"Monster hits player for %i damage. Attack target = %i",hitpower,Battle->atktarget); + //} + //Log( MSG_INFO, "hitpower %i. Attack %f ", hitpower,attack ); + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, hitpower ); + Enemy->damagecounter += hitpower;// is for AI + } + Enemy->Stats->HP -= hitpower; + //if(IsPlayer()) + // Log(MSG_DEBUG,"Player attack did damage %i",hitpower); + //if(IsMonster()) + // Log(MSG_DEBUG,"Monster attack did damage %i",hitpower); + + + + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->atktarget ); + + if(Enemy->IsDead()) + { + CDrop* thisdrop = NULL; + ADDWORD ( pak, (hitpower | ( critical?0xb000:0x8000 ) )); + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + int thisdroprate = Stats->itemdroprate + GServer->Config.DROP_RATE; + //Log(MSG_DEBUG,"Monster killed. calling drops function %i times",thisdroprate); + for(int j=0;jGetDrop( ); + if(thisdrop != NULL) + { + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + } + GServer->SendToVisible( &pak, Enemy ); + ClearBattle(Battle); + //ClearBattle(Enemy->Battle); + if(Enemy->IsMonster()) + { + CMonster* monster = reinterpret_cast(Enemy); + if(monster == NULL) + { + //Log(MSG_DEBUG,"Monster killed. Failed to create monster object"); + return; + } + else + { + //Log(MSG_DEBUG,"Monster killed. Setting death delay timer"); + monster->DeathDelayTimer = clock(); + } + } + OnEnemyDie( Enemy ); + ClearBattle(Battle); + GServer->SendToVisible( &pak, Enemy ); + } + else + { + ADDWORD ( pak, (hitpower|(hitpower>0?(critical?0x4000:0):0))); + //ADDDWORD ( pak, (hitpower>0?(critical?12:0):0) ); + GServer->SendToVisible( &pak, Enemy ); + } + ReduceABC( ); + Battle->lastAtkTime = clock( ); + //Battle->lastAtkTime = Battle->NextAtkTime; +} + +// do skill attack +bool CCharacter::SkillAttack( CCharacter* Enemy, CSkills* skill ) +{ + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(SkillAttack) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + UseAtkSkill( Enemy, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) + Stats->MP = 0; + //Battle->atktype = NORMAL_ATTACK; + //Battle->skilltarget = 0; + //Battle->atktarget = Battle->target; + //Battle->skillid = 0; + GServer->DoSkillScript( this, skill ); + Battle->lastAtkTime = clock( ); + //Battle->lastAtkTime = Battle->NextAtkTime; + if(Enemy->IsDead()) + ClearBattle(Battle); + + + Battle->iscasting = 1; + //Log(MSG_DEBUG,"Cast a skill. Iscasting set to true"); + return true; +} + +// do buff skill +bool CCharacter::BuffSkill( CCharacter* Target, CSkills* skill ) +{ + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(BuffSkill) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + //Log(MSG_DEBUG,"applied buff skill %i",skill->id); + UseBuffSkill( Target, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + GServer->DoSkillScript( this, skill ); //summons a monster. Why do we do this for all skills? + if(!IsMonster()) + { + //ClearBattle( Battle ); + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + } + else //Monsters need to be reset to normal attack and clear skill attacks. + { + //Battle->atktarget = Battle->target; + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + //StartAction(Target, NORMAL_ATTACK); + } + //Battle->lastAtkTime = clock( ); + Battle->lastAtkTime = Battle->NextAtkTime; + Battle->iscasting = 1; + return true; +} +//arnold +// do AoE skill +bool CCharacter::AoeSkill( CSkills* skill, CCharacter* Enemy ) +{ + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(AoeSkill) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + //Log(MSG_INFO,"Doing AOE Skill"); + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + CMap* map = GServer->MapList.Index[Position->Map]; + + //PY replacement code + if(IsPlayer() || IsSummon()) + { + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster == NULL) continue; + if(monster->clientid == clientid) continue; + if(monster->IsSummon( ) && (map->allowpvp == 0 || monster->owner == clientid)) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill,1 ); + } + } + if(IsMonster() && !IsSummon()) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + if(player->clientid == clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos, player->Position->current, (float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill,1 ); + } + } + //PY end + + // this code appears to me to be completely wrong replaced with code above + /* + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster->clientid == clientid) continue; + if(IsSummon( ) || IsPlayer( )) + { + if(monster->IsSummon( ) && (map->allowpvp==0 || monster->owner == clientid)) continue; + } + else + { + if(!monster->IsSummon( )) continue; + } + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill ); + } + if(map->allowpvp!=0 || (IsMonster( ) && !IsSummon( ))) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player->clientid==clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos,player->Position->current,(float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill ); + } + }*/ + Battle->iscasting = 1; + //Log(MSG_DEBUG,"Cast a skill. Iscasting set to true"); + if(Enemy != NULL) + { + if(Enemy->IsDead( )) + ClearBattle( Battle ); + } + else + ClearBattle( Battle ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + Battle->lastAtkTime = clock( ); + //Battle->lastAtkTime = Battle->NextAtkTime; + //Send skill animation to the world + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + return true; +} + +bool CCharacter::AoeBuff( CSkills* skill ) +{ + //Log(MSG_INFO,"BUFF AOE called with skill target ",skill->target); + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(AoeBuff) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + CMap* map = GServer->MapList.Index[Position->Map]; + + if(CharType != 2) //player || Summon || NPC. Anything that is not a monster + { + //Log(MSG_INFO,"It's a Player, Summon or NPC"); + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player == NULL)continue; + switch(skill->target) + { + case 1: //tPartyMember + if(player->Party->party == GetParty()) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough party member"); + if (player == this) //it is me so I had better cast it anyway ^-^ + { + UseBuffSkill( (CCharacter*)player, skill ); + } + else if(player->Party->party != NULL) // Don't cast the skill on players who are not in any party at all. + { + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + } + break; + case 2: //tClanMember + if (player->Clan == GetClan( ) && (player->Clan != 0 || player == this)) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough clan member"); + if(GServer->IsMonInCircle( Position->current, player->Position->current, (float)skill->aoeradius + 1 ) ) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough allied Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 5: //tHostileCharacter + { + if (CharType == 2) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough hostile Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + } + break; + default: + //Log(MSG_INFO,"No valid skill target found"); + break; + } + } + } + else if (CharType == 2) //monster + { + //Log(MSG_INFO,"It's a monster"); + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster == NULL)continue; + switch(skill->target) + { + case tPartyMember: // party + break; + case 2: //tClanMember + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough monster"); + if(GServer->IsMonInCircle( Position->current,monster->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)monster, skill ); + } + break; + case tHostileCharacter: + break; + default: + break; + } + } + } + + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + //Battle->atktarget = Battle->target; + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + ClearBattle( Battle ); + Battle->lastAtkTime = clock( ); + //Battle->lastAtkTime = Battle->NextAtkTime; + Battle->iscasting = 1; + //Send skill animation to the world + BEGINPACKET( pak1, 0x7b9); + ADDWORD ( pak1, clientid); + ADDWORD ( pak1, Battle->skillid); + ADDWORD ( pak1, 1); + GServer->SendToVisible( &pak1, this ); + return true; +} + + + +// use skill attack +void CCharacter::UseAtkSkill( CCharacter* Enemy, CSkills* skill, bool deBuff) +{ + + // Even newer formula based on Jayce's idea + //dodge. gives a percentage chance + Battle->iscasting = 1; + unsigned int hitpower = 0; + unsigned int Attack = 0; + // skill attacks now based on a percentage of base attack power so that they scale with level + Attack = (unsigned int)floor((double)skill->atkpower * Stats->Attack_Power / 100); + int success = GServer->RandNumber(0,100); + if(success <= skill->success) + { + //calculate damage + if(skill->skilltype == 6 || skill->skilltype == 7) //magical damage + { + //unsigned int totalpower = skill->atkpower + Enemy->Stats->Magic_Defense; + unsigned int totalpower = Attack + Enemy->Stats->Magic_Defense; + double hitmod = Attack * 100 / totalpower; //percentage of hitpower to use * 100 + hitpower = (unsigned int)floor((double)(hitmod * Attack / 100)); + //Log(MSG_DEBUG,"Skill magic: AP %i DEF %i",Stats->Attack_Power,Enemy->Stats->Defense); + //Log(MSG_DEBUG,"Skill magic: Skill power %i. Attack %i",skill->atkpower,Attack); + //Log(MSG_DEBUG,"Skill magic: hit total %i mod %f hit power %i",totalpower,hitmod,hitpower); + } + else + { + //unsigned int totalpower = skill->atkpower + Enemy->Stats->Defense; + unsigned int totalpower = Attack + Enemy->Stats->Defense; + double hitmod = Attack * 100 / totalpower; //percentage of hitpower to use * 100 + hitpower = (unsigned int)floor((double)(hitmod * Attack / 100)); + //Log(MSG_DEBUG,"Skill physical: AP %i DEF %i",Stats->Attack_Power,Enemy->Stats->Defense); + //Log(MSG_DEBUG,"Skill physical: Skill power %i. Attack %i",skill->atkpower,Attack); + //Log(MSG_DEBUG,"skill physical: hit total %i mod %f hit power %i",totalpower,hitmod,hitpower); + } + //add some randomness. + or - 5% of hitpower + int min = (int)floor((double)(hitpower * 0.85)); + int max = (int)floor((double)(hitpower * 1.15)); + int dif = max - min; + int mod = GServer->RandNumber( 0, dif ); + hitpower = min + mod; + } + + bool bflag = false; + Enemy->OnBeAttacked( this ); + if(hitpower <= 0) hitpower = 0; + if(IsPlayer()) + { + hitpower += (int)(hitpower * (Stats->ExtraDamage))/100; + } + if (skill->atkpower == 0) + { + hitpower = 0; // If the skill has a 0 atk power, it isn't meant to damage + Log( MSG_INFO, "This skill does no damage" ); + //ClearBattle( Battle ); + return; // probably need to add some alternative code here for legitimate skills that have no attack power. + } + if(hitpower > 2046)hitpower = 2046; + //Log(MSG_DEBUG,"Cast a skill. damage is %i",skillpower); + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, hitpower ); + Enemy->damagecounter += hitpower;// is for AI + } + //Log( MSG_INFO, "modified skillpower = %i",hitpower ); + Enemy->Stats->HP -= hitpower; + //if(IsPlayer()) + // Log(MSG_DEBUG,"Player Spell did damage %i",hitpower); + //if(IsMonster()) + // Log(MSG_DEBUG,"Monster spell did damage %i",hitpower); + BEGINPACKET( pak, 0x7b6 ); + ADDWORD ( pak, Enemy->clientid ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, 0x000007f8 ); + ADDBYTE ( pak, 0x00 ); + if (Enemy->Stats->HP < 1) + { + ADDWORD ( pak, hitpower + 0x8000 ); + } + else + { + ADDWORD ( pak, hitpower + 0x2000 ); + } + if(Enemy->IsDead()) + { + //Log(MSG_DEBUG,"Enemy died"); + CDrop* thisdrop = NULL; + ADDDWORD ( pak, 16 ); + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + //Log(MSG_DEBUG,"Monster killed. calling drops function %i times",Stats->itemdroprate); + int thisdroprate = Stats->itemdroprate + GServer->Config.DROP_RATE; + for(int j=0;jGetDrop( ); + if(thisdrop != NULL) + { + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + } + + CMonster* monster = reinterpret_cast(Enemy); + if(Enemy->IsMonster()) + { + if(monster == NULL) + { + //Log(MSG_DEBUG,"Monster killed. Failed to create monster object"); + return; + } + else + { + //Log(MSG_DEBUG,"Monster killed. Setting death delay timer"); + monster->DeathDelayTimer = clock(); + } + } + OnEnemyDie( Enemy ); + ClearBattle(Battle); + GServer->SendToVisible( &pak, Enemy ); + } + else + { + ADDDWORD ( pak, 4 ); + GServer->SendToVisible( &pak, Enemy ); + bflag = GServer->AddBuffs( skill, Enemy, GetInt( ) ); + + //Send (de)buff information to the whole world + if(skill->nbuffs > 0 && bflag) + { + RESETPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Battle->skilltarget ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( ) ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Enemy ); + } + } + if (deBuff) return; + //Send skill animation to the world + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + //Battle->lastAtkTime = clock( ); + Battle->lastAtkTime = Battle->NextAtkTime; //trying this to time skills better + //Battle->atktype = NORMAL_ATTACK; +} + +// use buff skill +void CCharacter::UseBuffSkill( CCharacter* Target, CSkills* skill, bool deBuff ) +{ + bool bflag = false; + bflag = GServer->AddBuffs( skill, Target, GetInt( ) ); + //Log(MSG_INFO,"In UseBuffSkills, skill %i, nbuffs %i, bflag %i to %u",skill->id,skill->nbuffs,bflag,Target->clientid); + if(skill->nbuffs > 0 && bflag == true) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( )/4 ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Target ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 1"); + } + if (deBuff) return; + BEGINPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 2 nbuffs = %i",skill->nbuffs); + //Log(MSG_INFO,"UseBuffSkill: Target HP = %i",Target->Stats->HP); +} + +// use buff skill from QSD +void CCharacter::UseBuffSkillQSD( CCharacter* Target, CSkills* skill, bool deBuff ) +{ + bool bflag = false; + bflag = GServer->AddBuffs( skill, Target, 1 ); + //Log(MSG_INFO,"In UseBuffSkillsQSD, skill %i, nbuffs %i, bflag %i to %u",skill->id,skill->nbuffs,bflag,Target->clientid); + if(skill->nbuffs > 0 && bflag == true) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, skill->id ); + ADDWORD ( pak, 1 ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Target ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 1"); + } + if (deBuff) return; + BEGINPACKET( pak, 0x7b9); + ADDWORD ( pak, Target->clientid); + ADDWORD ( pak, skill->id); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 2 nbuffs = %i",skill->nbuffs); + //Log(MSG_INFO,"UseBuffSkillQSD: Target HP = %i",Target->Stats->HP); +} + +// Use a skill +bool CCharacter::UseSkill( CSkills* skill, CCharacter *Target ) +{ + if (skill->atkpower > 0) + { + Log(MSG_INFO, "Need to do %i%s %s in range %i to target %i", + ( skill->atkpower ), + ( (skill->range > 0) ? " AOE" : "" ), + ( (GServer->isSkillTargetFriendly(skill)) ? "healing" : "damage" ), + ( skill->range ), + ( skill->target ) ); + } + for (int i = 0; i < 2; i++) { + Log(MSG_INFO, "Status ID: %i", skill->status[i]); + if (skill->status[i] == 0) continue; + CStatus* status = GServer->GetStatusByID(skill->status[i]); + if (status == NULL) continue; + /************************ + We'll probably need to use status->decrease (Figure out whether buf is up/down + status->repeat will tell us whether it's a one-time (Stat Boost [2]), repeat + (Recovery, continueing damage [1]) or Special (Status Effect [3]). + ************************/ + if (status->repeat == 1) // Continuous + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s over %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + } + else if (status->repeat == 2) // Once (Stat Boost) + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s for %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + + } else if (status->repeat == 3) // Status Effect (Poison,etc) + { + Log(MSG_INFO, "Need to give user status effect %i for %i seconds", skill->buff[i], + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ( skill->duration ) ); + } + } + return true; +} + + diff --git a/Src/World Server/battle_old_version.cpp b/Src/World Server/battle_old_version.cpp new file mode 100644 index 0000000..164c132 --- /dev/null +++ b/Src/World Server/battle_old_version.cpp @@ -0,0 +1,802 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +void CCharacter::DoAttack( ) +{ + //if(Status->Sleep <= 0xff) + //{ + // ClearBattle( Battle ); + // return; // can't take any action while we are asleep can we? + //} + if(IsSummon()) + { + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL && Battle->atktype != SKILL_AOE && Battle->atktype != BUFF_AOE) + { + //Log(MSG_DEBUG,"No Enemy found"); + ClearBattle( Battle ); + return; + } + //Log(MSG_DEBUG,"Client id: %i Enemy client id: %i",clientid, Enemy->clientid); + if(this == Enemy) //summoned monster is attacking itself. It has been observed to happen + { + //Log(MSG_INFO,"Clearing Battle for this summon"); + ClearBattle( Battle ); + return; + } + } + CMap* map = GServer->MapList.Index[Position->Map]; + //Log(MSG_INFO,"DoAttack Battle Attack type = %i",Battle->atktype); + switch(Battle->atktype) + { + case NORMAL_ATTACK://normal attack + { + //if(!Status->CanAttack) + // return; //can't attack right now. + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + if(Enemy == this) + { + //Log(MSG_INFO,"WTF?? I AM trying to attack myself"); + ClearBattle( Battle ); + } + if(IsTargetReached( Enemy ) && CanAttack( )) + { + NormalAttack( Enemy ); + if (Enemy->IsMonster()) // do monster AI script when monster is attacked. + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + monster->DoAi(monster->thisnpc->AI, 3); + } + + } + } + break; + case SKILL_ATTACK://skill attack + { + //if(!Status->CanCastSkill) + // return; // can't cast skills right now + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + if(IsTargetReached( Enemy, skill ) && CanAttack( )) + { + SkillAttack( Enemy, skill ); + if (Enemy->IsMonster()) + { + CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); + if(monster == NULL)return; + monster->DoAi(monster->thisnpc->AI, 3); + } + } + } + break; + case SKILL_BUFF://buffs + { + //if(!Status->CanCastSkill) + // return; // can't cast skills right now + CCharacter* Enemy = GetCharTarget( ); + if(Enemy == NULL) + { + //Log(MSG_DEBUG,"this target is NULL"); + ClearBattle( Battle ); + return; + } + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //Log(MSG_DEBUG,"this skill is NULL"); + //ClearBattle( Battle ); + return; + } + //Log(MSG_DEBUG,"Called skill buff code for skill %i",skill->id); + if(IsTargetReached( Enemy, skill ) && CanAttack( )) + { + //Log(MSG_DEBUG,"Skill successfully cast"); + BuffSkill( Enemy, skill ); + } + } + break; + case SKILL_AOE: + //case SKILL_SELF: //this is impossible. it can never be set + case AOE_TARGET: + { + //if(!Status->CanCastSkill) + // return; // can't cast skills right now + //Log(MSG_DEBUG,"Called skill type %i",Battle->atktype); + CCharacter* Enemy = NULL; + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + ClearBattle( Battle ); + return; + } + if(Battle->atktype == AOE_TARGET) + { + float distance = GServer->distance( Position->current, Position->destiny ); + if(distance <= skill->range && CanAttack( )) + { + Position->destiny = Position->current; + AoeSkill( skill, Enemy ); + } + } + else + { + Position->skilltargetpos = Position->current; + AoeSkill( skill, NULL ); + } + } + break; + case BUFF_SELF: + { + //if(!Status->CanCastSkill) + // return; // can't cast skills right now + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + return; + } + BuffSkill( this, skill ); + } + break; + case BUFF_AOE: + { + //if(!Status->CanCastSkill) + // return; // can't cast skills right now + Log(MSG_INFO,"BUFF AOE selected"); + CSkills* skill = GServer->GetSkillByID( Battle->skillid ); + if(skill == NULL) + { + //ClearBattle( Battle ); + + return; + } + AoeBuff( skill ); + } + break; + default: + return; + break; + } +} + + +// do normal attack +void CCharacter::NormalAttack( CCharacter* Enemy ) +{ + Enemy->OnBeAttacked( this ); + Position->destiny = Position->current; + // PY code + float mod = 0; + if(IsPlayer()) + { + mod = 23 + (Stats->Level / 1.5); + } + else + { + mod = 23 + (Stats->Level / 4); + + } + float attack = 0; + if(Stats->magicattack == 1) + { + attack = (float) Stats->Attack_Power / Enemy->Stats->Magic_Defense; + attack *= mod; + } + else + { + attack = (float) Stats->Attack_Power / Enemy->Stats->Defense; + attack *= mod; + } + //PY end + if(attack < 5) attack = 5; + float d_attack = attack / 100; + mod = GServer->RandNumber( 0, 10 ) * d_attack; + attack += mod; + long int hitpower = (long int)floor(attack + GServer->RandNumber(0, 10)); + if(IsPlayer( )) //temp fix to find balance btw monster and player + { + hitpower = (long int)floor(attack * (GServer->Config.PlayerDmg/100.00)); + hitpower+=((hitpower*(Stats->ExtraDamage))/100); + } + if(IsMonster( )) //temp fix to find balance btw monster and player + hitpower = (long int)floor(attack * (GServer->Config.MonsterDmg/100.00)); + bool critical = false; + if(hitpower <= 5) + { + hitpower = 5; + } + else + { + if(GServer->RandNumber(0,300)Critical) + { + hitpower = (long int)floor(hitpower * 1.5); + critical = true; + } + } + // dodge + unsigned int hitvalue = (unsigned int)floor((double)(Stats->Accury * 100 / Enemy->Stats->Dodge)); + if(hitvalue>100) hitvalue = 100; + if(GServer->RandNumber( 0, 100 )>hitvalue) + hitpower = 0; // dodged + if (hitpower > 0x7ff)//2047 packet size limit. + { + hitpower=0x7ff; + } + + //maybe this little line here will fix my ghosty problem + //they are definitely attacking. But no animation so maybe atktarget is empty + Battle->atktarget = Battle->target; + //Well it does make the damage show up at teh client but the animation is still dodgey + + if(IsMonster()) + { + Log(MSG_INFO,"Monster hits player for %i damage. Attack target = %i",hitpower,Battle->atktarget); + } + //Log( MSG_INFO, "hitpower %i. Attack %f ", hitpower,attack ); + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, hitpower ); + Enemy->damagecounter += hitpower;// is for AI + } + Enemy->Stats->HP -= hitpower; + + + + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->atktarget ); +#ifdef USE124 + ADDDWORD ( pak, hitpower ); +#endif + + if(Enemy->IsDead()) + { + CDrop* thisdrop = NULL; +#ifndef USE124 + ADDWORD ( pak, (hitpower | ( critical?0xb000:0x8000 ) )); +#else + ADDDWORD ( pak, critical?28:16 ); +#endif + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + thisdrop = Enemy->GetDrop( ); + if(thisdrop!=NULL) + { + ADDFLOAT ( pak, thisdrop->pos.x*100 ); + ADDFLOAT ( pak, thisdrop->pos.y*100 ); + if(thisdrop->type == 1) + { + ADDWORD( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + { + ADDWORD ( pak, GServer->BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, GServer->BuildItemData( thisdrop->item ) ); + } + ADDWORD ( pak, thisdrop->clientid ); + ADDWORD ( pak, thisdrop->owner ); + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, Enemy, thisdrop ); + OnEnemyDie( Enemy ); + } + else + { +#ifndef USE124 + ADDWORD ( pak, (hitpower|(hitpower>0?(critical?0x4000:0):0))); +#else + ADDDWORD ( pak, (hitpower>0?(critical?12:0):0) ); +#endif + GServer->SendToVisible( &pak, Enemy ); + } + ReduceABC( ); + Battle->lastAtkTime = clock( ); +} + +// do skill attack +bool CCharacter::SkillAttack( CCharacter* Enemy, CSkills* skill ) +{ + Position->destiny = Position->current; + //if(Battle->castTime==0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etimecastTime = 0; + UseAtkSkill( Enemy, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP<0) Stats->MP=0; + if(Battle->contatk) + { + Battle->atktype = NORMAL_ATTACK; + Battle->skilltarget = 0; + Battle->atktarget = Battle->target; + Battle->skillid = 0; + } + else + { + //ClearBattle( Battle ); + } + GServer->DoSkillScript( this, skill ); + Battle->lastAtkTime = clock( ); + return true; +} + +// do buff skill +bool CCharacter::BuffSkill( CCharacter* Target, CSkills* skill ) +{ + Position->destiny = Position->current; + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + //Log(MSG_DEBUG,"applied buff skill %i",skill->id); + UseBuffSkill( Target, skill ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + GServer->DoSkillScript( this, skill ); //summons a monster. Why do we do this for all skills? + if(!IsMonster()) + { + ClearBattle( Battle ); // clear battle for players when they use buff skills + //Battle->bufftarget = 0; + //Battle->skilltarget = 0; + //Battle->skillid = 0; + //Battle->atktype = NORMAL_ATTACK; + } + else //Monsters need to be reset to normal attack and clear skill attacks. + { + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + Battle->atktype = NORMAL_ATTACK; + } + Battle->lastAtkTime = clock( ); + return true; +} +//arnold +// do AoE skill +bool CCharacter::AoeSkill( CSkills* skill, CCharacter* Enemy ) +{ + Position->destiny = Position->current; + //Log(MSG_INFO,"Doing AOE Skill"); + //if(Battle->castTime == 0) + //{ + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + // Battle->castTime = clock(); + // return true; + //} + //else + //{ + // clock_t etime = clock() - Battle->castTime; + // if(etime < SKILL_DELAY) + // return true; + //} + //Battle->castTime = 0; + CMap* map = GServer->MapList.Index[Position->Map]; + + //PY replacement code + if(IsPlayer() || IsSummon()) + { + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster->clientid == clientid) continue; + if(monster->IsSummon( ) && (map->allowpvp == 0 || monster->owner == clientid)) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill ); + } + } + if(IsMonster() && !IsSummon()) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player->clientid == clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos,player->Position->current,(float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill ); + } + } + //PY end + + // this code appears to me to be completely wrong replaced with code above + /* + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + if(monster->clientid == clientid) continue; + if(IsSummon( ) || IsPlayer( )) + { + if(monster->IsSummon( ) && (map->allowpvp==0 || monster->owner == clientid)) continue; + } + else + { + if(!monster->IsSummon( )) continue; + } + if(GServer->IsMonInCircle( Position->skilltargetpos, monster->Position->current,(float)skill->aoeradius + 1)) + UseAtkSkill( (CCharacter*) monster, skill ); + } + if(map->allowpvp!=0 || (IsMonster( ) && !IsSummon( ))) + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + if(player->clientid==clientid) continue; + if(GServer->IsMonInCircle( Position->skilltargetpos,player->Position->current,(float)skill->aoeradius+1)) + UseAtkSkill( (CCharacter*) player, skill ); + } + }*/ + + if(Enemy != NULL) + { + if(!Enemy->IsDead( )) + { + Battle->atktarget = Battle->target; + Battle->atktype = NORMAL_ATTACK; + Battle->skilltarget = 0; + Battle->skillid = 0; + } + else ClearBattle( Battle ); + } + else ClearBattle( Battle ); + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + Battle->lastAtkTime = clock( ); + return true; +} + +bool CCharacter::AoeBuff( CSkills* skill ) +{ + //Log(MSG_INFO,"BUFF AOE called"); + Position->destiny = Position->current; + BEGINPACKET( pak, 0x7bb ); + ADDWORD ( pak, clientid ); + GServer->SendToVisible( &pak, (CCharacter*)this ); + CMap* map = GServer->MapList.Index[Position->Map]; + + if(CharType == 1 || CharType == 3) //player || Summon + { + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* player = map->PlayerList.at(i); + switch(skill->target) + { + case 1: //tPartyMember + if(player->Party->party == GetParty( )) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough party member"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 2: //tClanMember + if (player->Clan == GetClan( )) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough clan member"); + if(GServer->IsMonInCircle( Position->current, player->Position->current, (float)skill->aoeradius + 1 ) ) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough allied Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + break; + case 5: //tHostileCharacter + { + if (CharType == 2) + { + //Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough hostile Player"); + if(GServer->IsMonInCircle( Position->current,player->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)player, skill ); + } + } + break; + } + } + } + else if (CharType == 2) //monster + { + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* monster = map->MonsterList.at(i); + switch(skill->target) + { + case tPartyMember: // party + break; + case 2: //tClanMember + break; + case 3: //tAlly + case 7: //tAllCharacters + case 8: //tAllMembers. all characters + { + Log(MSG_INFO,"Applying AOE buff as long as I can find a close enough monster"); + if(GServer->IsMonInCircle( Position->current,monster->Position->current,(float)skill->aoeradius + 1)) + UseBuffSkill( (CCharacter*)monster, skill ); + } + break; + case tHostileCharacter: + break; + } + } + } + Stats->MP -= (skill->mp - (skill->mp * Stats->MPReduction / 100)); + if(Stats->MP < 0) Stats->MP = 0; + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + Battle->atktype = NORMAL_ATTACK; + //ClearBattle( Battle ); + Battle->lastAtkTime = clock( ); + return true; +} + + + +// use skill attack +void CCharacter::UseAtkSkill( CCharacter* Enemy, CSkills* skill, bool deBuff) +{ + long int skillpower = 0; + if(skill->skilltype == 6 || skill->skilltype == 7) //magical damage + { + skillpower = skill->atkpower * 2; + } + else // physical damage + { + skillpower = skill->atkpower * 2; + } + UINT weapontype = 0; + + float mod = 0; + float attack = 0; + if(IsPlayer()) + { + mod = 35 + (Stats->Level / 2); + } + else + { + mod = 35 + (Stats->Level / 2); + } + if(skill->skilltype == 6 || skill->skilltype == 7) //magical damage + { + attack = skillpower / Enemy->Stats->Magic_Defense; + } + else + { + attack = skillpower / Enemy->Stats->Defense; + } + attack *= mod; + attack += (Stats->Attack_Power / 2); // let's add our AP to the skillpower to make it worthwhile to cast skills. + + skillpower = (int)attack; + bool bflag = false; + Enemy->OnBeAttacked( this ); + if(skillpower <= 0) skillpower = 0; + if(IsPlayer()) + { + skillpower += (int)(skillpower * (Stats->ExtraDamage))/100; + } + if (skill->atkpower == 0) + { + skillpower = 0; // If the skill has a 0 atk power, it isn't meant to damage + Log( MSG_INFO, "This skill does no damage" ); + //ClearBattle( Battle ); + return; // probably need to add some alternative code here for legitimate skills that have no attack power. + } + if(skillpower >2046)skillpower = 2046; + if(!Enemy->IsSummon( ) && Enemy->IsMonster( )) + { + Enemy->AddDamage( this, skillpower ); + Enemy->damagecounter += skillpower;// is for AI + } + Enemy->Stats->HP -= skillpower; + BEGINPACKET( pak, 0x7b6 ); + ADDWORD ( pak, Enemy->clientid ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, 0x000007f8 ); + ADDBYTE ( pak, 0x00 ); + if (Enemy->Stats->HP<1) + { + ADDWORD ( pak, skillpower + 0x8000 ); + } + else + { + ADDWORD ( pak, skillpower + 0x2000 ); + } + if(Enemy->IsDead()) + { + CDrop* thisdrop = NULL; + ADDDWORD ( pak, 16 ); + if(!Enemy->IsSummon( ) && !Enemy->IsPlayer( )) + { + thisdrop = Enemy->GetDrop( ); + if(thisdrop!=NULL) + { + ADDFLOAT ( pak, thisdrop->pos.x*100 ); + ADDFLOAT ( pak, thisdrop->pos.y*100 ); + if(thisdrop->type==1) + { + ADDWORD( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + { + ADDWORD ( pak, GServer->BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, GServer->BuildItemData( thisdrop->item ) ); + } + ADDWORD ( pak, thisdrop->clientid ); + ADDWORD ( pak, thisdrop->owner ); + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, Enemy, thisdrop ); + OnEnemyDie( Enemy ); + } + else + { + ADDDWORD ( pak, 4 ); + GServer->SendToVisible( &pak, Enemy ); + bflag = GServer->AddBuffs( skill, Enemy, GetInt( ) ); + + //Send (de)buff information to the whole world + if(skill->nbuffs>0 && bflag) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Battle->skilltarget ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( ) ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Enemy ); + } + } + if (deBuff) return; + //Send skill animation to the world + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + Battle->bufftarget = 0; + Battle->skilltarget = 0; + Battle->skillid = 0; + Battle->atktype = NORMAL_ATTACK; +} + +// use buff skill +void CCharacter::UseBuffSkill( CCharacter* Target, CSkills* skill ) +{ + bool bflag = false; + bflag = GServer->AddBuffs( skill, Target, GetInt( ) ); + if(skill->nbuffs > 0 && bflag == true) + { + BEGINPACKET( pak, 0x7b5 ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Battle->skillid ); + ADDWORD ( pak, GetInt( )/4 ); + ADDBYTE ( pak, skill->nbuffs ); + GServer->SendToVisible( &pak, Target ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 1"); + } + + BEGINPACKET( pak, 0x7b9); + ADDWORD ( pak, clientid); + ADDWORD ( pak, Battle->skillid); + ADDWORD ( pak, 1); + GServer->SendToVisible( &pak, this ); + //Log(MSG_DEBUG,"Buff Skill. buffs applied 2 nbuffs = %i",skill->nbuffs); + //Log(MSG_INFO,"Target HP = %i",Target->Stats->HP); +} + +// Use a skill +bool CCharacter::UseSkill( CSkills* skill, CCharacter *Target ) +{ + if (skill->atkpower > 0) + { + Log(MSG_INFO, "Need to do %i%s %s in range %i to target %i", + ( skill->atkpower ), + ( (skill->range > 0) ? " AOE" : "" ), + ( (GServer->isSkillTargetFriendly(skill)) ? "healing" : "damage" ), + ( skill->range ), + ( skill->target ) ); + } + for (int i = 0; i < 2; i++) { + Log(MSG_INFO, "Status ID: %i", skill->status[i]); + if (skill->status[i] == 0) continue; + CStatus* status = GServer->GetStatusByID(skill->status[i]); + if (status == NULL) continue; + /************************ + We'll probably need to use status->decrease (Figure out whether buf is up/down + status->repeat will tell us whether it's a one-time (Stat Boost [2]), repeat + (Recovery, continueing damage [1]) or Special (Status Effect [3]). + ************************/ + if (status->repeat == 1) // Continuous + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s over %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + } + else if (status->repeat == 2) // Once (Stat Boost) + { + Log(MSG_INFO, "Need to take stat %i and %s it by %i%s for %i seconds", skill->buff[i], + ((status->decrease) ? "decrease" : "increase"), + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ((skill->value1[i] != 0) ? "" : "%"), skill->duration); + + } else if (status->repeat == 3) // Status Effect (Poison,etc) + { + Log(MSG_INFO, "Need to give user status effect %i for %i seconds", skill->buff[i], + ((skill->value1[i] != 0) ? skill->value1[i] : skill->value2[i]), + ( skill->duration ) ); + + } + } + return true; +} \ No newline at end of file diff --git a/Src/World Server/character.cpp b/Src/World Server/character.cpp new file mode 100644 index 0000000..72d2736 --- /dev/null +++ b/Src/World Server/character.cpp @@ -0,0 +1,205 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +//constructor +CCharacter::CCharacter( ) +{ + // STATS + Stats = new STATS; + //refnpc=NULL; + assert(Stats); + Stats->Level = 0; + Stats->HP = 0; + Stats->MP = 0; + Stats->MaxHP = 0; + Stats->MaxMP = 0; + Stats->Attack_Power = 0; + Stats->Defense = 0; + Stats->Critical = 0; + Stats->Dodge = 0; + Stats->Accury = 0; + Stats->Magic_Defense = 0; + Stats->Move_Speed = 0; + Stats->Attack_Speed = 0; + Stats->Attack_Distance = 0; + Stats->MaxWeight = 0; + Stats->MaxSummonGauge = 0; + Stats->MPReduction = 0; + Stats->ExtraDamage = 0; + // POSITION + Position = new POSITION; + assert(Position); + Position->Map = 0; + Position->lastMoveTime = 0; + Position->saved = 0; + Position->respawn = 0; + //BATLE + Battle = new BATTLE; + assert(Battle); + ClearBattle( Battle ); + Battle->castTime = 0; + //STATUS + Status = new STATUS; + assert(Status); + //Stats up + Status->Attack_up = 0xff; + Status->Defense_up = 0xff; + Status->Magic_Defense_up = 0xff; + Status->Accury_up = 0xff; + Status->Critical_up = 0xff; + Status->Dodge_up = 0xff; + Status->Haste_up = 0xff; + Status->Dash_up = 0xff; + Status->HP_up = 0xff; + Status->MP_up = 0xff; + //Stats down + Status->Attack_down = 0xff; + Status->Defense_down = 0xff; + Status->Magic_Defense_down = 0xff; + Status->Accury_down = 0xff; + Status->Critical_down = 0xff; + Status->Dodge_down = 0xff; + Status->Haste_down = 0xff; + Status->Dash_down = 0xff; + Status->HP_down = 0xff; + Status->MP_down = 0xff; + //Status + Status->Stuned = 0xff; + Status->Poisoned = 0xff; + Status->Muted = 0xff; + Status->ExtraDamage_up = 0xff; + Status->ExtraDamage_down = 0xff; + Status->Sleep = 0xff; + Status->CanAttack = true; + Status->CanRun = true; + Status->CanMove = true; + Status->CanCastSkill = true; + // Stance + Status->Stance = RUNNING; + for(int i=0;i<30;i++) + { + MagicStatus[i].Status = 0; + MagicStatus[i].Buff = 0; + MagicStatus[i].Value =0; + MagicStatus[i].Duration = 0; + MagicStatus[i].BuffTime = 0; + } + CharType = 0; + clientid = 0xffff; + nearChar = NULL; + findChar = NULL; +} + +//deconstructor +CCharacter::~CCharacter( ) +{ + delete Stats; + delete Position; + delete Battle; + delete Status; +} + +// Virtual +bool CCharacter::UpdateValues( ) +{ + return true; +} + +// update position +void CCharacter::UpdatePosition( ) +{ + float ntime; + clock_t etime = clock() - Position->lastMoveTime; + if(IsOnBattle( ) && Battle->target != 0) + { + CCharacter* Target = GetCharTarget( ); + if(Target == NULL) + ClearBattle( Battle ); + } + if(!IsMoving()) + { + if(IsPlayer())Position->lastMoveTime = clock(); + return; + } + float dx = Position->destiny.x - Position->current.x; + float dy = Position->destiny.y - Position->current.y; + float distance = sqrt( (dx*dx) + (dy*dy) ); + if(IsPlayer()) + { + + ntime = ( distance / (float(Stats->Move_Speed)/GServer->MOVE_SPEED_MOD)) * CLOCKS_PER_SEC; // calculates clock ticks to target position + //Log( MSG_DEBUG, "Dist: %f Speed: %i Mod: %i ntime: %f", distance, Stats->Move_Speed, GServer->MOVE_SPEED_MOD, ntime); + } + if(IsMonster()) + { + ntime = ( distance / (float(Stats->Move_Speed)/GServer->MOVE_SPEED_MOD)) * CLOCKS_PER_SEC; // calculates clock ticks to target position + Position->current.x = dx*(etime/ntime) + Position->current.x; + Position->current.y = dy*(etime/ntime) + Position->current.y; + + } + //float ntime = ( distance / Stats->Move_Speed * GServer->MOVE_SPEED_MOD ); // lmame method + /*if(IsPlayer()) + { + Log(MSG_DEBUG,"ntime = %f distance = %f ",ntime, distance ); + }*/ + + if(IsOnBattle() && distance <= Stats->Attack_Distance) //this will prevent the character from moving if in battle and in range + { + Position->destiny.x = Position->current.x; + Position->destiny.y = Position->current.y; + dx = 0; + dy = 0; + } + if (ntime <= etime || distance == 0) + { + /*if(IsPlayer()) + { + Log(MSG_DEBUG,"Movement timed out or target reached. Set current to destiny."); + }*/ + Position->current.x = Position->destiny.x; + Position->current.y = Position->destiny.y; + } + else + { + if(IsPlayer()) + { + /*{ + Log(MSG_DEBUG,"Still moving. Distance to target %f time to target %f", distance, ntime); + }*/ + Position->current.x = dx*(etime/ntime) + Position->current.x; + Position->current.y = dy*(etime/ntime) + Position->current.y; + } + } + /*if(IsPlayer()) + { + if(Position->current.x == Position->destiny.x) + { + Log(MSG_DEBUG,"Character is at destiny position in the X direction."); + } + else + { + Log(MSG_DEBUG,"Character is still moving. current x %f Destination x %f",Position->current.x,Position->destiny.x); + } + }*/ + Position->lastMoveTime = clock(); +} diff --git a/Src/World Server/character.h b/Src/World Server/character.h new file mode 100644 index 0000000..9ae3df4 --- /dev/null +++ b/Src/World Server/character.h @@ -0,0 +1,118 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef _ROSE_CHARACTER_ +#define _ROSE_CHARACTER_ +#include "chartype.h" +//#include "party.h" + +class CCharacter +{ + public: + CCharacter( ); + ~CCharacter( ); + + unsigned int clientid; + BYTE CharType; // 0 = undefined | 1 = player | 2 = monster | 3 = summon | 4 = NPC + CCharacter* nearChar; + CCharacter* findChar; + int damagecounter; + + STATS *Stats; + POSITION *Position; + BATTLE* Battle; + STATUS* Status; + MAGICSTATUS MagicStatus[30]; + int AIVar[20]; + + // Batle Functions + void DoAttack( ); + void NormalAttack( CCharacter* Enemy ); + bool SkillAttack( CCharacter* Enemy, CSkills* skill ); + bool BuffSkill( CCharacter* Target, CSkills* skill ); + bool AoeSkill( CSkills* skill, CCharacter* Enemy=NULL ); + bool AoeBuff( CSkills* skill ); + void UseBuffSkill( CCharacter* Target, CSkills* skill, bool deBuff = false ); + void UseBuffSkillQSD( CCharacter* Target, CSkills* skill, bool deBuff = false ); + + //void UseAtkSkill( CCharacter* Enemy, CSkills* skill ); + void UseAtkSkill( CCharacter* Enemy, CSkills* skill, bool deBuff= false ); + int GetHitRate(CCharacter* pDEF); + int GetCritRate(); + int GetAtkDmg(CCharacter* pDEF, word wHitCNT, int iSuc); + int GetMgcDmg(CCharacter* pDEF, word wHitCNT, int iSuc); + + bool UseSkill( CSkills* skill, CCharacter *Target = NULL ); + //functions + bool IsMonster( ); + bool IsPlayer( ); + bool IsDead( ); + bool IsSummon( ); + bool IsOnBattle( ); + bool IsNPC( ); + void StartAction( CCharacter* Target, BYTE action, UINT skillid=0 ); + //void StartAction2( CCharacter* Target, BYTE action, UINT skillid=0, bool restart=false, CCharacter* receiver=NULL); + bool IsTargetReached( CCharacter* , CSkills* skill=NULL ); + bool CanAttack( ); + bool IsMoving( ); + CCharacter* GetCharTarget( ); + bool IsAttacking( ); + + // virtual functions + // Events + virtual bool OnBeAttacked( CCharacter* ); + virtual bool OnDie( ); + virtual bool OnEnemyDie( CCharacter* ); + virtual bool OnEnemyOnSight( CCharacter* ); + virtual bool OnSpawn( bool ); + virtual bool OnAlmostDie( ); + virtual bool OnFar( ); + + // Functions + virtual CDrop* GetDrop( ); + virtual void AddDamage( CCharacter* enemy, long int hitpower); + virtual void UpdatePosition( ); + virtual bool UpdateValues( ); + virtual void ReduceABC( ); + virtual int GetEVBuff( ); + void RefreshBuff( ); + virtual CParty* GetParty( ); + virtual CLAN* GetClan( ); + + // stats + virtual unsigned int GetAttackPower( ); + virtual unsigned int GetDefense( ); + virtual unsigned int GetXPRate( ); + virtual unsigned int GetItemDropRate( ); + virtual unsigned int GetItemDropCountRate( ); + virtual unsigned int GetDodge( ); + virtual unsigned int GetAccury( ); + virtual unsigned int GetMagicDefense( ); + virtual unsigned int GetMagicAttack( ); + virtual unsigned int GetCritical( ); + virtual unsigned int GetAttackSpeed( ); + virtual unsigned int GetMoveSpeed( ); + virtual unsigned int GetMaxHP( ); + virtual unsigned int GetMaxMP( ); + virtual float GetAttackDistance( ); + virtual unsigned int GetInt( ); +}; + +#endif diff --git a/Src/World Server/charevent.cpp b/Src/World Server/charevent.cpp new file mode 100644 index 0000000..be2b870 --- /dev/null +++ b/Src/World Server/charevent.cpp @@ -0,0 +1,63 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" + +//virtual [called when a enemy hit this character] +bool CCharacter::OnBeAttacked( CCharacter* Enemy ) +{ + return true; +} + +// virtual [called when this character die] +bool CCharacter::OnDie( ) +{ + return true; +} + +// virtual [called when enemy die] +bool CCharacter::OnEnemyDie( CCharacter* Enemy ) +{ + return true; +} + +// virtual [called when a enmy is on sigh] +bool CCharacter::OnEnemyOnSight( CCharacter* Enemy ) +{ + return true; +} + +// virtual [called when this character spawn] +bool CCharacter::OnSpawn( bool ) +{ + return true; +} + +// virtual [called when this character is almost dead] +bool CCharacter::OnAlmostDie( ) +{ + return true; +} + +// virtual [called when character is far] +bool CCharacter::OnFar( ) +{ + return true; +} diff --git a/Src/World Server/charfunctions.cpp b/Src/World Server/charfunctions.cpp new file mode 100644 index 0000000..3a5a8ee --- /dev/null +++ b/Src/World Server/charfunctions.cpp @@ -0,0 +1,606 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" +#include "worldserver.h" + +// return true if this character is monster +bool CCharacter::IsMonster( ) +{ + if(CharType == 2 || CharType == 3) + return true; + return false; +} + +// return true if this character is player +bool CCharacter::IsPlayer( ) +{ + if(CharType == 1) + return true; + return false; +} + +//return true if this character is an NPC +bool CCharacter::IsNPC( ) +{ + if(CharType == 4) + return true; + return false; +} + +// return true if HP is lower to 1 +bool CCharacter::IsDead( ) +{ + if(Stats->HP <= 0) + return true; + return false; +} + +// return true if this character is summon +bool CCharacter::IsSummon( ) +{ + if(this == NULL) return false; + if(CharType == 3)// this line is erroring. No idea why + return true; + return false; +} + +// Virtual function [return item drop] +CDrop* CCharacter::GetDrop( ) +{ + return NULL; +} + +// start action [attack] +void CCharacter::StartAction( CCharacter* Target, BYTE action, UINT skillid) +{ + + BEGINPACKET( pak, 0 ); + switch(action) + { + case NORMAL_ATTACK: + { + //Log( MSG_INFO, "case NORMAL_ATTACK"); + if(Target == NULL)return; + RESETPACKET( pak, 0x798 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, Stats->Move_Speed ); + ADDFLOAT ( pak, Target->Position->current.x * 100 ); + ADDFLOAT ( pak, Target->Position->current.y * 100 ); + Battle->target = Target->clientid; + Battle->atktarget = Target->clientid; + Battle->atktype = action; + Battle->skilltarget = 0; + Battle->bufftarget = 0; + Battle->skillid = 0; + Position->destiny = Target->Position->current; //move to intercept target + Position->lastMoveTime = clock(); + //Log(MSG_DEBUG,"Normal Attack set destiny X: %f Y: %f.",Position->current.x,Position->current.y); + } + break; + case SKILL_ATTACK: //Log( MSG_INFO, "case SKILL_ATTACK"); + case SKILL_BUFF: + { + if(Target == NULL)return; + RESETPACKET( pak, 0x7b3 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, Target->clientid ); + ADDWORD ( pak, skillid ); + ADDWORD ( pak, 0x0000 ); + ADDFLOAT ( pak, Target->Position->current.x * 100 ); + ADDFLOAT ( pak, Target->Position->current.y * 100 ); + Battle->target = Target->clientid; + + if(action == SKILL_ATTACK) + { + //Log( MSG_INFO, "case SKILL_ATTACK"); + Battle->skilltarget = Target->clientid; + Battle->bufftarget = 0; + } + else + { + //Log( MSG_INFO, "case SKILL_BUFF"); + Battle->bufftarget = Target->clientid; + Battle->skilltarget = 0; + } + + Battle->atktype = action; + Position->destiny = Target->Position->current; //move toward target + Battle->skillid = skillid; + Position->lastMoveTime = clock(); + //Log(MSG_DEBUG,"Skill Attack set destiny X: %f Y: %f.",Position->current.x,Position->current.y); + } + break; + case SKILL_AOE://Log( MSG_INFO, "case SKILL_AOE"); + case BUFF_SELF://Log( MSG_INFO, "case BUFF_SELF"); + case BUFF_AOE: + { + + //Log( MSG_INFO, "Battle set for skill %i Action = %i", skillid, action); + //Log( MSG_INFO, "case BUFF_AOE"); + RESETPACKET( pak, 0x7b2); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, skillid ); + Battle->atktype = action; + Battle->skillid = skillid; + Position->skilltargetpos.x = Position->current.x; + Position->skilltargetpos.y = Position->current.y; + //Log( MSG_INFO, "BUFF_AOE packet sent"); + } + break; + case AOE_TARGET: + { + //Log( MSG_INFO, "case AOE_TARGET"); + //Target is NULL for this function when cast by a player + //Target is NOT NULL when it comes from a monster + if(Target != NULL) //monster attack + { + Position->skilltargetpos.x = Target->Position->current.x; + Position->skilltargetpos.y = Target->Position->current.y; + } + RESETPACKET( pak, 0x7b4 ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, skillid ); + ADDFLOAT ( pak, Position->skilltargetpos.x * 100 ); + ADDFLOAT ( pak, Position->skilltargetpos.y * 100 ); + Battle->atktype = action; + Battle->skillid = skillid; + Battle->skilltarget = 0; + Battle->bufftarget = 0; + } + break; + default: + return; + break; + } + GServer->SendToVisible( &pak, this ); + Battle->contatk = true; + + //Log(MSG_INFO,"Battle Attacktype = %i",Battle->atktype); + return; +} + +bool CCharacter:: IsOnBattle( ) +{ + if(Battle->atktype == 0) + { + if(Battle->iscasting == 1) + { + clock_t etime = clock() - Battle->lastAtkTime; + if( etime < CLOCKS_PER_SEC * 100 / Stats->Attack_Speed ) + { + //Log(MSG_DEBUG,"Iscasting is true while NOT in battle. Reset to false."); + Battle->iscasting = 0; + } + else + { + return false; + } + } + } + switch(Battle->atktype) + { + case 1: //NORMAL_ATTACK: + { + if(Battle->atktarget != 0) return true; + } + break; + case 2: //SKILL_ATTACK: + case 8: //AOE_TARGET: + { + if(Battle->skilltarget != 0 && Battle->skillid != 0) return true; + } + break; + case 3: //SKILL_BUFF: + { + if(Battle->bufftarget != 0 && Battle->skillid != 0) return true; + } + break; + case 4: //SKILL_AOE: + //case SKILL_SELF: //no such skill type + case 6: //BUFF_SELF: + case 7: //BUFF_AOE: + { + if(Battle->skillid != 0) return true; + } + break; + default: return false; + } +} + +bool CCharacter::CanAttack( ) +{ + + clock_t etime = clock() - Battle->lastAtkTime; + if(CharType == 4) //it's an npc + { + + //Log(MSG_DEBUG, "It's an NPC so set speed to 1"); + //always return true for an NPC + Stats->Attack_Speed = 1; + } + if (Stats->Attack_Speed < 1) + { + + Log( MSG_INFO, "weapon with no attackspeed."); + return false; + } + if(Battle->iscasting == 1) + { + if( etime < CLOCKS_PER_SEC * GServer->Config.SkillMult / Stats->Attack_Speed ) //should make the delay 4 times longer + { + //Log(MSG_INFO,"Cannot attack yet. Last attack time = %i",Battle->lastAtkTime); + return false; + } + else + { + Log(MSG_INFO,"iscasting reset to 0 after time delay"); + Battle->iscasting = 0; + Battle->NextAtkTime = Battle->lastAtkTime + (CLOCKS_PER_SEC * GServer->Config.SkillMult / Stats->Attack_Speed); + return true; + } + } + else //normal attack + { + if( etime < CLOCKS_PER_SEC * GServer->Config.AttkMult / Stats->Attack_Speed ) + { + //Log(MSG_INFO,"Cannot attack yet. Last attack time = %i",Battle->lastAtkTime); + return false; + } + } + Battle->NextAtkTime = Battle->lastAtkTime + (CLOCKS_PER_SEC * GServer->Config.SkillMult / Stats->Attack_Speed); + return true; +} + +// return true if target is reached +bool CCharacter::IsTargetReached( CCharacter* Enemy, CSkills* skill ) +{ + CMap* map = GServer->MapList.Index[Position->Map]; + if(map == NULL) + return true; + float distance = GServer->distance( Position->current, Enemy->Position->current ); + + if(skill == NULL) //it's a normal attack + { + if(distance <= Stats->Attack_Distance) + { + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(istargetreached) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + return true; + } + else + { + Position->destiny = Enemy->Position->current; + //Log(MSG_DEBUG,"(istargetreached) Destiny set to Enemy position X: %f Y: %f.",Position->current.x,Position->current.y); + } + } + else // skill attack + { + float SkillRange = (float)skill->range; + if(SkillRange == 0) + { + SkillRange = Stats->Attack_Distance; + } + if(distance <= SkillRange) + { + Position->destiny = Position->current; + //Log(MSG_DEBUG,"(istargetreached skill) Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + return true; + } + else + { + Position->destiny = Enemy->Position->current; + //Log(MSG_DEBUG,"(istargetreached skill) Destiny set to Enemy position X: %f Y: %f.",Position->current.x,Position->current.y); + //Log(MSG_DEBUG,"(istargetreached skill) Distance: %f range: %i.", distance, SkillRange); + } + } + //Log(MSG_INFO,"(istargetreached) Cannot attack. Too far away"); + return false; +} + +// return true if the character is moving +bool CCharacter::IsMoving( ) +{ + if(Position->current.x == Position->destiny.x && Position->current.y == Position->destiny.y ) + return false; + return true; +} + +// VIRTUAL [add player damage] +void CCharacter::AddDamage( CCharacter* enemy, long int hitpower) +{ + +} + +// return character target +CCharacter* CCharacter::GetCharTarget( ) +{ + CCharacter* thistarget = GServer->MapList.Index[Position->Map]->GetCharInMap( Battle->target ); + if (thistarget != NULL) + { + //Log(MSG_DEBUG, "getchartarget found target OK"); + return thistarget; + } + //Log(MSG_DEBUG, "getchartarget failed to find target"); + //if I don't currently have a target then return the character who just hit me + return GServer->MapList.Index[Position->Map]->GetCharInMap( Battle->hitby ); +} + +// VIRTUAL [reduce ammon/bullets/cannons] +void CCharacter::ReduceABC( ) +{ + +} + +// VIRTUAL [get extra buff value] +int CCharacter::GetEVBuff( ) +{ + return 0; +} + +// VIRTUAL [return intelligence] +unsigned int CCharacter::GetInt( ) +{ + return 15; +} + + +// Check For Debuffs +void CCharacter::RefreshBuff( ) +{ + bool bflag = false; + for( UINT i=0;i<30;i++) + { + if(MagicStatus[i].Buff == 0) continue; + clock_t etime = clock() - MagicStatus[i].BuffTime; + if( etime >= MagicStatus[i].Duration * CLOCKS_PER_SEC ) + { + switch(MagicStatus[i].Status) + { + case 18: // attack power up + case 19: // attack power down + case 48: // attack power up + if(i<15) + Status->Attack_up = 0xff; + else + Status->Attack_down = 0xff; + Stats->Attack_Power = GetAttackPower( ); + break; + case 20: // def up + case 21: // def down + case 49: // def up + if(i<15) + Status->Defense_up= 0xff; + else + Status->Defense_down = 0xff; + Stats->Defense = GetDefense( ); + break; + case 24: //accuracy up + case 25: //accuracy down + case 51: //attack accuracy up. + if(i<15) + Status->Accury_up= 0xff; + else + Status->Accury_down = 0xff; + Stats->Accury = GetAccury( ); + break; + case 22: // macic resistance up + case 23: // magic resistance down + case 50: // magic resistance up + if(i<15) + Status->Magic_Defense_up = 0xff; + else + Status->Magic_Defense_down = 0xff; + Stats->Magic_Defense = GetMagicDefense( ); + break; + case 28: //dodge up + case 29: //dodge down + case 53: //dodge rate up + if(i<15) + Status->Dodge_up = 0xff; + else + Status->Dodge_down = 0xff; + Stats->Dodge = GetDodge( ); + break; + case 14: //dash + case 15: //slow + case 46: //movement speed increased + if(i<15) + Status->Dash_up = 0xff; + else + Status->Dash_down = 0xff; + Stats->Move_Speed = GetMoveSpeed( ); + break; + case 16: // haste attack + case 17: // slow attack + case 47: // attack speed up + if(i<15) + Status->Haste_up = 0xff; + else + Status->Haste_down = 0xff; + Stats->Attack_Speed = GetAttackSpeed( ); + break; + case 26: // crit up + case 27: // crit down + case 52: // crit up + if(i<15) + Status->Critical_up = 0xff; + else + Status->Critical_down = 0xff; + Stats->Critical = GetCritical( ); + break; + case 12: // max HP up + case 44: // max HP up + if(i<15) + Status->HP_up = 0xff; + else + Status->HP_down = 0xff; + Stats->MaxHP = GetMaxHP( ); + break; + case 13: // max MP up + case 45: // max MP up + if(i<15) + Status->MP_up = 0xff; + else + Status->MP_down = 0xff; + Stats->MaxMP = GetMaxMP( ); + break; + case 32: // faint + Status->Stuned = 0xff; + Status->CanAttack = true; + //printf("removing stun\n"); + break; + case 7: case 8: case 9: case 10: case 11: //poisoned + Status->Poisoned = 0xff; + //printf("removing poison\n"); + break; + case 30: // muted + Status->Muted = 0xff; + Status->CanCastSkill = true; + break; + case 31: // sleep May need to be fixed later to accomodate multiple status effects. + Status->Sleep = 0xff; + Status->CanMove = true; + Status->CanCastSkill = true; + Status->CanAttack = true; + break; + case 54: //A_GMExtra_Damage: + case 36: //A_Extra_Damage: + if(i<15) + { + Status->ExtraDamage_up = 0xff; + Stats->ExtraDamage = 0xff; + } + else + { + Status->ExtraDamage_down = 0xff; + Stats->ExtraDamage = 0xff; + } + + break; + case 56: // Taunt + Status->Taunt = 0xff; + break; + default: + break; + } + + MagicStatus[i].Status = 0; + MagicStatus[i].Buff = 0; + MagicStatus[i].BuffTime = 0; + MagicStatus[i].Duration = 0; + MagicStatus[i].Value = 0; + bflag = true; + } + else if (MagicStatus[i].Status >= 7 && MagicStatus[i].Status <= 11 && etime > 1*CLOCKS_PER_SEC) //Do poison dmg every 1.5 seconds + { + Stats->HP -= MagicStatus[i].Status; //Actually take 7, 8, 9, 10 or 11 from the health. Based on the Status itself + MagicStatus[i].BuffTime += 1*CLOCKS_PER_SEC; + MagicStatus[i].Duration -= 1; + //printf("did %i poison dmg to the player, still %i seconds and %i HP remain \n", MagicStatus[i].Status, MagicStatus[i].Duration, Stats->HP); + + //A bunch of messy code to send dmg packet + BEGINPACKET( pak, 0x7b6 ); + ADDWORD ( pak, clientid ); // client taking damage + ADDWORD ( pak, 0 ); // client doing damage + ADDDWORD ( pak, 0x000007f8 ); + ADDBYTE ( pak, 0x00 ); + if (Stats->HP < 1) + { + ADDWORD ( pak, MagicStatus[i].Status + 0x8000 ); + } + else + { + ADDWORD ( pak, MagicStatus[i].Status + 0x2000 ); + } + //ADDDWORD ( pak, MagicStatus[i].Status ); //damage amount + + //If poisoned entity (player or monster) is killed + if( IsDead()) + { + //printf("char died\n"); + Log( MSG_INFO, "found dead monster"); + CDrop* thisdrop = NULL; + CMap* map = GServer->MapList.Index[thisdrop->posMap]; + ADDDWORD ( pak, 16 ); + if( !IsSummon( ) && !IsPlayer( )) + { + thisdrop = GetDrop( ); + if( thisdrop != NULL) + { + map->AddDrop( thisdrop ); + } + } + GServer->SendToVisible( &pak, this ); // removed thisdrop from the packet + //kill the monster if its HP is less than 0 + BEGINPACKET( pak1, 0x799 ); + ADDWORD ( pak1, clientid ); + ADDWORD ( pak1, clientid ); + ADDDWORD ( pak1, 0x8005 ); + GServer->SendToVisible( &pak1, this ); + } + + //If enemy is still alive + else + { + ADDDWORD ( pak, 4 ); + GServer->SendToVisible( &pak, this ); + } + } + } + if(bflag) + { + BEGINPACKET( pak,0x7b7 ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, GServer->BuildBuffs( this ) ); + GServer->SendToVisible( &pak, this ); + } +} + +// VIRTUAL [return party pointer] +CParty* CCharacter::GetParty( ) +{ + return NULL; +} + +// VIRTUAL [return clan pointer] +CLAN* CCharacter::GetClan( ) +{ + return NULL; +} + +bool CCharacter::IsAttacking( ) +{ + if(IsOnBattle( )) + { + switch(Battle->atktype) + { + case SKILL_BUFF: + case BUFF_SELF: + case BUFF_AOE: + return false; + default: + return false; + break; + } + } + else return false; +} diff --git a/Src/World Server/charstats.cpp b/Src/World Server/charstats.cpp new file mode 100644 index 0000000..0be8f9c --- /dev/null +++ b/Src/World Server/charstats.cpp @@ -0,0 +1,111 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "character.h" + +// virtual [return attackpower] +unsigned int CCharacter::GetAttackPower( ) +{ + return 0; +} + +// virtual [return defense] +unsigned int CCharacter::GetDefense( ) +{ + return 0; +} + +// virtual [return dodge] +unsigned int CCharacter::GetDodge( ) +{ + return 0; +} + +// virtual [return xprate] +unsigned int CCharacter::GetXPRate( ) +{ + return 0; +} + +// virtual [return item drop rate] +unsigned int CCharacter::GetItemDropRate( ) +{ + return 0; +} + +// virtual [return item drop rate] +unsigned int CCharacter::GetItemDropCountRate( ) +{ + return 0; +} + +// virtual [return accury] +unsigned int CCharacter::GetAccury( ) +{ + return 0; +} + +// virtual [return magic defense] +unsigned int CCharacter::GetMagicDefense( ) +{ + return 0; +} + +// virtual [return magic attack] +unsigned int CCharacter::GetMagicAttack( ) +{ + return 0; +} + +// virtual [return critical] +unsigned int CCharacter::GetCritical( ) +{ + return 0; +} + +// virtual [return attackspeed] +unsigned int CCharacter::GetAttackSpeed( ) +{ + return 0; +} + +// virtual [return movespeed] +unsigned int CCharacter::GetMoveSpeed( ) +{ + return 0; +} + +// virtual [return maxhp] +unsigned int CCharacter::GetMaxHP( ) +{ + return 0; +} + +// virtual [return maxmp] +unsigned int CCharacter::GetMaxMP( ) +{ + return 0; +} + +// virtual [return attackdistance] +float CCharacter::GetAttackDistance( ) +{ + return 0; +} diff --git a/Src/World Server/chartype.h b/Src/World Server/chartype.h new file mode 100644 index 0000000..16fe4e3 --- /dev/null +++ b/Src/World Server/chartype.h @@ -0,0 +1,303 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef _CHARACTER_TYPE_ +#define _CHARACTER_TYPE_ +#define MAX_STORAGE 160 +#define MAX_INVENTORY 140 +#define MAX_BASICSKILL 42 +#define MAX_QUICKBAR 32 +#define ClearBattle(i) { i->target=0;i->atktarget=0; i->bufftarget=0; i->skilltarget=0; i->skillid=0; i->atktype=0; i->hitby=0; i->iscasting=0; } +typedef unsigned char BYTE; +#include "datatypes.h" + +#define TPLAYER 1 +#define TMONSTER 2 +#define TSUMMON 3 +#define TNPC 4 + +//**************** SHARED ****************// +struct POSITION +{ + fPoint source; // source position + fPoint current; // current position + fPoint destiny; // destiny position + fPoint skilltargetpos; + unsigned int Map; // current map + unsigned int respawn; // respawn id [player = respawnid | monster = spawnid] + unsigned int saved; // saved id [player town | monster = 0] + clock_t lastMoveTime; +}; + +struct STATS +{ + int Level; + long int HP; + long int MP; + unsigned int MaxHP; + unsigned int MaxMP; + unsigned int stance; + + unsigned int Attack_Power; + unsigned int Defense; + unsigned int Critical; + unsigned int Dodge; + unsigned int Accury; + unsigned int Magic_Defense; + unsigned int Move_Speed; + unsigned int Mspd_base; + unsigned int Attack_Speed; + float Attack_Distance; + unsigned int MaxWeight; + unsigned int MaxSummonGauge; + unsigned int MPReduction; + unsigned int ExtraDamage; + unsigned int xprate; + unsigned int itemdroprate; + unsigned int itemdropcountrate; + unsigned int zulydroprate; + unsigned int greydrop; + unsigned int magicattack; + int ItemXPRate; + int ItemDropRate; + clock_t ItemXPTimer; + unsigned int ItemXPTime; + clock_t ItemDropRateTimer; + +}; + +struct BATTLE +{ + unsigned int target; + unsigned int atktarget; + unsigned int bufftarget; + unsigned int skilltarget; + unsigned int skillid; + unsigned int atktype; + unsigned int hitby; + unsigned int iscasting; + bool contatk; + clock_t lastAtkTime; + clock_t castTime; + clock_t NextAtkTime; +}; + +struct STATUS +{ + //Stats up + BYTE Dash_up; + BYTE Haste_up; + BYTE Attack_up; + BYTE Defense_up; + BYTE Magic_Defense_up; + BYTE Accury_up; + BYTE Critical_up; + BYTE Dodge_up; + + BYTE HP_up; + BYTE MP_up; + BYTE MaxHP_up; + BYTE MaxMP_up; + BYTE HPMP_up; + //Stats down + BYTE Dash_down; + BYTE Haste_down; + BYTE Attack_down; + BYTE Defense_down; + BYTE Magic_Defense_down; + BYTE Accury_down; + BYTE Critical_down; + BYTE Dodge_down; + BYTE HP_down; + BYTE MP_down; + BYTE MaxHP_down; + BYTE MaxMP_down; + BYTE HPMP_down; + + //Status Effects + BYTE Poisoned; + BYTE Muted; + BYTE Sleep; + BYTE Faint; + BYTE Stuned; + BYTE Camo; + BYTE Invis; + BYTE Shield; + BYTE Curse; + BYTE Recover; + BYTE Dispell; + BYTE Detect; + BYTE Invinc; + BYTE Flame; + BYTE ExtraDamage_up; + BYTE ExtraDamage_down; + BYTE Taunt; + bool CanAttack; + bool CanRun; + bool CanMove; + bool CanCastSkill; + bool IsTowerDef; + bool nightonly; + bool dayonly; + UINT spawnid; + + // Stance + BYTE Stance; +}; + +struct MAGICSTATUS +{ + unsigned int Status; // Store the skill Status + unsigned int Buff; // Store the buff type + unsigned int Value; // Buff Value + unsigned int Duration; // Skill Duration + clock_t BuffTime; // Buff Time +}; + +//**************************************************************/ +//***************Player only******************************************/ +struct ATTRIBUTES +{ + unsigned int Str; + unsigned int Dex; + unsigned int Int; + unsigned int Con; + unsigned int Cha; + unsigned int Sen; + + unsigned int Estr; + unsigned int Edex; + unsigned int Eint; + unsigned int Econ; + unsigned int Echa; + unsigned int Esen; + +}; + +struct USEDITEM +{ + clock_t lastRegTime; // Last Regeneration + UINT usevalue; // Max Heal Value + UINT usetype; // Consumible Type (MP/HP/STAMINA/...) + UINT userate; // consumible rate + UINT used; // Consumible Used +}; + +// Player information +struct INFO +{ + char charname[17]; + unsigned int charid; + BYTE Sex; + UINT Face; + UINT Hair; + UINT Exp; + UINT Pending_Exp; //temporary storage space for exp during calculations + UINT Job; + long int Zulies; + long int Storage_Zulies; + long int LastGlobal; + int StatPoints; + int SkillPoints; + unsigned int stamina; + UINT IsGM; + UINT IsDev; + //int xprate +}; + +struct SESSION +{ + unsigned int userid; + char username[17]; + char password[33]; + int accesslevel; + bool isLoggedIn; + bool inGame; + UINT Respawned; + BYTE RespawnChoice; + bool codedebug; + UINT KTPoints; + UINT NewPoints; + UINT logtime; // tracks total log points. 1 point = 1 hour. 5 points = 1KTPoint + clock_t LogTime; // time for the current session + UINT TotalLogTime; + UINT award; +}; + +struct RIDE +{ + bool Drive; + bool Ride; + UINT charid; +}; + +struct TRADE +{ + unsigned int short trade_itemid[0xa]; + unsigned int trade_count[0xb]; + unsigned int trade_status; + unsigned int trade_target; +}; + +struct PARTY +{ + class CParty* party; + bool IsMaster; +}; + +struct SHOP +{ + bool open; + char name[64]; + CBuying BuyingList[30]; + CSelling SellingList[30]; + UINT Buying; + UINT Selling; + UINT ShopType; +}; + +struct SKILLS +{ + unsigned id; + unsigned level; + CSkills* thisskill; +}; + +struct CLAN +{ + unsigned int clanid; + unsigned int clanrank; + unsigned int grade; + unsigned int CP; + unsigned int logo; + unsigned int back; + char clanname[17]; +}; + +struct UNION_s +{ + UINT unionvar[11]; +}; + + +//***************Monster only******************************************/ + +#endif + diff --git a/Src/World Server/clan.cpp b/Src/World Server/clan.cpp new file mode 100644 index 0000000..344af06 --- /dev/null +++ b/Src/World Server/clan.cpp @@ -0,0 +1,257 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Clan Manager +bool CWorldServer::pakClanManager ( CPlayer* thisclient, CPacket* P ) +{ + int action = GETBYTE((*P),0); + //Log(MSG_DEBUG, "Clan action %i", action ); + switch(action) + { + case 0xfa://new member added + { + MYSQL_ROW row; + int charid = GETWORD((*P),1); + int clanid = GETWORD((*P),3); + Log(MSG_INFO, "Adding member %d to clan %d", charid, clanid); + CPlayer* otherclient = GetClientByCID ( charid ); + if(otherclient==NULL) { + Log(MSG_ERROR, "Char with id %d doesn't exist", charid); + return true; + } + MYSQL_RES *result = DB->QStore("SELECT logo,back,name,grade FROM list_clan where id = %i", clanid); + if(result==NULL) + { + Log(MSG_INFO, "NULL result from query to list_clan"); + return true; + } + if(mysql_num_rows(result)!=1) + { + Log(MSG_WARNING, "Invalid clan %i", clanid ); + DB->QFree( ); + return true; + } + row = mysql_fetch_row(result); + otherclient->Clan->logo = atoi(row[0]); + otherclient->Clan->back = atoi(row[1]); + strcpy(otherclient->Clan->clanname,row[2]); + otherclient->Clan->grade = atoi(row[3]); + DB->QFree( ); + otherclient->Clan->clanid=clanid; + otherclient->Clan->clanrank=1; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 );//funcion + ADDWORD ( pak, otherclient->clientid );//client id + ADDWORD ( pak, clanid );//? + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, otherclient->Clan->back ); + ADDWORD ( pak, otherclient->Clan->logo ); + ADDBYTE ( pak, otherclient->Clan->grade ); + ADDBYTE ( pak, otherclient->Clan->clanrank ); + ADDSTRING ( pak, otherclient->Clan->clanname ); + ADDBYTE ( pak, 0x00 ); + SendToVisible( &pak, otherclient ); + } + break; + case 0xfb://Member Kicked + { + char nick[30]; + memcpy( nick, &P->Buffer[1], P->Size ); + CPlayer* otherclient = GetClientByCharName( nick ); + if(otherclient!=NULL) + { + otherclient->Clan->clanid=0; + otherclient->Clan->clanrank=1; + otherclient->Clan->back=0; + otherclient->Clan->logo=0; + otherclient->Clan->grade=0; + strcpy(otherclient->Clan->clanname,""); + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, otherclient->clientid ); + ADDQWORD ( pak, 0 ); + ADDWORD ( pak, 0x0001 ); + SendToVisible( &pak, otherclient ); + } + } + break; + case 0xfc://member change rank + { + char nick[30]; + int newrank = GETBYTE((*P),1); + memcpy( nick, &P->Buffer[2], P->Size ); + CPlayer* otherclient = GetClientByCharName( nick ); + if(otherclient!=NULL) + { + otherclient->Clan->clanrank = newrank; + } + } + break; + case 0xfd://disorg + { + unsigned int clanid = GETWORD((*P),1); + unsigned int charid = GETWORD((*P),3); + CPlayer* tclient = GetClientByCID( charid ); + if(tclient == NULL) + return true; + tclient->Clan->clanid = 0; + tclient->Clan->clanrank = 1; + tclient->Clan->grade = 0; + tclient->Clan->back = 0; + tclient->Clan->logo = 0; + memset( &tclient->Clan->clanname, '\0', 17 ); + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, tclient->clientid ); + ADDQWORD ( pak, 0 ); + ADDWORD ( pak, 0x0001 ); + SendToVisible( &pak, tclient ); + } + break; + case 0xfe://Member Leave + { + char nick[17]; + memcpy( nick, &P->Buffer[1], P->Size ); + CPlayer* otherclient = GetClientByCharName(nick); + if(otherclient!=NULL) + { + otherclient->Clan->clanid=0; + otherclient->Clan->clanrank=0; + otherclient->Clan->back=0; + otherclient->Clan->logo=0; + otherclient->Clan->grade=0; + strcpy(otherclient->Clan->clanname,""); + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, otherclient->clientid ); + ADDQWORD ( pak, 0 ); + ADDWORD ( pak, 0x0001 ); + SendToVisible( &pak, otherclient ); + } + } + break; + case 0xff: // update clan mark + { + unsigned int clanid = GETWORD((*P),1); + unsigned int clanlogo = GETDWORD((*P), 3 ); + for(unsigned int i=0;iplayer==NULL) continue; + CPlayer* player = (CPlayer*)ClientList.at(i)->player; + if(player->Clan->clanid==clanid) + { + player->Clan->back = 0; + player->Clan->logo = clanlogo; + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 );//funcion + ADDWORD ( pak, player->clientid );//cleint id + ADDWORD ( pak, clanid ); + ADDWORD ( pak, 0x0000 );//? + ADDWORD ( pak, player->Clan->back );//? + ADDWORD ( pak, player->Clan->logo );//? + ADDBYTE ( pak, player->Clan->grade ); + ADDBYTE ( pak, player->Clan->clanrank ); + ADDSTRING ( pak, player->Clan->clanname ); + ADDBYTE ( pak, 0x00 ); + SendToVisible( &pak, player ); + } + } + } + break; + default: + Log( MSG_INFO, "Clan manager unknow action %i", action ); + } + return true; +} + +// Create a new clan +bool CWorldServer::pakCreateClan ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->CharInfo->Zulies<1000000) + return true; + thisclient->CharInfo->Zulies -= 1000000; + MYSQL_ROW row; + int background = GETWORD((*P),1); + int icon = GETWORD((*P),3); + const char *name = ""; + const char *slogan = ""; + name=(char*)&P->Buffer[5]; + slogan=(char*)&P->Buffer[strlen(name)+6]; + + //Check if name already exists + MYSQL_RES *result = DB->QStore("SELECT name FROM list_clan WHERE name='%s'", name); + if(result==NULL) return true; + if ( mysql_num_rows( result ) > 0 ) + { + BEGINPACKET( pak, 0x07e0 ); + ADDWORD ( pak, 0x42 ); + thisclient->client->SendPacket( &pak ); + DB->QFree( ); + return true; + } + DB->QFree( ); + + //Check if user can create a clan + if( thisclient->Clan->clanid != 0 ) + { + BEGINPACKET( pak, 0x07e0 ); + ADDWORD ( pak, 0x44 ); + thisclient->client->SendPacket( &pak ); + return true; + } + + //Add clan to sql table + DB->QExecute( "INSERT into list_clan (logo,back,name,cp,grade,slogan,news) values (%i,%i,'%s',0,1,'%s','')", + icon,background,name,slogan); + thisclient->Clan->clanid = mysql_insert_id(DB->Mysql); + thisclient->Clan->clanrank = 6; + thisclient->Clan->logo = icon; + thisclient->Clan->back = background; + strncpy(thisclient->Clan->clanname,name,16); + thisclient->Clan->grade = 1; + + //Update user clan information + DB->QExecute( "UPDATE characters set clanid=%i,clan_rank=6 where id=%i", + thisclient->Clan->clanid,thisclient->CharInfo->charid); + //load clan info in char server + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0xfa ); //action to update clan informacion (charserver) + ADDWORD ( pak, thisclient->Clan->clanid ); + ADDWORD ( pak, thisclient->CharInfo->charid ); + ADDWORD ( pak, thisclient->clientid ); + SendISCPacket( &pak ); + + //Send to other players + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisclient->Clan->clanid); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, background ); + ADDWORD ( pak, icon ); + ADDBYTE ( pak, 0x01 );//clan grade + ADDBYTE ( pak, 0x06 );//clan rank + ADDSTRING ( pak, name ); + ADDBYTE ( pak, 0x00 ); + SendToVisible( &pak, thisclient ); + return true; +} diff --git a/Src/World Server/datatypes.h b/Src/World Server/datatypes.h new file mode 100644 index 0000000..5db59b1 --- /dev/null +++ b/Src/World Server/datatypes.h @@ -0,0 +1,1414 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef __ROSE_DATATYPES__ +#define __ROSE_DATATYPES__ + +//*upgraded status +//#define NULL 2 +#define STRENGTH 10 +#define DEXTERY 11 +#define INTELIGENCE 12 +#define CONCENTRATION 13 +#define CHARM 14 +#define SENSE 15 +#define C_HP 16 +#define C_MP 17 +#define ATTACK_POWER 18 +#define ACCURY 20 +#define MAGIC_RESISTENCE 21 +#define DODGE 22 +#define MOVE_SPEED 23 +#define ATTACK_SPEED 24 +#define INV_CAPACITY 25 +#define CRITICAL 26 +#define XP_RATE 30 +#define EXTRA_DAMAGE 36 +#define ONE_HAND_WEAP_AP 42 +#define TWO_HAND_WEAP_AP 43 +#define MOV_SPEED 52 +#define DEFENSE 53 +#define MAX_HP 54 +#define MAX_MP 55 +#define HP_REC_AMONT 56 +#define MP_REC_RATE 57 +#define BAGPACK_CAPACITY 58 +#define MP_COST_RED 61 +#define SUMMON_GAUGE 62 +#define ITEM_DROP_RATE 63 +#define ZULY_DROP_RATE 64 +#define ATK_ACCURACY 99 +#define SHIELD_DEFENSE 102 +#define ONE_HAND_SWORD_AP 104 +#define ONE_HAND_MELEE_AP 105 +#define TWO_HAND_SWORD_AP 106 +#define TWO_HAND_SPEAR_AP 107 +#define TWO_HAND_AXE_AP 108 +#define BOW_AP 109 +#define GUN_AP 110 +#define LAUNCHER_AP 111 +#define STAFF_POWER 113 +#define WAND_POWER 114 +#define KATAR_AP 115 +#define DUALSWORD_AP 116 +#define BOW_ATK_SPD 123 +#define GUN_ATK_SPD 124 +#define LAUNCHER_ATK_SPD 125 +#define KATAR_ATK_SPD 129 +#define DUALSWORD_ATK_SPD 130 + +//*status/condition +#define A_STR 10 +#define A_DEX 11 +#define A_INT 12 +#define A_CON 13 +#define A_CHA 14 +#define A_SEN 15 +#define A_HP 16 +#define A_MP 17 +#define A_ATTACK 18 +#define A_DEFENSE 19 +#define A_ACCUR 20 +#define A_MRESIST 21 +#define A_DODGE 22 +#define A_DASH 23 +#define A_HASTE 24 +#define A_INVENTORY_CAPACITY 25 +#define A_CRITICAL 26 +#define A_HP_REC_RATE 27 +#define A_MP_REC_RATE 28 +#define A_MP_CONSUMATION 29 +#define A_Extra_Damage 36 +#define A_MAX_HP 38 +#define A_MAX_MP 39 +#define A_GMExtra_Damage 54 +#define A_STUN 40 +#define A_MUTE 41 +#define A_POISON 42 + + +//*spawn values +//BYTE1 +#define UNK1 0x01 +#define MAX_HP_MP 0x02//revizar +#define POISONED 0x04 +#define UNK2 0x08 +#define MP_UP 0x10//revizar +#define HP_UP 0x20//revizar +#define DASH_UP 0x40 +#define DASH_DOWN 0x80 +//BYTE2 +#define HASTE_UP 0x01 +#define HASTE_DOWN 0x02 +#define ATTACK_UP 0x04 +#define ATTACK_DOWN 0x08 +#define DEFENSE_UP 0x10 +#define DEFENSE_DOWN 0x20 +#define MDEFENSE_UP 0x40 +#define MDEFENSE_DOWN 0x80 +//BYTE3 +#define HITRATE_UP 0x01 +#define HITRATE_DOWN 0x02 +#define CRITICAL_UP 0x04 +#define CRITICAL_DOWN 0x08 +#define DODGE_UP 0x10 +#define SUMMON 0x20 +#define MUTED 0x40 +#define SLEEPING 0x80 +//BYTE4 +#define STUN 0x01 +#define INVISIBLE_1 0x02 +#define INVISIBLE_2 0x04 +#define UNK3 0x08 +#define DAMAGE_UP 0x10 +#define SUMMON 0x20 +#define UNK4 0x40 +#define UNK5 0x80 + +//Stance +#define mWALKING 0 +#define mRUNNING 1 +#define ATTACKING 1 +#define WALKING 2 +#define RUNNING 3 +#define DRIVING 4 +#define SITTING 5 + +//Target Type +#define T_MONSTER 1 +#define T_PLAYER 2 +#define T_SELF 3 +#define T_PARTY 4 +#define T_OTHER 5 + +// attack type +#define NORMAL_ATTACK 1 +#define SKILL_ATTACK 2 +#define SKILL_BUFF 3 +#define SKILL_AOE 4 +#define SKILL_SELF 5 +#define BUFF_SELF 6 +#define BUFF_AOE 7 +#define AOE_TARGET 8 + +// other constants +#define MAX_SKILL 90 +#define SKILL_DELAY 500 //ms + +// weapon type +#define ONE_HAND_SWORD 211 +#define ONE_HAND_BUNT_SWORD 212 +#define TWO_HAND_SWORD 221 +#define SPEAR 222 +#define TWO_HAND_AXE 223 +#define BOW 231 +#define GUN 232 +#define LAUNCHER 233 +#define STAFF 241 +#define WAND 242 +#define KATAR 251 +#define DOUBLE_SWORD 252 +#define DUAL_GUN 253 +#define CROSSBOW 271 + +//equip type +#define MASK 1 +#define CAP 2 +#define BODY 3 +#define GLOVE 4 +#define SHOE 5 +#define BACK 6 +#define JEWEL 7 +#define WEAPON 8 +#define SUBWEAPON 9 +#define CONSUMIBLE 10 +#define JEM 11 +#define NATURAL 12 +#define QUEST 13 +#define PAT 14 + +//time +#define DAY 0 +#define MORNING 1 +#define EVENING 2 +#define NIGHT 3 + +// Quests + +#define sIsSummon 1 +#define sGender 2 +#define sBirthStone 3 +#define sJob 4 +#define sUnion 5 +#define sUnionPoints 83 +#define sRanking 6 +#define sReputation 7 +#define sFace 8 +#define sHair 9 + +#define sStrength 10 +#define sDexterity 11 +#define sIntelligence 12 +#define sConcentration 13 +#define sCharm 14 +#define sSensibility 15 + +#define sHP 16 +#define sMP 17 + +#define sAttackPower 18 +#define sDefense 19 +#define sAccuracy 20 +#define sMagicResistance 21 +#define sDodge 22 +#define sMoveSpeed 23 +#define sAttackSpeed 24 +#define sMaxWeight 25 +#define sCritical 26 + +#define sHPRate 27 +#define sMPRate 28 +#define sMPDecrease 29 + +#define sEXPRate 30 +#define sLevel 31 +#define sStatPoints 32 +#define sTendency 33 +#define sPvp 34 +#define sHeadSize 35 +#define sBodySize 36 +#define sSkillPoints 37 + +#define sMaxHP 38 +#define sMaxMP 39 + +#define sMoney 40 +#define sUnarmedAtkPow 41 +#define sOneHandAtkPow 42 +#define sTwoHandAtkPow 43 +#define sBowAtkPow 44 +#define sGunAtkPow 45 +#define sMagicAtkPow 46 +#define sCrossbowAtkPow 47 +#define sCombatAtkPow 48 + +#define sBowAtkSpd 49 +#define sGunAtkSpd 50 +#define sCombatAtkSpd 51 + +#define sMoveSpeed2 52 //Passive +#define sDefense2 53 //Passive + +#define sMaxHP2 54 //Passive +#define sMaxMP2 55 //Passive +#define sHPRecovery 56 +#define sMPRecovery 57 + +#define sMaxWeight2 58 +#define sBuyDiscount 59 +#define sSalesPremium 60 +#define sMPSpendDecrease 61 +#define sExpandSummonGauge 62 +#define sItemDropIncrease 63 +#define sIncreaseSummonGauge 63 + +#define sCurrentPlanet 75 +#define sStamina 76 + +#define sClanPoints 92 + + + +#define sNoStorageCharge 93 +#define sExpandStorage 95 +#define sRemodelShop 96 +#define sCartGauge 97 + +// Status Type +#define sNone 0 +#define sPoison1 7 +#define sPoison2 8 +#define sPoison3 9 +#define sPoison4 10 +#define sPoison5 11 +#define sMaxHPUp 12 +#define sMaxMPUp 13 +#define sDash 14 +#define sSlow 15 +#define sHasteAtk 16 +#define sSlowAtk 17 +#define sAtkUp 18 +#define sAtkDown 19 +#define sDefUp 20 +#define sDefDown 21 +#define sMResUp 22 +#define sMResDown 23 +#define sAccUp 24 +#define sAccDown 25 +#define sCritUp 26 +#define sCritDown 27 +#define sDodgeUp 28 +#define sDodgeDown 29 +#define sMute 30 +#define sSleep 31 +#define sFaint 32 +#define sCamo 33 +#define sInvis 34 +#define sShield 35 +#define sAddDmg 36 +#define sCurse 37 +#define sRecover 38 +#define sDispell 39 +#define sHPMPRest 40 +#define sHPMPRest2 41 +#define sHPMPRest3 42 + +#define sHPDown 43 +#define sMaxHPUp2 44 +#define sMaxMPUp2 45 +#define sDash2 46 +#define sHasteAtk2 47 +#define sAtkUp2 48 +#define sDefUp2 49 +#define sMResUp2 50 +#define sAccUp2 51 +#define sCritUp2 52 +#define sDodgeUp2 53 +#define sAddDmg2 54 +#define sDetect 55 +#define sTaunt 56 +#define sInvinc 57 +#define sFlame 58 +#define sDefDown2 59 +#define sSlow2 60 +#define sFlame2 61 +#define sHPRes100 62 +#define sHPRes200 63 +#define sHPRes300 64 +#define sHPRes40 65 +#define sHPRes80 66 +#define sHPRes120 67 +#define sMPRes40 68 +#define sMPRes80 69 +#define sMPRes120 70 +#define sMPRes10 71 +#define sMPRes15 72 +#define sMPRes20 73 +#define sFlame3 74 +#define sHPRes160 75 +#define sMPRes25 76 +#define sFlame4 77 +#define sFlame5 78 +#define sFlame6 79 +#define sFlame7 80 + + +// Skills - Classes +#define cVisitor 0 +#define cSoldier 1 +#define cMuse 2 +#define cHawker 3 +#define cDealer 4 +#define cKnight 11 +#define cChampion 12 +#define cMage 13 +#define cCleric 14 +#define cRaider 15 +#define cScout 16 +#define cBourgeois 17 +#define cArtisan 18 +#define cSoldierJob 41 +#define cMuseJob 42 +#define cHawkerJob 43 +#define cDealerJob 44 +#define cFirstJob 46 +#define cSecondJob 47 +#define cThirdJob 48 +#define cSoldier2nd 51 +#define cMuse2nd 52 +#define cHawker2nd 53 +#define cDealer2nd 54 +#define cSoldier3rd 56 +#define cMuse3rd 57 +#define cHawker3rd 58 +#define cDealer3rd 59 +#define cKnightJob 61 +#define cChampionJob 62 +#define cMageJob 63 +#define cClericJob 64 +#define cRaiderJob 65 +#define cScoutJob 66 +#define cBourgeoisJob 67 +#define cArtisanJob 68 +#define cSoldierHawker 71 + +// Skills - Types +#define tBasic 1 +#define tCraft 2 +#define tDamage 3 // Seen (Start) +#define tWeaponUp 4 +#define tWeaponUp2 5 +#define tSpell 6 // Seen (Start) (This one is odd, an AOE skill, that uses Start) + +#define tAOEDamage 7 // Seen (AOE) +#define tContinueSelf2 8 // Seen (Self) +#define tContinuing2 9 +#define tRecoverSelf 10 // Seen (Self) +#define tRecover 11 // Seen (Start) +#define tContinueSelf 12 // Seen (Self) +#define tContinue 13 +#define tSummon 14 +#define tPassive 15 +#define tEmotion 16 +#define tAOEAttackSelf 17 // Seen (Self) +#define tReservation 18 +#define tAbsorb 19 +#define tResurrect 20 + +// Skills - Targets +#define tYourself 0 +#define tPartyMember 1 +#define tClanMember 2 +#define tAlly 3 +#define tMonster 4 +#define tHostileCharacter 5 +#define tHostileTeamMember 6 +#define tAllCharacters 7 +#define tAllMembers 8 +#define tFaintedAlly 9 +#define tHostileMonster 10 + +// Stats calc - Jobs +#define jVisitor 0 +#define jSoldier 111 +#define jKnight 121 +#define jChampion 122 +#define jMuse 211 +#define jMage 221 +#define jCleric 222 +#define jHawker 311 +#define jRaider 321 +#define jScout 322 +#define jDealer 411 +#define jBourgeois 421 +#define jArtisan 422 + + + + +#include "../common/sockets.h" + +//LTB strings +struct CLTBstring +{ + char LTBstring[200]; + char NPCname[50]; + char QuestNpcName[50]; + char QuestLTB[200]; +}; + +// Hold party experience when kill a moster +struct CPartyExp +{ + class CParty* thisparty; + UINT exp; + UINT num; + UINT partymember[50]; + UINT maxlevel; + bool flag; +}; + +// Equip information +struct CEquip +{ + UINT id; + char name[50]; + UINT itemtype; + UINT equiptype; + UINT type; + UINT price; + UINT pricerate; + UINT weight; + UINT quality; + UINT level; + UINT material; + UINT occupation[4]; + UINT condition1[3]; + UINT condition2[3]; + UINT stat1[3]; + UINT stat2[3]; + UINT durability; + UINT defense; + UINT magicresistence; + UINT attackdistance; + UINT attackpower; + UINT attackspeed; + UINT magicattack; + UINT refinetype; + UINT itemgrade; + UINT movespeed; + UINT raretype; + UINT unionpoint; + UINT breakid; +}; + +// Jem Data +struct CJemData +{ + UINT id; + UINT itemtype; + UINT type; + UINT price; + UINT pricerate; + UINT weight; + UINT quality; + UINT material; + UINT stat1[2];/**/ + UINT stat2[2];/**/ + UINT unionpoint; +}; + +// natural data +struct CNaturalData +{ + UINT id; + UINT itemtype; + UINT type; + UINT price; + UINT pricerate; + UINT weight; + UINT quality; + UINT pricevalue; + UINT unionpoint; +}; + +// pat data +struct CPatData +{ + UINT id; + UINT itemtype; + UINT type; + UINT price; + UINT pricerate; + UINT weight; + UINT quality; + UINT material; + UINT partversion; + UINT level; + UINT condition[2]; + UINT modifier[2]; + UINT maxfuel; + UINT fuelcons; + UINT speed; + UINT attackdistance; + UINT attackpower; + UINT attackspeed; + UINT unionpoint; + UINT durability; +}; + +struct CProductData +{ + UINT id; + UINT matType; // used for material type in crafting + UINT item[5]; + UINT itemtype[5]; // item type + UINT itemid[5]; // item number + UINT amount[5]; +}; + +struct CBreakData +{ + UINT id; //id of the break + UINT ItemId; //item being broken + UINT Mat[20]; //material id + UINT Min[20]; //minimum count + UINT Max[20]; //maximum count + UINT Chance[20]; //chance for this item + UINT MinDis; //Minimum number of items from the list + UINT MaxDis; //Maximun number of items from the list + UINT ItemCount; //Count of items in the list +}; + +struct CCSellData +{ + UINT id; + UINT item[49]; +}; + +struct CUseData +{ + UINT id; + UINT itemtype; + UINT restriction; + UINT type; + UINT price; + UINT pricerate; + UINT weight; + UINT quality; + UINT usecondition[2]; + UINT useeffect[2]; + UINT pricevalue; + UINT unionpoint; +}; + +struct CVector2D +{ + CVector2D() : x(0), y(0) {} + CVector2D(dword nX, dword nY) : x(nX), y(nY) {} + dword x, y; + + bool operator == ( const CVector2D& otherpoint ){ + return (x == otherpoint.x) && (y == otherpoint.y); + } + + bool operator != ( const CVector2D& otherpoint ){ + return (x != otherpoint.x) || (y != otherpoint.y); + } +}; + +struct CVector2F +{ + CVector2F() : x(0), y(0) {} + CVector2F(float nX, float nY) : x(nX), y(nY) {} + float x, y; + + bool operator == ( const CVector2F& otherpoint ) + { + return (fabs(this->x-otherpoint.x)<0.001f && fabs(this->y-otherpoint.y)<0.001f); + } +}; + +struct SBasic +{ + word exp; + word map; + CVector2F pos; +}; + +// ----------------------------------------------------------------------------------------- +// A 2d point, for positions +// ----------------------------------------------------------------------------------------- +struct fPoint +{ + float x; + float y; + float z; // Unused? +}; + +// AIP data +struct CAIPData +{ + char* filename[200]; + +}; + +// List Mob Data +struct CNPCData +{ + UINT id; + char name[50]; + UINT life; + UINT wspeed; + UINT rspeed; + BYTE stance; + UINT dspeed; + UINT weapon; + UINT subweapon; + UINT level; + UINT hp; + UINT atkpower; + UINT hitrate; + UINT defense; + UINT magicdefense; + UINT dodge; + float atkspeed; + UINT magicattack; + UINT AI; + UINT exp; + UINT dropid; + UINT money; + UINT item; + UINT tab1; + UINT tab2; + UINT tab3; + UINT specialtab; + float atkdistance; + UINT aggresive; + char QTrigger[20]; + UINT shp; + UINT dialogid; + UINT eventid; + + UINT side; + UINT sidechance; + UINT zulydrop; + UINT mapdrop; + UINT mobdrop; + UINT leveldrop; + UINT dropchance; + + dword EventID; + SBasic basic; + bool IsMoving; + CVector2F newPos; + CVector2D lastBlock; + CVector2D curBlock; + dword _EntityType; + UINT refNPC; + + UINT die_quest; +}; + +// Store the damage for monster to give exp +struct MonsterDamage +{ + UINT charid; + long int damage; +}; + +struct CDropInfo +{ + BYTE type; + DWORD item; + QWORD prob; +}; + +// For store the drops info +struct CMDrops +{ + UINT id; + UINT zuly; + UINT zulyprob; + vector Drops; + UINT level_min;//for map drops + UINT level_max;//for map drops + UINT level_boss;//for map drops + UINT probmax; + + UINT prob; + UINT map; + UINT mob; + UINT itemtype; + UINT itemnum; + UINT alt[8]; + //experimental structure for New Drops + UINT Dropid; + UINT item[100]; + UINT chance[100]; + dword RunningTotal[100]; + dword TotalChance; + +}; + +// For store the Buff info +struct CBuff +{ + unsigned int Buff;//store the buff type + unsigned int value1;//buff value 1 (direct) + unsigned int value2;//buff value 2 (%) + unsigned int Evalue;//Extra value 1 (buffer int +) + unsigned int Duration; //Skill Duration + clock_t bufftime;//Buff Time +}; + +// ----------------------------------------------------------------------------------------- +// For store the Buff info +// ----------------------------------------------------------------------------------------- +struct CBuffValue +{ + int value1; + int value2; + int value3; +}; + +// ----------------------------------------------------------------------------------------- +// For store the Buff info +// ----------------------------------------------------------------------------------------- +struct CPSkill +{ + int buff[3];//Buff type + int value1[3]; + int value2[3]; +}; + +// ----------------------------------------------------------------------------------------- +// For store the clan info +// ----------------------------------------------------------------------------------------- +struct SClan +{ + int id; + int logo; + int back; + int grade; + int cp; + char name[50]; + char slogan[100]; + char news[200]; +}; + +// ----------------------------------------------------------------------------------------- +// GM Respawn Mobs (50 points max) +// ----------------------------------------------------------------------------------------- +struct CRespawnPoints +{ + int d;//flag to duplicate + int b;//flag to store + int n;//num points + int min;//mob min + int max;//mob max + int map;//map + int mobID;//mob id + int respawntime;//respawn time + fPoint points[50];//points +}; + +// ----------------------------------------------------------------------------------------- +// An item that a client owns +// ----------------------------------------------------------------------------------------- +class CItem +{ +public: + unsigned short GetPakHeader( ); + unsigned GetPakData( ); + unsigned GetVisInfo( ); + void Clear( ); + + inline bool IsStackable() + { + return (itemtype >= 10) && (itemtype <= 13); + }; + unsigned itemnum; + UINT itemtype; + UINT refine; + UINT lifespan; + UINT durability; + bool socketed; + bool appraised; + int count; + UINT stats; + UINT gem; + UINT quality; + UINT type; + UINT raretype; +}; + +// ----------------------------------------------------------------------------------------- +// Selling / buying items +// ----------------------------------------------------------------------------------------- +struct BSItem { + int slot; + int count; + long int price; + int head; + int data; +}; + +// ----------------------------------------------------------------------------------------- +// A typical npc +// ----------------------------------------------------------------------------------------- +struct CNPC +{ + unsigned short clientid; + unsigned char posMap; + unsigned npctype; + fPoint pos; + float dir; + CNPCData* thisnpc; + clock_t lastAiUpdate; +}; + +struct SWarp { + SWarp() : TargetPoint(0), TargetMap(0), SourceMap(0){pos.x = 0; pos.y = 0;} + ~SWarp(){} + + CVector2F pos; + dword SourceMap; + dword TargetMap; + strings TargetPoint; +}; + +// Zone Data +struct SZonPoint { + CVector2F pos; + strings name; +}; + +// Item data object +struct CItemData +{ + unsigned type; + unsigned id; +}; + +// Structure for holding loaded STB data + +struct CNPCTrigger +{ + unsigned hash; + unsigned char linkcount; + unsigned char conditioncount; + unsigned char rewardcount; + unsigned* links; + unsigned char** actions; +}; + +// ----------------------------------------------------------------------------------------- +// Skill Data +// ----------------------------------------------------------------------------------------- +struct CSkills +{ + UINT id; + UINT skillid; + UINT level; + UINT type; + UINT skilltype; + UINT range; + UINT target; + UINT status[2]; + UINT aoerange; + UINT successrate; + UINT costtype[2]; + UINT costamount[2]; // New + UINT duration; + UINT atkpower; + UINT mp; + UINT costtype2; // New + UINT costamount2; // New + UINT cooldown; // New + UINT weapon[5]; + UINT rskill[3]; + UINT lskill[3]; + UINT c_class[4]; + UINT req[2]; + UINT reqam[2]; + UINT zuly; // New + UINT clevel; + UINT success; + UINT sp; + UINT buff[3]; + UINT value1[3]; + UINT value2[3]; + UINT nbuffs; + + unsigned short int aoe; + UINT aoeradius; + UINT script; + UINT svalue1; + +}; + +//LMA: Grade structure. +struct CGrade +{ + UINT atk_percent; + UINT atk_addbonus; + UINT acc_percent; + UINT acc_addbonus; + UINT def_percent; + UINT def_addbonus; + UINT mdef_percent; + UINT mdef_addbonus; + UINT dodge_percent; + UINT dodge_addbonus; +}; + +// ----------------------------------------------------------------------------------------- +// Status Object +// ----------------------------------------------------------------------------------------- +struct CStatus +{ + UINT id; // + UINT type; // Unk [1] + UINT duplication; // Unk [2] + UINT dir; // 0: Up 1: Down 2: ? [3] + UINT repeat; // 1: Repeat 2: Once 3: Special [4] + UINT ability[2]; // Status # to call? [5][7] + UINT amount[2]; // # to increase/decrease by? [6][8] + UINT decrease; // 1: decrease 0: increase [17] +}; + +// ----------------------------------------------------------------------------------------- +// Sell data object +// ----------------------------------------------------------------------------------------- +struct CSellData +{ + unsigned id; + CItem items[48]; +}; + +// ----------------------------------------------------------------------------------------- +// User skill info +// ----------------------------------------------------------------------------------------- +struct CSkill +{ + unsigned id; + unsigned level; + CSkills* thisskill; +}; + +// ----------------------------------------------------------------------------------------- + +// An item or zuly dropped on the ground +// ----------------------------------------------------------------------------------------- +struct CDrop +{ + unsigned short clientid; + unsigned char posMap; + bool spawned; + fPoint pos; + char type; + unsigned amount; + CItem item; + time_t droptime; + unsigned short owner; + class CParty* thisparty; +}; + +// ----------------------------------------------------------------------------------------- +// pvp zones +// ----------------------------------------------------------------------------------------- +struct CPVPZones +{ + int map; +}; +// ----------------------------------------------------------------------------------------- +// A teleport gate object +// ----------------------------------------------------------------------------------------- +struct CTeleGate +{ + unsigned short id; + fPoint src; + unsigned char srcMap; + fPoint dest; + unsigned char destMap; +}; + +// ----------------------------------------------------------------------------------------- +// A respawn point object +// ----------------------------------------------------------------------------------------- +struct CRespawnPoint +{ + unsigned short id; + fPoint dest; + unsigned char destMap; + unsigned char radius; + bool masterdest; +}; + +// ----------------------------------------------------------------------------------------- +// A monster spawn zone +// ----------------------------------------------------------------------------------------- +struct CSpawnArea +{ + UINT id; + UINT type; + //0 = inactive + //1 = normal + //2 = cascade monster triggered by specific trigger defined in typeoftrigger + //3 = cascade to max. As 2 but all possible mobs are spawned up to the spawn maximum + //4 = minion spawns. i.e. scarabs around a Guardian Tree. don't use a spawn area but spawn around the boss + //5 = minion spawns to max. As 4 but all possible mobs are spawned to the spawn maximum. + UINT typeoftrigger; + //0 = disabled + //1 = triggered from any event in the map + //2 = triggered only from events in a specific spawn + UINT limit; + //0 = day or night spawns permitted + //1 = daytime spawns only + //2 = nighttime spawns only + UINT spawnkey; + //0 = no key defined + //1 = On spawn + //2 = On die + //3 = On being in a battle ( Isonbattle() ) + //4 = On target mob being alive + UINT map; + UINT min; + UINT max; + UINT respawntime; + UINT montype; + UINT pcount; + UINT triggertype; + UINT triggeramount; + UINT triggercount; + UINT aggressive; + UINT amon; + fPoint* points; + fPoint point; + UINT radius; + clock_t lastRespawnTime; + CNPCData* thisnpc; + CMDrops* mobdrop; + CMDrops* mapdrop; + UINT AI; + UINT RefVar; + UINT RefVal; +}; + +#ifdef USEIFO +struct CMob { + UINT id; + UINT mobId; + bool tactical; + UINT amount; + CNPCData* thisnpc; + CMDrops* mobdrop; + CMDrops* mapdrop; +}; + +struct CMobGroup { + UINT id; + UINT map; + UINT limit; + UINT active; + UINT tacticalpoints; + UINT respawntime; + UINT basicKills; + UINT curTac; + UINT curBasic; + fPoint point; + UINT range; + clock_t lastRespawnTime; + vector basicMobs; + vector tacMobs; +}; +#endif + +struct CUseInfo +{ + int itemnum; + int itemtype; + int usescript; + int usetype; + int usevalue; +}; + +struct CItemType +{ + int itemnum; + int itemtype; + int probability; +}; + +struct CItemSlot +{ + UINT newslot[2]; + UINT nslot; + bool flag; +}; + +// Buying List (Shop) +struct CBuying +{ + CItem item; + long int price; + UINT slot; + UINT count; +}; + +// Selling List (Shop) +struct CSelling +{ + UINT slot; + UINT count; + long int price; +}; + +// MONSTER <--------------------------- +struct MPosition // Monster position +{ + UINT Map; + UINT SpawnID; + fPoint Spos; // Start position + fPoint Cpos; // Current Position + fPoint Dpos; // destiny Position +}; + +struct MDrop // monster drops +{ + bool drop; + unsigned int firsthit; + unsigned int firstlevel; + CMDrops* mobdrop; + CMDrops* mapdrop; +}; + +struct CBValue +{ + UINT NewValue; + UINT Value; + UINT Position; +}; + + +// ITEMS +struct CEquipList +{ + CEquip *Index[5000]; + vector Data; + CEquip* nullequip; + UINT STBMax; +}; + +struct CJemList +{ + CJemData *Index[4000]; + vector Data; + CJemData* nulljem; +}; + +struct CNaturalList +{ + CNaturalData *Index[1000]; + vector Data; + CNaturalData* nullnatural; +}; + + +struct CPatList +{ + CPatData *Index[1000]; + vector Data; + CPatData* nullpat; +}; + +struct CProductList +{ + CProductData* Index[2000]; + vector Data; + CProductData* nullproduct; +}; + +struct CBreakList +{ + CBreakData* Index[6000]; + vector Data; + CBreakData* nullbreak; +}; + +struct CSellList +{ + CCSellData* Index[1000]; + vector Data; + CCSellData* nullsell; +}; + +struct CUseList +{ + CUseData* Index[2000]; + vector Data; + CUseData* nulluse; +}; + +struct CMapList +{ + class CMap* Index[300]; + vector Map; + class CMap* nullzone; +}; + +struct CItemStas +{ + UINT stat[2]; + UINT value[2]; +}; + +struct CFairy +{ + UINT ListIndex; + clock_t LastTime; + UINT WaitTime; + bool assigned; + bool scroll; +}; + +// Quests + +struct SQuest +{ + word QuestID; + dword StartTime; + word Variables[10]; + byte Switches[4]; + CItem Items[5]; + byte unknown[6]; + + bool GetSwitchBit( dword switchId ) + { + dword byteId = switchId / 8; + dword bitId = switchId % 8; + return ((Switches[byteId] >> bitId) & 1); + } + + void SetSwitchBit( byte switchId, byte value ) + { + dword byteId = switchId / 8; + dword bitId = switchId % 8; + Switches[byteId] = Switches[byteId] | ((value?1:0) << bitId); + } + + void AddItem(CItem* item, byte btOp = 2) + { + for(dword i = 0; i < 6; i++) + { + if(Items[i].GetPakHeader() == item->GetPakHeader()) + { + if(btOp == 1) + { + Items[i].count += item->count; + return; + } + else if(btOp == 0) + { + if(Items[i].count <= item->count) + Items[i].Clear(); + else + Items[i].count -= item->count; + return; + } + } + if(btOp == 0) continue; + if(Items[i].GetPakHeader() != 0) continue; + Items[i].itemnum = item->itemnum; + Items[i].itemtype = item->itemtype; + Items[i].count = item->count; + return; + } + } + + void Clear() + { + memset(this, 0, sizeof(SQuest)); + } + +}; + +struct SQuestData +{ + word EpisodeVar[5]; + word JobVar[3]; + word PlanetVar[7]; + word UnionVar[10]; + + SQuest quests[10]; + byte flags[0x40]; + byte Qflags[64][8]; //new quest flag structure for testing PY + + void SetFlag( dword flagid, bool value ) + { + dword byteid = flagid / 8; + dword bitid = flagid % 8; + //flags[byteid] = flags[byteid] | ((value?1:0) << bitid); + Qflags[byteid][bitid] = value?1:0; + } + + bool GetFlag( dword flagid ) + { + dword byteid = flagid / 8; + dword bitid = flagid % 8; + //return ((flags[byteid] >> bitid) & 1); + return (Qflags[byteid][bitid] == 1); + } +}; + +struct TowerDef +{ + fPoint pos; + float X; + float Y; + UINT WPType; +}; + +enum class QuestResult +{ + RESULT_QUEST_REPLY_NULL = 0, + RESULT_QUEST_REPLY_ADD_SUCCESS = 1, + RESULT_QUEST_REPLY_ADD_FAILED = 2, + RESULT_QUEST_REPLY_DEL_SUCCESS = 3, + RESULT_QUEST_REPLY_DEL_FAILED = 4, + RESULT_QUEST_REPLY_TRIGGER_SUCCESS = 5, // This packet is received, the client applies the compensation. + RESULT_QUEST_REPLY_TRIGGER_FAILED = 6 +}; + + + +#endif + diff --git a/Src/World Server/extrafunctions.cpp b/Src/World Server/extrafunctions.cpp new file mode 100644 index 0000000..f7ea349 --- /dev/null +++ b/Src/World Server/extrafunctions.cpp @@ -0,0 +1,1173 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Build Item Head +unsigned CWorldServer::BuildItemHead( CItem thisitem ) +{ + if (thisitem.count==0) return 0; + return ( ( thisitem.itemnum & 0x7ffffff ) << 5 ) | ( thisitem.itemtype & 0x1f ); +} + +// Build Item Data +unsigned CWorldServer::BuildItemData( CItem thisitem ) +{ + if (thisitem.count==0) return 0; + if ( thisitem.itemtype >= 10 && thisitem.itemtype <= 13 ) + { + return thisitem.count; + } + else + { + unsigned part1 = (thisitem.refine>>4) << 28; + unsigned part2 = (thisitem.appraised?1:0) << 27; + unsigned part3 = (thisitem.socketed?1:0) << 26; + unsigned part4 = (thisitem.lifespan*10) << 16; + unsigned part5 = thisitem.durability << 9; + unsigned part6 = thisitem.stats; + unsigned part7 = thisitem.gem; + if (!part7==0)part6=0; + return part1 | part2 | part3 | part4 | part5 | part6 | part7; + } +} + +// Get Item By Head and Data (Coded by Caali) +CItem CWorldServer::GetItemByHeadAndData( unsigned head, unsigned data ) +{ + CItem thisitem; + thisitem.Clear ( ); + + //Get info from ItemHead + thisitem.itemnum = (head >> 5) & 0x7ffffff; + head ^= (thisitem.itemtype & 0x7ffffff) << 5; + thisitem.itemtype = head & 0x1f; + + //Get info from ItemData + if( thisitem.itemtype >= 10 && thisitem.itemtype <= 13 ) //Stackable + { + thisitem.refine = 0; + thisitem.appraised = 0; + thisitem.socketed = 0; + thisitem.lifespan = 100; + thisitem.durability = 40; + thisitem.gem = 0; + thisitem.stats = 0; + thisitem.count = (int)data; + } + else //Non-stackable + { + thisitem.refine = (data >> 28) << 4; + data-=(thisitem.refine>>4) << 28; + thisitem.appraised = ((data >> 27)==1)?true:false; + data-=(thisitem.appraised?1:0) << 27; + thisitem.socketed = ((data >> 26)==1)?true:false; + data-=(thisitem.socketed?1:0) << 26; + thisitem.lifespan = (data >> 16) / 10; + data-=(thisitem.lifespan*10) << 16; + thisitem.durability = data >> 9; + data-=thisitem.durability << 9; + thisitem.gem = data; + data-=thisitem.gem; + thisitem.count = 1; + } + return thisitem; +} + +//----Build Item Show +unsigned CWorldServer::BuildItemShow(CItem thisitem) +{ + if (thisitem.gem == 0) + { + unsigned part1 = thisitem.refine*0x10000; + unsigned part2 = thisitem.itemnum; + return part2|part1; + } + else + { + unsigned part1 = thisitem.refine*0x10000; + unsigned part2 = thisitem.itemnum; + unsigned part3 = (0xd0000)+(((thisitem.gem-320)*0x400)); + return part3|part2|part1; + } +} + + +//----Build Item refine +unsigned CWorldServer::BuildItemRefine(CItem thisitem) +{ + if (thisitem.gem == 0) + return ((thisitem.refine)*256); + else + { + return (0xd00+(thisitem.gem-320)*4)+((thisitem.refine)*256); + } +} + +// Send a packet too all connected clients +void CWorldServer::SendToAll( CPacket* pak ) +{ + for(UINT i=0;iplayer; + if (otherclient->Session->inGame) + otherclient->client->SendPacketCpy( pak ); + } +} + +// Send a packet too all X who are visible +// -- CLIENT -- +void CWorldServer::SendToVisible( CPacket* pak, CPlayer* thisclient, bool dothisclient ) +{ + for(unsigned j=0; jVisiblePlayers.size(); j++) + { + thisclient->VisiblePlayers.at( j )->client->SendPacketCpy( pak ); + } + if(dothisclient) + thisclient->client->SendPacketCpy( pak ); +} + +void CWorldServer::SendToVisible( CPacket* pak, CPlayer* thisclient, CPlayer* xotherclient ) +{ + for(unsigned j=0; jVisiblePlayers.size(); j++) + { + CPlayer* otherclient = thisclient->VisiblePlayers.at( j ); + if(otherclient==xotherclient) + continue; + otherclient->client->SendPacketCpy( pak ); + } +} + +// -- MONSTER -- +void CWorldServer::SendToVisible( CPacket* pak, CMonster* thismon, CDrop* thisdrop ) +{ + CMap* map = MapList.Index[thismon->Position->Map]; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* otherclient = map->PlayerList.at(i); + if( IsVisible(otherclient, thismon) ) + otherclient->client->SendPacketCpy( pak ); + if(thisdrop!=NULL) + { + otherclient->VisibleDrops.push_back( thisdrop ); + } + } +} + + +// -- CHARACTER -- +void CWorldServer::SendToVisible( CPacket* pak, CCharacter* character, CDrop* thisdrop ) +{ + if(character->IsPlayer( )) + { + SendToVisible( pak, (CPlayer*)character ); + } + else + if(character->IsMonster( )) + { + SendToVisible( pak, (CMonster*)character, thisdrop ); + } +} + +// -- DROP -- +void CWorldServer::SendToVisible( CPacket* pak, CDrop* thisdrop ) +{ + CMap* map = MapList.Index[thisdrop->posMap]; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* otherclient = map->PlayerList.at(i); + if (otherclient->client == NULL) continue; + if (!otherclient->client->isActive) continue; + if( IsVisible(otherclient, thisdrop) ) + otherclient->client->SendPacketCpy( pak ); + } +} + +// Send a packet too all clients on the specified map +void CWorldServer::SendToMap( CPacket* pak, int mapid ) +{ + CMap* map = MapList.Index[mapid]; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* otherclient = map->PlayerList.at(i); + if( otherclient->Session->inGame ) + otherclient->client->SendPacketCpy( pak ); + } +} + +// ----------------------------------------------------------------------------------------- +// Check if an object is visible to a client +// ----------------------------------------------------------------------------------------- +// -- CLIENT -- +bool CWorldServer::IsVisible( CPlayer* thisclient, CPlayer* otherclient ) +{ + for(unsigned j=0; jVisiblePlayers.size(); j++) + { + if (otherclient==thisclient->VisiblePlayers.at(j)) + return true; + } + return false; +} + +// -- MOB -- +bool CWorldServer::IsVisible( CPlayer* thisclient, CMonster* thismon ) +{ + for(unsigned j=0; jVisibleMonsters.size(); j++) + { + if (thismon->clientid == thisclient->VisibleMonsters.at(j)) return true; + } + return false; +} + +// -- DROP -- +bool CWorldServer::IsVisible( CPlayer* thisclient, CDrop* thisdrop ) +{ + for(unsigned j=0; jVisibleDrops.size(); j++) + { + if (thisdrop == thisclient->VisibleDrops.at(j)) return true; + } + return false; +} + +// -- NPC -- +bool CWorldServer::IsVisible( CPlayer* thisclient, CNPC* thisnpc ) +{ + for(unsigned j=0; jVisibleNPCs.size(); j++){ + if (thisnpc==thisclient->VisibleNPCs.at(j)) return true; + } + return false; +} + +// This function gets a new clientID for a npc, monster or mob +unsigned CWorldServer::GetNewClientID( ) { + unsigned i = ClientIDIndex; // Set i (the index value) equal to the current Index Pointer value + for (unsigned j=1; j<0xffff; j++) { // Outer loop will limit the entire function to no more than 65535 iterations + if ( ClientIDList[i] == 0 ) { // Found a free ID at position i + ClientIDList[i] = 1; // Set the value at this position to 1 to indicate it is in use + ClientIDIndex = i + 1; // Set the pointer to the next element of the Array + if (ClientIDIndex >= 0xffff) // If ClientIDIndex is greater than or equal to 65535 then wrap it around to 1 + ClientIDIndex = 1; + return i; // Return the index value of the element that we just found + } + i++; // Increment i + if (i >= 0xffff) // If i is greater than or equal to 65535 then wrap it around to 1 + i = 1; + } + return 0; // After 65535 iterations we failed to find a free ID so return 0 +} + +// This function will free our clientID +void CWorldServer::ClearClientID( unsigned int id ) { + ClientIDList[id] = 0; // Clear the value at the position pointed at by the released ID +} + +// Search a drop by ID +CDrop* CWorldServer::GetDropByID( UINT id, UINT map ) +{ + if(map!=0) + return MapList.Index[map]->GetDropInMap( id ); + for(unsigned j=0; jGetDropInMap( id ); + if(thisdrop != NULL) + return thisdrop; + } + return NULL; +} + +// Search a Monster by ID +CMonster* CWorldServer::GetMonsterByID( UINT id, UINT map ) +{ + if(map!=0) + return MapList.Index[map]->GetMonsterInMap( id ); + for(UINT i=0;iGetMonsterInMap( id ); + if(thismon!=NULL) + return thismon; + } + return NULL; +} + +// Search a Client by Username +CPlayer* CWorldServer::GetClientByUserName( char *username ) +{ + for(UINT i=0;iplayer; + if (strcmp(thisclient->Session->username,username)==0) + return thisclient; + } + return NULL; +} + +// Get Client By ID +CPlayer* CWorldServer::GetClientByID( UINT id, UINT map ) +{ + if(map!=0) + return MapList.Index[map]->GetPlayerInMap( id ); + for(UINT i=0;iplayer; + if (thisclient->clientid==id) + return thisclient; + } + return NULL; +} + +// Search a Client by CharID +CPlayer* CWorldServer::GetClientByCID( UINT id, UINT map ) +{ + if(map!=0) + return MapList.Index[map]->GetCharIDInMap( id ); + for(UINT i=0;iplayer; + if (thisclient->CharInfo->charid==id) + return thisclient; + } + return NULL; +} + +// Seach a client by Charname +CPlayer* CWorldServer::GetClientByCharName( char* name ) +{ + for(UINT i=0;iplayer; + if (strncmp(thisclient->CharInfo->charname,name, 16)==0) + return thisclient; + } + return NULL; +} + +// Get Spawn Area by ID +CSpawnArea* CWorldServer::GetSpawnArea( UINT id, UINT map ) +{ + if(map!=0) + { + for(unsigned j=0; jMonsterSpawnList.size(); j++) + { + CSpawnArea* thisspawn = MapList.Index[map]->MonsterSpawnList.at(j); + if (thisspawn->id==id) + return thisspawn; + } + } + else + { + for(map=0;mapMonsterSpawnList.size(); j++) + { + CSpawnArea* thisspawn = MapList.Index[map]->MonsterSpawnList.at(j); + if (thisspawn->id==id) + return thisspawn; + } + } + } + return NULL; +} + +#ifdef USEIFO +CMobGroup* CWorldServer::GetMobGroup(UINT id, UINT map ) +{ + if (map != 0) + { + for (unsigned j = 0; j < MapList.Index[map]->MobGroupList.size(); j++) + { + CMobGroup* thisgroup = MapList.Index[map]->MobGroupList.at(j); + if (thisgroup->id == id) + return thisgroup; + } + } + else + { + for (map = 0; map < MapList.Map.size(); map++) + { + for (unsigned j = 0; j < MapList.Index[map]->MobGroupList.size(); j++) + { + CMobGroup* thisgroup = MapList.Index[map]->MobGroupList.at(j); + if (thisgroup->id == id) + return thisgroup; + } + } + } + return NULL; +} +#endif + +// delete a spawn +bool CWorldServer::DeleteSpawn( CSpawnArea* spawn ) +{ + if(spawn==NULL) + return false; + for(UINT i=0;imap]->MonsterSpawnList.size();i++) + { + if(MapList.Index[spawn->map]->MonsterSpawnList.at(i)==spawn) + { + MapList.Index[spawn->map]->MonsterSpawnList.erase( MapList.Index[spawn->map]->MonsterSpawnList.begin()+i ); + delete spawn; + return true; + } + } + return false; +} + +// Search NPC by ID +CNPC* CWorldServer::GetNPCByID( UINT id, UINT map ) +{ + if(map!=0) + return MapList.Index[map]->GetNPCInMap( id ); + for(unsigned j=0; jGetNPCInMap( id ); + if (thisnpc!=0) + return thisnpc; + } + return NULL; +} + +// Get Telegate by ID +CTeleGate* CWorldServer::GetTeleGateByID( unsigned int id ) +{ + for(unsigned j=0; jid == id) return thisgate; + } + // Hmm, shit, couldent find it + return NULL; +} + +// Get Triangle Area +float CWorldServer::AreaOfTriangle( fPoint p1, fPoint p2, fPoint p3 ) +{ + return abs((int)((p2.x * p1.y - p1.x * p2.y) + (p3.x * p2.y - p2.x * p3.y) + (p1.x * p3.y - p2.x * p1.y))) / 2; +} + +// Get a random point in triangle area +fPoint CWorldServer::RandInTriangle( fPoint p1, fPoint p2, fPoint p3 ) +{ + fPoint thispoint; + float a = 1.0; + float b = 1.0; + while (a+b>1) { a=(float)(rand()*1.0/RAND_MAX); b=(float)(rand()*1.0/RAND_MAX); } + float c = 1 - a - b; + thispoint.x = (p1.x*a)+(p2.x*b)+(p3.x*c); + thispoint.y = (p1.y*a)+(p2.y*b)+(p3.y*c); + return thispoint; +} + +// Get random point in poly +fPoint CWorldServer::RandInPoly( fPoint p[], int pcount ) +{ + int tnum = 0; + float tval = 0; + float totalarea = 0; + float* areas = new float[pcount-1]; + + for(int i=0; iareas[tnum-1]) break; } + + return RandInTriangle(p[0], p[tnum], p[tnum+1]); +} + +// Get Random point in circle +fPoint CWorldServer::RandInCircle( fPoint center, float radius ) +{ + fPoint thispoint; + float angle = (float)(rand() * ( pi * 2 ) / RAND_MAX); + float dist = (float)sqrt( rand() * 1.0 / RAND_MAX ) * radius; + thispoint.x = cos(angle) * dist + center.x; + thispoint.y = sin(angle) * dist + center.y; + return thispoint; +} + +// Check if a Monster is in a Circle around a Client with a specific range +bool CWorldServer::IsMonInCircle( CPlayer* thisclient, CMonster* thismon, float radius ) +{ + if ( thisclient->Position->Map!=thismon->Position->Map ) return false; + + float dx = ( thisclient->Position->current.x - thismon->Position->current.x ); + float dy = ( thisclient->Position->current.y - thismon->Position->current.y ); + + if ( sqrt( (dx * dx) + (dy * dy) ) <= radius ) + { + return true; + } + else + { + return false; + } +} + +// Check if a Monster is in a Circle around a point with a specific radius +bool CWorldServer::IsMonInCircle( fPoint center, fPoint position, float radius ) +{ + float dx = ( center.x - position.x ); + float dy = ( center.y - position.y ); + + if ( sqrt( (dx * dx) + (dy * dy) ) <= radius ) + { + return true; + } + else + { + return false; + } +} + +// Check if a Player is in a Circle around a Client with a specific range +bool CWorldServer::IsPlayerInCircle( CPlayer* thisclient, CPlayer* otherclient, float radius ) +{ + if ( thisclient->Position->Map!=otherclient->Position->Map ) return false; + + float dx = ( thisclient->Position->current.x - otherclient->Position->current.x ); + float dy = ( thisclient->Position->current.y - otherclient->Position->current.y ); + + if ( sqrt( (dx * dx) + (dy * dy) ) <= radius ) + { + return true; + } + else + { + return false; + } +} + +// Search Respawn by ID +CRespawnPoint* CWorldServer::GetRespawnByID( unsigned id ) +{ + for(UINT i=0;iRespawnList.size();j++) + { + CRespawnPoint* thisrespawn = thismap->RespawnList.at(j); + if(thisrespawn->id==id) + return thisrespawn; + } + } + return NULL; +} + +// Search Respawn by Map +CRespawnPoint* CWorldServer::GetRespawnByMap( int map ) +{ + for(UINT j=0;jRespawnList.size();j++) + { + if(MapList.Index[map]->RespawnList.at(j)->destMap==map) + return MapList.Index[map]->RespawnList.at(j); + } + return NULL; +} + +// Search Skill By ID +CSkills* CWorldServer::GetSkillByID( unsigned int id ) +{ + unsigned int A=0,B=0,C=0; + for(A=0,B=SkillList.size()-1;A<=B;) + { + if(A==B) + { + CSkills* thisskill = (CSkills*) SkillList.at( A ); + if( id = thisskill->id )// lol this function aint working correct + { + //Log(MSG_INFO,"skill returned id = %i", id ); + return thisskill; + } + else + { + Log(MSG_WARNING,"SKILL NOT FOUND! %i", id ); + return NULL; + } + } + C = (A+B)/2; + CSkills* thisskill = (CSkills*) SkillList.at( C ); + if(thisskill->id == id) + { + //Log(MSG_INFO,"skill returned id = %i. C = %i", id,C ); + return thisskill; + } + if(thisskill->id > id) + { + B=C-1; + } + else + { + A=C+1; + } + } + Log( MSG_WARNING,"SKILL NOT FOUNDED! %i", id ); + return NULL; +} + +// Search Status By ID +CStatus* CWorldServer::GetStatusByID( unsigned int id ) +{ + unsigned int A=0,B=0,C=0; + for(A=0,B=StatusList.size()-1;A<=B;) + { + if(A==B) + { + CStatus* thisstatus = (CStatus*) StatusList.at( A ); + if( id = thisstatus->id ) + return thisstatus; + else + { + Log(MSG_WARNING,"STATUS NOT FOUND! %i", id ); + return NULL; + } + } + C = (A+B)/2; + CStatus* thisstatus = (CStatus*) StatusList.at( C ); + if(thisstatus->id == id) + return thisstatus; + if(thisstatus->id > id) + B=C-1; + else + A=C+1; + } + Log( MSG_WARNING,"STATUS NOT FOUND! %i", id ); + return NULL; +} + +// Get Monster Drop By ID +CMDrops* CWorldServer::GetDropData( unsigned int id ) +{ + return NULL; + unsigned int A=0,B=0,C=0; + for(A=0,B=MDropList.size()-1;A<=B;) + { + if(A==B) + { + CMDrops* thismdrop = (CMDrops*) MDropList.at( A ); + if( id == thismdrop->id ){return thismdrop;} + else + { + Log(MSG_WARNING,"DROP NOT FOUNDED! %i", id ); + return 0; + } + } + C = (A+B)/2; + CMDrops* thismdrop = (CMDrops*) MDropList.at( C ); + if(thismdrop->id == id){return thismdrop;} + if(thismdrop->id > id){B=C-1;} + else{A=C+1;} + } + Log(MSG_WARNING,"DROP NOT FOUNDED! %i", id ); + return NULL; +} + +// Get NPC Data by ID +CNPCData* CWorldServer::GetNPCDataByID( unsigned int id ) +{ + unsigned int A=0,B=0,C=0; + for(A=0,B=NPCData.size()-1;A<=B;) + { + if(A==B) + { + CNPCData* thisnpc = (CNPCData*) NPCData.at( A ); + if( id == thisnpc->id ){return thisnpc;} + else + { + Log(MSG_WARNING,"NPC NOT FOUNDED! %i", id ); + return 0; + } + } + C = (A+B)/2; + CNPCData* thisnpc = (CNPCData*) NPCData.at( C ); + if(thisnpc->id == id){return thisnpc;} + if(thisnpc->id > id){B=C-1;} + else{A=C+1;} + } + Log(MSG_WARNING,"NPC NOT FOUNDED! %i", id ); + return NULL; +} + +// Get Consumible Item Info +CUseInfo* CWorldServer::GetUseItemInfo(CPlayer* thisclient, unsigned int slot ) +{ + if(thisclient->items[slot].count<1) + return NULL; + CUseInfo* useitem = new (nothrow) CUseInfo; + if(useitem==NULL) + { + Log(MSG_WARNING, "Error allocing memory GetUseItemInfo"); + return useitem; + } + useitem->itemnum = 0; + useitem->itemtype = 0; + useitem->usescript = 0; + useitem->usetype = 0; + useitem->usevalue = 0; + unsigned int type = 0; + useitem->itemnum = thisclient->items[slot].itemnum; + useitem->itemtype = thisclient->items[slot].itemtype; + type = UseList.Index[useitem->itemnum]->type; + switch(type) + { + case 311://Medicine + case 312://Food + { + useitem->usescript = 1; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + } + break; + case 313://Magic Item [scrolls/summons/fireworks/etc] + { + // scrolls [maxhp,attackpower up, ... ] + if((useitem->itemnum >= 301 && useitem->itemnum <= 323)|| (useitem->itemnum >= 961 && useitem->itemnum <= 964) ||(useitem->itemnum >= 331 && useitem->itemnum <= 340)) + { + useitem->usescript = 7; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + useitem->usevalue = 0; + } + else//Return Scrolls + if(useitem->itemnum > 349 && useitem->itemnum < 367) + { + if( thisclient->Stats->MP < 33 ){delete useitem;} + thisclient->Stats->MP -= 32; + useitem->usescript = 2; + switch( useitem->itemnum ) + { + //Adventure plain [credits to tomiz] + case 350: + useitem->usetype = 22; + useitem->usevalue = 51105310; + break; + //Zant + case 351: + useitem->usetype = 1; + useitem->usevalue = 52405192; + break; + //Junon + case 352: + useitem->usetype = 2; + useitem->usevalue = 55095283; + break; + //Eucar + case 353: + useitem->usetype = 51; + useitem->usevalue = 53665099; + break; + //Xita + case 354: + useitem->usetype = 61; + useitem->usevalue = 54344607; + break; + //Goblin cave + case 360: + useitem->usetype = 31; + useitem->usevalue = 55185444; + break; + //Desert of dead + case 361: + useitem->usetype = 29; + useitem->usevalue = 50825013; + break; + //El Verloon + case 362: + useitem->usetype = 24; + useitem->usevalue = 55275377; + break; + //George of Silence + case 363: + useitem->usetype = 28; + useitem->usevalue = 54674783; + break; + //Shady Jungle + case 364: + useitem->usetype = 62; + useitem->usevalue = 57515196; + break; + //Sunshine Beach + case 366: + useitem->usetype = 37; + useitem->usevalue = 50055382; + break; + default: + break; + } + } + else //Charm Scrolls + if( (useitem->itemnum>380 && useitem->itemnum<384) ) + { + useitem->usescript = 3; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + thisclient->CharInfo->stamina -= 5000; + } + else //Dance Scroll + if( (useitem->itemnum>370 && useitem->itemnum<377) || + (useitem->itemnum>389 && useitem->itemnum<398) ) + { + useitem->usescript = 12; // should not be treated the same as firecrackers. this is a skill + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + } + else // Firecrackers + if( useitem->itemnum == 913 || (useitem->itemnum >= 921 && useitem->itemnum <= 929) || + (useitem->itemnum >= 991 && useitem->itemnum <= 995) ) + { + useitem->usescript = 13; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + //useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + } + else // Emotions + if( (useitem->itemnum>970 && useitem->itemnum<979) ) + { + useitem->usescript = 4; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + } + else // Summons + if(useitem->itemnum > 400 && useitem->itemnum < 440) + { + if( thisclient->CharInfo->stamina < 100 ) + return NULL; + thisclient->CharInfo->stamina -= 100; + useitem->usescript = 5; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + CSkills* skill = GetSkillByID(useitem->usetype); + if(skill == NULL) + { + Log(MSG_INFO,"Useitem returnd a NULL skill"); + return NULL; + } + useitem->usevalue = skill->svalue1; + } + else // Summons 2. Special summons such as goodie bags + if(useitem->itemnum > 908 && useitem->itemnum < 918) + { + if( thisclient->CharInfo->stamina < 100 ) + return NULL; + thisclient->CharInfo->stamina -= 100; + useitem->usescript = 5; // testing at 11 for a bit. Gives no owner to the summons. + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[1]; + CSkills* skill = GetSkillByID(useitem->usetype); + if(skill == NULL) + { + Log(MSG_INFO,"Useitem returnd a NULL skill"); + return NULL; + } + useitem->usevalue = skill->svalue1; + } + else // Snowball + if( useitem->itemnum == 326 ) + { + useitem->usescript = 6; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + } + else // fairy scroll + if( useitem->itemnum == 4860 ) // random number. please adjust to the correct id for your scroll + { + useitem->usescript = 14; // new category for fairy scroll + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + } + else + { + Log( MSG_WARNING, "Unknown Item %i - Type %i",useitem->itemnum,type); + delete useitem; + return NULL; + } + } + break; + case 314://Skill Book + useitem->usescript = 10; + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + break; + case 315://Repair Tool + delete useitem; + return NULL; + break; + case 316://Quest Scroll + delete useitem; + return NULL; + break; + case 317://Engine Fuel + delete useitem; + return NULL; + break; + case 320://Automatic Consumption + delete useitem; + return NULL; + break; + case 321://Time Coupon / shop coupon / storage expansion coupon + useitem->usetype = UseList.Index[useitem->itemnum]->useeffect[0]; + useitem->usevalue = UseList.Index[useitem->itemnum]->useeffect[1]; + switch( useitem->usetype ) + { + case 96: // shop coupons + delete useitem; + thisclient->Shop->ShopType = UseList.Index[useitem->itemnum]->quality; + break; + case 94: // free storage coupon + break; + case 95: // storage expansion coupon + break; + case 133: // Exp medals + delete useitem; + thisclient->Stats->ItemXPRate = 2; + if (thisclient->Stats->ItemXPTime == 0) // don't reset timer if it already has time on it + { + thisclient->Stats->ItemXPTime = clock( ); // set start time for clock countdown + } + thisclient->Stats->ItemXPTimer += useitem->usevalue * 600000; //convert to seconds and add to time remaining + break; + case 138: // Exp nulifier + delete useitem; + thisclient->Stats->ItemXPRate = -1; // set to -1 for XP nulification + thisclient->Stats->ItemXPTime = clock( ); // set start time for clock countdown. This overwrites any other XP timer + thisclient->Stats->ItemXPTimer += useitem->usevalue * 600000; //convert to seconds and add to time remaining + break; + default: + break; + } + /*switch( useitem->itemnum ) + { + case 954: //shop coupon A + { + delete useitem; + thisclient->Shop->ShopType = 1; + } + case 955: //shop coupon B + { + delete useitem; + thisclient->Shop->ShopType = 2; + } + case 956: //shop coupon C + { + delete useitem; + thisclient->Shop->ShopType = 3; + } + } + */ + return NULL; + break; + case 322: // gift boxes and stuff + Log( MSG_WARNING, "Tried to use a gift box"); + break; + default: + Log( MSG_WARNING, "Unknown use item type: %i",type); + return NULL; + break; + } + useitem->itemtype -= 1; + return useitem; +} + +// Look if a skill target is friendly +bool CWorldServer::isSkillTargetFriendly( CSkills* thisskill ) +{ + switch(thisskill->target) + { //Non-Hostile Target + case tYourself://yourself + case tPartyMember://party member + case tClanMember://clan member + case tAlly://ally + case tAllMembers://all members + case tAllCharacters: // All characters + case tFaintedAlly://Fainted + return true; + break; //Hostile Target + case tMonster: //monster + case tHostileCharacter: //hostile character + case tHostileTeamMember: //hostile team member + case tHostileMonster: //hostile monster + return false; + break; + default: + Log(MSG_WARNING,"Unknown skill target %i. Skill id = %i", thisskill->target, thisskill->id); + break; + } + return false; +} + +// calc distance +float CWorldServer::distance( fPoint pos1, fPoint pos2 ) +{ + float dist = 0; + float dx = pos1.x - pos2.x; + float dy = pos1.y - pos2.y; + dist = sqrt( (dx*dx) + (dy*dy) ); + return dist; +} + +// Search client by userid +CPlayer* CWorldServer::GetClientByUserID( UINT userid ) +{ + for(UINT i=0;iplayer; + if(thisclient->Session->userid==userid) + return thisclient; + }; + return NULL; +} + +// Check if a slot is valid (to prevent crash) +bool CWorldServer::CheckInventorySlot( CPlayer* thisclient, int slot ) +{ + if(slot>=MAX_INVENTORY || slot<0) + { + Log(MSG_HACK, "Invalid Inventory slot %i from %s", slot, thisclient->Session->username ); + return false; + } + return true; +} + +// Return a pseudo random number +UINT CWorldServer::RandNumber( UINT init, UINT range, UINT seed ) +{ + if(seed!=0) + srand(seed); + if(range>RAND_MAX) + { + UINT max = (UINT)GServer->round(sqrt((double)range)); + UINT num1 = init+int(max*rand()/(RAND_MAX + 1.0)); + UINT num2 = init+int(max*rand()/(RAND_MAX + 1.0)); + UINT num3 = init+int(max*rand()/(RAND_MAX + 1.0)); + UINT res = (num1 * num2)+num3; + return res>range?range:res; + } + else + { + UINT ranum = init+int(range*rand()/(RAND_MAX + 1.0)); + return ranum; + } + +} + +// Check if is a valid item +bool CWorldServer::IsValidItem(UINT type, UINT id ) +{ + if(type==0 || id==0 ) + return false; + if(type<10) + { + if(id>4999) + return false; + if(EquipList[type].Index[id]->id==0) + return false; + } + else + { + switch(type) + { + case 10: + if(id>1999) + return false; + if(UseList.Index[id]->id==0) + return false; + break; + case 11: + if(id>3999) + return false; + if(JemList.Index[id]->id==0) + return false; + break; + case 12: + if(id>999) + return false; + if(NaturalList.Index[id]->id==00) + return false; + break; + case 14: + if(id>999) + return false; + if(PatList.Index[id]->id==00) + return false; + break; + default: + return false; + } + } + return true; +} + +int CWorldServer::GetIntValue( const char* s , void* var ) +{ + char *tmp = strtok( (char*)var, (const char*)s ); + return atoi(tmp==NULL?0:tmp); +} + +UINT CWorldServer::GetUIntValue( const char* s , void* var ) +{ + char *tmp = strtok( (char*)var, (const char*)s ); + return atoi(tmp==NULL?0:tmp); +} + +char* CWorldServer::GetStrValue( const char* s , void* var ) +{ + char *tmp = strtok( (char*)var, (const char*)s ); + return tmp; +} + +bool CWorldServer::AddParty( CParty* thisparty ) +{ + PartyList.push_back( thisparty ); + return true; +} + +bool CWorldServer::RemoveParty( CParty* thisparty ) +{ + for(UINT i=0;iNaturalList.Data.size();i++) + { + if(GServer->NaturalList.Index[i]->type == matType) + { + ThisCount++; + if(ThisCount == CraftLevel) + { + return GServer->NaturalList.Index[i]->id; + } + } + } + return 0; +} + +// return fairy range for waiting time random +UINT CWorldServer::GetFairyRange( UINT part ) +{ + UINT Range1[] = { 5, 5, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + UINT Range2[] = { 10, 7, 7, 6, 6, 6, 6, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1 }; + if (part == 0) return Range1[GServer->FairyList.size()]; + if (part == 1) return Range2[GServer->FairyList.size()]; +} diff --git a/Src/World Server/gmcmds.cpp b/Src/World Server/gmcmds.cpp new file mode 100644 index 0000000..2a8d15b --- /dev/null +++ b/Src/World Server/gmcmds.cpp @@ -0,0 +1,3877 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + + +// Parse our commands to their appropriate function +bool CWorldServer::pakGMCommand( CPlayer* thisclient, CPacket* P ) +{ + + char* tmp; + char* command = strtok( (char*)&(*P).Buffer[1] , " "); + if (command==NULL) return true; + //non GM functions + if (strcmp(command, "b")==0) + { + if(Config.Command_Broadcast > thisclient->Session->accesslevel) + return true; + time_t seconds; + seconds = time (NULL); + if((thisclient->CharInfo->LastGlobal+Config.Command_GlobalTime) <= seconds || thisclient->Session->accesslevel > 100) + { + thisclient->CharInfo->LastGlobal = time (NULL); + char outputmsg[200]; + sprintf( outputmsg, "%s %s", Config.Command_GlobalPrefix, &P->Buffer[3] ); + Log( MSG_INFO, "%s> %s %s", thisclient->CharInfo->charname, Config.Command_GlobalPrefix, &P->Buffer[3]); + SendGlobalMSG(thisclient, outputmsg); + } + else + { + long int remaining = (Config.Command_GlobalTime-(seconds-thisclient->CharInfo->LastGlobal)); + char buffer2[200]; + sprintf ( buffer2, "Please wait %i seconds before sending another global message.", remaining ); + SendPM(thisclient, buffer2); + } + return true; + } + + // all commands beyond here require IsGM or IsDev to be active in the character record + if (thisclient->CharInfo->IsGM == 0) + { + SendPM(thisclient, "Sorry. you do not have GM priveleges with this character"); + return true; + } + //SendPM(thisclient, "Well it looks like you do have GM or DEV priveleges with this character"); + if (strcmp(command, "here")==0) + { + if(Config.Command_Here > thisclient->Session->accesslevel) + return true; + + return pakGMTele(thisclient, thisclient->Position->Map, thisclient->Position->current.x, thisclient->Position->current.y); + } + else if (strcmp(command, "tele")==0) // **** TELEPORT TO MAX AND X Y POINTS ***** + { + if(Config.Command_Tele > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned map=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; float x=(float)atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; float y=(float)atoi(tmp); + Log( MSG_GMACTION, " %s : /tele %i,%i,%i" , thisclient->CharInfo->charname, map, x, y); + return pakGMTele(thisclient, map, x, y); + } + else if (strcmp(command, "usebuff")==0) // apply a status effect. icon plus visual + { + //SendPM(thisclient, "effect used"); + //if(thisclient->Session->accesslevel < 300) + //return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + unsigned effect = atoi(tmp); + SendPM(thisclient, "Effect called for skill effect %i",effect); + return pakGMEffect(thisclient, effect); + } + else if (strcmp(command, "mute")==0) //==== Mute a player ==== [by Paul_T] + { + //SendPM(thisclient, "trying to use mute"); + if(Config.Command_Mute > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + char* id=tmp; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + int min=atoi(tmp); + Log( MSG_GMACTION, " %s : /mute %s %i" , thisclient->CharInfo->charname, id, min); + return pakGMMute(thisclient, id, min); + } + else if (strcmp(command, "configreset")==0) // *** REALOAD DATA FILES ****** + { + //SendPM(thisclient, "trying to use configreset"); + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + char* action; + if ((tmp = strtok(NULL, " ")) == NULL)return true; action = tmp; + if(strcmp(action, "maxlevel")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.MaxLevel = newval; + const char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET maxlevel=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "xprate")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.EXP_RATE = newval; + const char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET exp_rate=%i WHERE conf='%s'",newval,confname); + } + } + else if(strcmp(action, "cmd")==0) + { + LoadConfigurations( "commands.ini" ); + } + else + { + Log( MSG_INFO, "Unrecognized configreset command by GM %s" , thisclient->CharInfo->charname); + SendPM(thisclient, "/reload %s is not a valid command", tmp); + return true; + } + Log( MSG_GMACTION, " %s : /configreset %s" , thisclient->CharInfo->charname, action); + SendPM(thisclient, "%s data has been reloaded", action); + return true; + } + else if (strcmp(command,"readobjvar")==0) + { + //SendPM(thisclient, "trying to use readobjvar"); + if(thisclient->Session->accesslevel < 300 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + int objVar = atoi(tmp); + if (objVar > 1999)return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + int thisIndex = atoi(tmp); + if (thisIndex > 19)return true; + int thisValue = GServer->ObjVar[objVar][thisIndex]; + SendPM(thisclient, "Global Object Variable [%i][%i] current value is %i" , objVar,thisIndex,thisValue); + } + else if (strcmp(command,"setobjvar")==0) + { + //SendPM(thisclient, "trying to use setobjvar"); + if(thisclient->Session->accesslevel < 300 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + int objVar = atoi(tmp); + if (objVar > 1999)return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + int thisIndex = atoi(tmp); + if (thisIndex > 19)return true; + if ((tmp = strtok(NULL, " ")) == NULL) + return true; + int thisValue = atoi(tmp); + SendPM(thisclient, "Global Object Variable [%i][%i] set to value %i" , objVar,thisIndex,thisValue); + GServer->ObjVar[objVar][thisIndex] = thisValue; + } + /*else if (strcmp(command, "configreset")==0) // *** REALOAD DATA FILES ****** + { + if(thisclient->Session->accesslevel < 900) + return true; + char* action; + char* buffer; + if ((tmp = strtok(NULL, " ")) == NULL)return true; action = tmp; + if(strcmp(action, "maxlevel")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.MaxLevel = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET maxlevel=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "xprate")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.EXP_RATE = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET exp_rate=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "droprate")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.DROP_RATE = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET drop_rate=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "droptype")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.DROP_TYPE = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET drop_type=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "zulyrate")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.ZULY_RATE = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET zuly_rate=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "savetime")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.SAVETIME = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET savetime=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "partygap")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.PARTYGAP = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET partygap=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "maxstat")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.MAXSTAT = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET maxstat=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "playerdmg")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.PLAYERDMG = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET player_damage=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "monsterdmg")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.MONSTERDMG = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET monster_damage=%i WHERE conf='%s'",newval,confname); + } + } + if(strcmp(action, "cfmode")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT newval = atoi(tmp); + { + Config.CFMODE = newval; + char* confname = "default"; + GServer->DB->QExecute("UPDATE list_config SET cfmode=%i WHERE conf='%s'",newval,confname); + } + } + else if(strcmp(action, "cmd")==0) + { + LoadConfigurations( "commands.ini" ); + } + else + { + Log( MSG_INFO, "Unrecognized configreset command by GM %s" , thisclient->CharInfo->charname); + sprintf ( buffer, "/reload %s is not a valid command", tmp ); + SendPM(thisclient, buffer); + return true; + } + Log( MSG_GMACTION, " %s : /configreset %s" , thisclient->CharInfo->charname, action); + sprintf ( buffer, "%s data has been reloaded", action ); + SendPM(thisclient, buffer); + return true; + }*/ + else if (strcmp(command, "save")==0) // *** SAVE USER DATA ***** + { + if(Config.Command_Save > thisclient->Session->accesslevel) + return true; + thisclient->savedata(); + SendPM(thisclient, "Character data saved"); + return true; + } + else if (strcmp(command, "savequest")==0) // *** SAVE USER DATA ***** + { + if(Config.Command_Save > thisclient->Session->accesslevel) + return true; + thisclient->savequests(thisclient); + SendPM(thisclient, "Character Quest data saved"); + return true; + } + else if (strcmp(command, "aiwatch")==0) // set a skill for mobs to use. testing + { + //SendPM(thisclient, "trying to use aiwatch"); + if(thisclient->Session->accesslevel < 300 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + UINT newval = atoi(tmp); + Config.AIWatch = newval; + SendPM(thisclient, "AI script watching set to %i",newval); + } + else if (strcmp(command, "reload")==0) // *** REALOAD DATA FILES ****** + { + //SendPM(thisclient, "trying to use reload"); + if(Config.Command_Reload > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0 ) + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + if(strcmp(tmp, "config")==0) + { + LoadConfigurations( (const char*)filename.c_str() ); + GServer->LoadConfig( ); + } + else if(strcmp(tmp, "mobs")==0) + GServer->LoadNPCData( ); + else if(strcmp(tmp, "drops")==0) + //GServer->LoadPYDropsData( ); + GServer->LoadNewDrops( ); + else if(strcmp(tmp, "cmd")==0) + LoadConfigurations( "commands.ini" ); + else if(strcmp(tmp, "spawns")==0) + { + GServer->LoadMonsterSpawn(); + } + else if(strcmp(tmp, "tdef")==0) + LoadWayPoints( ); + //else if(strcmp(tmp, "equip")==0) + //{ + // GServer->LoadEquip(); + //} + else + { + Log( MSG_INFO, "Unrecognized reload command by GM %s" , thisclient->CharInfo->charname); + SendPM(thisclient, "/reload %s is not a valid command", tmp); + return true; + } + Log( MSG_GMACTION, " %s : /reload %s" , thisclient->CharInfo->charname, tmp); + SendPM(thisclient, "%s data has been reloaded", tmp); + return true; + } + else if(strcmp(command, "setspeedmod")==0) + { + if((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned mSpeedModif = atoi(tmp); + Log( MSG_GMACTION, " mSpeedModif changed to %i by %s" , mSpeedModif, thisclient->CharInfo->charname); + return pakGMChangeMSpeedModif(thisclient, mSpeedModif); + } + else if(strcmp(command, "mystat")==0) + { + //SendPM(thisclient, "trying to use mystat"); + if ((tmp = strtok(NULL, " "))==NULL)return true; + if(strcmp(tmp, "ap")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My Attack Power is %i", thisclient->Stats->Attack_Power ); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "acc")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My Accuracy is %i", thisclient->Stats->Accury ); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "hp")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My current HP is %i", thisclient->Stats->HP ); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "mp")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My current MP is %i", thisclient->Stats->MP ); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "maxhp")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My Maximum HP is %i", thisclient->GetMaxHP()); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "maxmp")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My maximum MP is %i", thisclient->GetMaxMP()); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "dodge")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My dodge is %i", thisclient->Stats->Dodge); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "def")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My defense is %i", thisclient->Stats->Defense); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "mdef")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My Magic defense is %i", thisclient->Stats->Magic_Defense); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "crit")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My critical is %i", thisclient->Stats->Critical); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "mspd")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My move speed is %i", thisclient->Stats->Move_Speed); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "aspd")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My attack speed is %i", thisclient->Stats->Attack_Speed); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "xprate")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My xp rate is %i", thisclient->Stats->xprate); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "stamina")==0) + { + char buffer2[200]; + sprintf ( buffer2, "My Stamina is %i", thisclient->CharInfo->stamina); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "weight")==0) + { + int weight = thisclient->GetCurrentWeight(); + int maxweight = thisclient->GetMaxWeight(); + char buffer2[200]; + sprintf ( buffer2, "My current weight is %i. max weight = %i", weight,maxweight); + SendPM(thisclient, buffer2); + } + else if(strcmp(tmp, "zulydrop")==0) + { + SendPM(thisclient, "My zuly drop rate is %i", thisclient->Stats->zulydroprate); + } + else if(strcmp(tmp, "itemdrop")==0) + { + SendPM(thisclient, "My item drop rate is %i", thisclient->Stats->itemdroprate); + } + else if(strcmp(tmp, "serverdrop")==0) + { + SendPM(thisclient, "Server item drop rate is %i", GServer->Config.DROP_RATE); + } + else if(strcmp(tmp, "clan")==0) + { + SendPM(thisclient, "My clan id is: %i My clan rank is: %i", thisclient->Clan->clanid, thisclient->Clan->clanrank); + } + else if(strcmp(tmp, "map")==0) + { + SendPM(thisclient, "My current is: %i ", thisclient->Position->Map); + } + else if(strcmp(tmp, "job")==0) + { + SendPM(thisclient, "My class id is: %i ", thisclient->CharInfo->Job); + } + } + else if (strcmp(command, "ann")==0) // *** SEND A ANNUNCEMENT *** + { + if(Config.Command_Ann > thisclient->Session->accesslevel) + return true; + Log( MSG_GMACTION, " %s : /ann %s" , thisclient->CharInfo->charname, &P->Buffer[5] ); + return pakGMAnn(thisclient, P); + } + else if(strcmp(command, "bodysize")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT size=atoi(tmp); + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 36); + ADDWORD(pak, size); + ADDWORD(pak, 0); + thisclient->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + thisclient->client->SendPacket(&pak); + SendSysMsg(thisclient, "Body size changed!"); + } + else if(strcmp(command, "headsize")==0) + { + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT size=atoi(tmp); + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 35); + ADDWORD(pak, size); + ADDWORD(pak, 0); + thisclient->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + thisclient->client->SendPacket(&pak); + SendSysMsg(thisclient, "Head size changed!"); + } + else if(strcmp(command, "face") == 0) + { + if(Config.Command_Face > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT face = atoi(tmp); + if (face > 15) return true; + UINT style; + if ((tmp = strtok(NULL, " ")) == NULL) style = 0; else style = atoi(tmp); + if (style > 3) style = 3; + thisclient->CharInfo->Face = ((face*7) + style + 1); + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 8); + ADDWORD(pak, thisclient->CharInfo->Face); + ADDWORD(pak, 0); + thisclient->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + thisclient->client->SendPacket(&pak); + SendPM(thisclient, "Face changed!"); + DB->QExecute("UPDATE characters SET face = %i WHERE id = %i", thisclient->CharInfo->Face, thisclient->CharInfo->charid); + } + else if(strcmp(command, "hair")==0) + { + if(Config.Command_Hair > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; UINT hair = atoi(tmp); + if (hair > 6) return true; + thisclient->CharInfo->Hair = (hair*5); + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 9); + ADDWORD(pak, thisclient->CharInfo->Hair); + ADDWORD(pak, 0); + thisclient->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + thisclient->client->SendPacket(&pak); + DB->QExecute("UPDATE characters SET hairStyle=%i WHERE id=%i", thisclient->CharInfo->Hair, thisclient->CharInfo->charid); + SendPM(thisclient, "Hair changed!"); + } + else if(strcmp(command, "pvp")==0) + { + if(Config.Command_Pvp > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + CMap* map = MapList.Index[thisclient->Position->Map]; + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 34); + if(map->allowpvp!=0) + { + map->allowpvp = 0; + ADDWORD(pak, 2); + SendSysMsg(thisclient, "PVP off"); + } + else + { + map->allowpvp = 1; + ADDWORD(pak, 51); + SendSysMsg(thisclient, "PVP on"); + } + ADDWORD(pak, 0); + SendToMap(&pak, thisclient->Position->Map); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + SendToMap(&pak, thisclient->Position->Map); + } + else if(strcmp(command, "who")==0) + { + if(Config.Command_Who > thisclient->Session->accesslevel) + return true; + int count = 1; + int gmcount = 0; + int playercount = 0; + char line0[200]; + while(count <= (ClientList.size()-1)) + { + CPlayer* whoclient = (CPlayer*)ClientList.at(count)->player; + if(whoclient != NULL) + { + if(whoclient->isInvisibleMode != true) + { + if(whoclient->Session->accesslevel > 100) + { + gmcount++; + } + else + { + playercount++; + } + } + count++; + } + } + sprintf(line0, "There are currently %i players and %i game moderators currently connected.", playercount, gmcount); + Log( MSG_GMACTION, " %s : /who" , thisclient->CharInfo->charname); + SendPM(thisclient, line0 ); + return true; + } + else if (strcmp(command, "go")==0) // AtCommandGo + { + SendPM(thisclient,"Got to the go command"); + if(Config.Command_Go > thisclient->Session->accesslevel) + return true; + SendPM(thisclient,"Got past the accesslevel check"); + if ((tmp = strtok(NULL, " ")) == NULL) tmp = 0; int loc=atoi(tmp); + int x = 0; + int y = 0; + int map = 0; + if(loc == 1) + { + map = 22; + x = 5749; + y = 5101; + } + else if(loc == 2) + { + map = 1; + x = 5240; + y = 5192; + } + else if (loc == 3) + { + map = 2; + x = 5516; + y = 5236; + } + else if (loc == 4) + { + map = 51; + x = 5357; + y = 5013; + } + else if (loc == 5) + { + map = 6; + x = 5243; + y = 5240; + } + else if (loc == 6) + { + map = 24; + x = 5525; + y = 5376; + } + else if (loc == 7) + { + map = 31; + x = 5516; + y = 5437; + } + else if (loc == 8) + { + map = 28; + x = 5639; + y = 4761; + } + else + { + SendPM(thisclient, "Please input a number after the go command, below is a list of places and their appropriate number"); + SendPM(thisclient, "1 = Adventurers plains"); + SendPM(thisclient, "2 = The city of Zant"); + SendPM(thisclient, "3 = Junon Polis"); + SendPM(thisclient, "4 = The city of Eucar"); + SendPM(thisclient, "5 = Training grounds"); + SendPM(thisclient, "6 = El Verloon Desert"); + SendPM(thisclient, "7 = Goblin Cave (B1)"); + SendPM(thisclient, "8 = Gorge of Silence"); + SendPM(thisclient, "Example; /go 3"); + } + + if( (x != 0) && (y != 0) && (map != 0) ) + { + fPoint coord; + coord.x = x; + coord.y = y; + SendPM(thisclient, "teleport to map: %i",map); + MapList.Index[map]->TeleportPlayer( thisclient, coord, false ); + Log( MSG_GMACTION, " %s : /go %i" , thisclient->CharInfo->charname, loc); + } + return true; + } + else if(strcmp(command, "p")==0) //*** READ THE PACKET.TXT AND SEND IT + { + if(Config.Command_Pak > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + + char buffer; + std::string fname = "packet/packet"; + fname += tmp; + fname += ".txt"; + FILE *packet1 = fopen(fname.c_str(),"r"); + if(packet1==NULL) + { + printf("Error opening packet.txt!\n"); + return true; + } + unsigned int comm = 0; + fread( &comm, 1, 2, packet1 ); + BEGINPACKET(pak,comm); + while((fscanf(packet1,"%c",&buffer))!=EOF) + ADDBYTE(pak,buffer); + thisclient->client->SendPacket( &pak ); + fclose(packet1); + } + else if (strcmp(command, "mybuffs")==0) + { + if(thisclient->Session->accesslevel < 300) + return true; + for(int i=0;i<15;i++) + { + if(thisclient->MagicStatus[i].Buff != 0) + { + // don't do anything for now. Still working on this concept. + Log(MSG_GMACTION, "MyBuffs command called. Not yet functional"); + } + } + } + else if (strcmp(command, "level")==0) + { + if(Config.Command_Level > thisclient->Session->accesslevel) + return true; + char* name; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned level=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) name = thisclient->CharInfo->charname; else name = tmp; + Log( MSG_GMACTION, " %s : /level %i %s" , thisclient->CharInfo->charname, level, name); + return pakGMLevel( thisclient, level, name ); + } + else if (strcmp(command, "info")==0) + { + if(Config.Command_Info > thisclient->Session->accesslevel) + return true; + Log( MSG_GMACTION, " %s : /info" , thisclient->CharInfo->charname); + thisclient->GetPlayerInfo( ); + return true; + } + else if (strcmp(command, "exp")==0) + { + if(Config.Command_Exp > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned exp=atoi(tmp); + char* name; + if ((tmp = strtok(NULL, " "))==NULL) + name = thisclient->CharInfo->charname; + else + name = tmp; + CPlayer* otherclient = GetClientByCharName( name ); + if (otherclient == NULL) + return true; + otherclient->CharInfo->Exp += exp; + BEGINPACKET( pak, 0x79b ); + ADDDWORD ( pak, otherclient->CharInfo->Exp ); + ADDWORD ( pak, otherclient->CharInfo->stamina ); + ADDWORD ( pak, 0 ); + otherclient->client->SendPacket( &pak ); + Log( MSG_GMACTION, " %s : /exp %i %s" , thisclient->CharInfo->charname, exp, name); + } + else if (strcmp(command, "tdmon") == 0) + { + if(Config.Command_Mon > thisclient->Session->accesslevel) + return true; + unsigned montype = 0; + unsigned moncount = 1; + unsigned monAI = 331; //TD Mob AI + if ((tmp = strtok(NULL, " "))==NULL) return true; montype = atoi(tmp); + if ((tmp = strtok(NULL, " "))!=NULL)moncount = atoi(tmp); + Log( MSG_GMACTION, " %s : spawned %i TD Mobs of type %i" , thisclient->CharInfo->charname, moncount, montype); + return pakGMMon( thisclient, montype, moncount, monAI ); + } + else if (strcmp(command, "mon") == 0) + { + if(Config.Command_Mon > thisclient->Session->accesslevel) + return true; + unsigned montype = 0; + unsigned moncount = 1; + unsigned monAI = 0; //default AI. Monster gets whatever AI is normal for its type + if ((tmp = strtok(NULL, " "))==NULL) return true; montype = atoi(tmp); + if ((tmp = strtok(NULL, " "))!=NULL)moncount = atoi(tmp); + if ((tmp = strtok(NULL, " "))!=NULL)monAI = atoi(tmp); + Log( MSG_GMACTION, " %s : spawned %i monsters of type %i with AI %i" , thisclient->CharInfo->charname, moncount, montype, monAI); + return pakGMMon( thisclient, montype, moncount, monAI ); + } + else if (strcmp(command, "kick")==0) + { + if(Config.Command_Kick > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + CPlayer* otherclient = GServer->GetClientByCharName(name); + if (otherclient == NULL) + return true; + if(thisclient->Session->accesslevel <= otherclient->Session->accesslevel) + { + SendPM(thisclient,"You cannot kick that player. His access level is as high as yours"); + SendPM(otherclient,"LOL %s just tried to kick you but his access level isn't higher than yours"); + Log( MSG_GMACTION, " %s : /kick %s" , thisclient->CharInfo->charname, name); + return true; + } + else + { + Log( MSG_GMACTION, " %s : /kick %s" , thisclient->CharInfo->charname, name); + return pakGMKick( thisclient, name ); + } + } + else if (strcmp(command, "job")==0) // *** Change Job ***** + { + if(Config.Command_Job > thisclient->Session->accesslevel) + return true; + //char* tmp; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* job=tmp; + Log( MSG_GMACTION, " %s : /job %s" , thisclient->CharInfo->charname, job); + if(thisclient->CharInfo->Job != 0 || thisclient->Stats->Level<10) + return true; + if(strcmp(job,"soldier")==0) + { + BEGINPACKET(pak, 0x721); + ADDWORD (pak, 0x0004); + ADDWORD (pak, 0x006f); + ADDWORD (pak, 0x0000); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x730); + ADDBYTE(pak, 0x05); + ADDBYTE(pak, 0x00); + ADDWORD(pak, 0xf52f); + ADDWORD(pak, 0x2964); + thisclient->client->SendPacket( &pak ); + thisclient->CharInfo->Job=111; + } + else + if(strcmp(job,"muse")==0) + { + BEGINPACKET(pak, 0x721); + ADDWORD (pak, 0x0004); + ADDWORD (pak, 0x00D3); + ADDWORD (pak, 0x0000); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x730); + ADDBYTE(pak, 0x05); + ADDBYTE(pak, 0x00); + ADDWORD(pak, 0xdc3b); + ADDWORD(pak, 0x20cd); + thisclient->client->SendPacket( &pak ); + thisclient->CharInfo->Job=211; + } + else + if(strcmp(job,"hawker")==0) + { + BEGINPACKET(pak, 0x721); + ADDWORD (pak, 0x0004); + ADDWORD (pak, 0x0137); + ADDWORD (pak, 0x0000); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x730); + ADDBYTE(pak, 0x05); + ADDBYTE(pak, 0x00); + ADDWORD(pak, 0x1104); + ADDWORD(pak, 0x5150); + thisclient->client->SendPacket( &pak ); + thisclient->CharInfo->Job=311; + } + else + if(strcmp(job,"dealer")==0) + { + BEGINPACKET(pak, 0x721); + ADDWORD (pak, 0x0004); + ADDWORD (pak, 0x019b); + ADDWORD (pak, 0x0000); + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x730); + ADDBYTE(pak, 0x05); + ADDBYTE(pak, 0x00); + ADDWORD(pak, 0x4c1c); + ADDWORD(pak, 0xef69); + thisclient->client->SendPacket( &pak ); + thisclient->CharInfo->Job=411; + } + } + else if (strcmp(command, "set")==0) + { + if(Config.Command_Set > thisclient->Session->accesslevel) + return true; + int refine; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + int id=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + refine = 0; + else + refine=atoi(tmp); + refine *= 16; + BEGINPACKET( pak, 0); + if(EquipList[2].Index[id+100]!=NULL) + { + if(id > 200) + { + thisclient->items[2].itemnum = id+100; //hats are always 100 positions higher so this should make set line up + } + else + { + thisclient->items[2].itemnum = id; + } + thisclient->items[2].itemtype = 2; + thisclient->items[2].refine =refine; + thisclient->items[2].durability = 50; + thisclient->items[2].lifespan = 100; + thisclient->items[2].count = 1; + thisclient->items[2].stats = 0; + thisclient->items[2].socketed = false; + thisclient->items[2].appraised = true; + thisclient->items[2].gem = 0; + thisclient->UpdateInventory( 2 ); + + RESETPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, 0x0002); + ADDWORD( pak, id+100); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[2] ) ); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE + SendToVisible( &pak,thisclient ); + } + if(EquipList[3].Index[id]!=NULL) + { + thisclient->items[3].itemnum = id; + thisclient->items[3].itemtype = 3; + thisclient->items[3].refine =refine; + thisclient->items[3].durability = 50; + thisclient->items[3].lifespan = 100; + thisclient->items[3].count = 1; + thisclient->items[3].stats = 0; + thisclient->items[3].socketed = false; + thisclient->items[3].appraised = true; + thisclient->items[3].gem = 0; + thisclient->UpdateInventory( 3 ); + + RESETPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, 0x0003); + ADDWORD( pak, id); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[3] )); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE + SendToVisible( &pak,thisclient ); + } + if(EquipList[4].Index[id]!=NULL) + { + thisclient->items[5].itemnum = id; + thisclient->items[5].itemtype = 4; + thisclient->items[5].refine =refine; + thisclient->items[5].durability = 50; + thisclient->items[5].lifespan = 100; + thisclient->items[5].count = 1; + thisclient->items[5].stats = 0; + thisclient->items[5].socketed = false; + thisclient->items[5].appraised = true; + thisclient->items[5].gem = 0; + + thisclient->UpdateInventory( 4 ); + + RESETPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, 5); + ADDWORD( pak, id); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[5] )); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE 2602 + SendToVisible( &pak,thisclient ); + } + if(EquipList[5].Index[id]!=NULL) + { + thisclient->items[6].itemnum = id; + thisclient->items[6].itemtype = 5; + thisclient->items[6].refine =refine; + thisclient->items[6].durability = 50; + thisclient->items[6].lifespan = 100; + thisclient->items[6].count = 1; + thisclient->items[6].stats = 0; + thisclient->items[6].socketed = false; + thisclient->items[6].appraised = true; + thisclient->items[6].gem = 0; + + thisclient->UpdateInventory( 6 ); + + RESETPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, 6); + ADDWORD( pak, id); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[6] )); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed); // REFINE + SendToVisible( &pak,thisclient ); + } + thisclient->SetStats( ); + } + else if (strcmp(command, "cha")==0) + { + if(Config.Command_Cha > thisclient->Session->accesslevel) + return true; + int slot; + int tipo; + int id; + int stats; + int socket; + int refine; + if ((tmp = strtok(NULL, " "))==NULL)return true; + if(strcmp(tmp, "mask")==0) + { + slot=1; + tipo=1; + } + else + if(strcmp(tmp, "cap")==0) + { + slot=2; + tipo=2; + } + else + if(strcmp(tmp, "suit")==0) + { + slot=3; + tipo=3; + } + else + if(strcmp(tmp, "back")==0) + { + slot=4; + tipo=6; + } + else + if(strcmp(tmp, "glov")==0) + { + slot=5; + tipo=4; + } + else + if(strcmp(tmp, "shoe")==0) + { + slot=6; + tipo=5; + } + else + if(strcmp(tmp, "weap")==0) + { + slot=7; + tipo=8; + } + else + if(strcmp(tmp, "shield")==0) + { + slot=8; + tipo=9; + } + else + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + id=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + stats=0; + else + stats=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + socket=0; + else + socket=1; + if ((tmp = strtok(NULL, " "))==NULL) + refine=0; + else + refine=atoi(tmp); + thisclient->items[slot].itemnum = id; + thisclient->items[slot].itemtype = tipo; + thisclient->items[slot].refine = refine; + thisclient->items[slot].durability = 50; + thisclient->items[slot].lifespan = 100; + thisclient->items[slot].count = 1; + thisclient->items[slot].stats = stats; + thisclient->items[slot].socketed = socket; + thisclient->items[slot].appraised = true; + if(stats > 300) + { + thisclient->items[slot].gem = stats; + thisclient->items[slot].socketed = 1; + } + else + { + thisclient->items[slot].gem = 0; + } + + BEGINPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, slot); + ADDWORD( pak, id); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[slot] )); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE 2602 + SendToVisible( &pak,thisclient ); + + thisclient->UpdateInventory( slot ); + thisclient->SetStats( ); + return true; + } + else if (strcmp(command, "learnskill")==0) + { + if (thisclient->Session->accesslevel < 300) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT skillId = atoi(tmp); + Log( MSG_GMACTION, " %s : /learnskill %i", thisclient->CharInfo->charname, skillId); + if (skillId < 0) return false; + if (skillId > SkillList.size()) return false; + LearnSkill(thisclient, skillId); + return true; + } + else if (strcmp(command, "spawn")==0) + { + if(Config.Command_ESpawn > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + int mobid; + int max; + int radius; + int stime; + int spawntype; + int triggermontype; + int triggeramount; + int lim; + int spawnk; + int aggro; + int refvar; //Objvar NPC reference + int refval; //Objvar array reference + + if ((tmp = strtok(NULL, " "))==NULL) + return true; + else + mobid = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + return true; + else + max = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + return true; + else + radius = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + return true; + else + stime = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + return true; + else + spawntype = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + triggermontype = 0; + else + triggermontype = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + triggeramount = 0; + else + triggeramount = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + lim = 0; + else + lim = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + spawnk = 1; + else + spawnk = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + aggro = 1; + else + aggro = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + refvar = 0; + else + refvar = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + refval = 0; + else + refval = atoi(tmp); + + GServer->DB->QExecute("INSERT INTO list_special_spawns (map,montype,min,max,respawntime,x,y,radius,agressive,triggerammount,triggermontype,spawntype,areatrigger,lim,spawnk,refvar,refval) VALUES(%i,%i,%i,%i,%i,%f,%f,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i)", + thisclient->Position->Map,mobid,0,max,stime,thisclient->Position->current.x,thisclient->Position->current.y,radius,aggro,triggeramount,triggermontype,spawntype,1,lim,spawnk,refvar,refval ); + CSpawnArea* thisspawn = new (nothrow) CSpawnArea; + if(thisspawn == NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + DB->QFree( ); + return false; + } + GServer->maxSpawnId++; + thisspawn->id = GServer->maxSpawnId; + thisspawn->map = thisclient->Position->Map; + thisspawn->montype = mobid; + thisspawn->min = 0;//atoi(row[3]); + thisspawn->max = max; + thisspawn->respawntime = stime; + thisspawn->point.x = thisclient->Position->current.x; + thisspawn->point.y = thisclient->Position->current.y; + thisspawn->radius = radius; + thisspawn->amon = 0; + thisspawn->lastRespawnTime = 0; + thisspawn->type = spawntype; + thisspawn->triggertype = triggermontype; + thisspawn->triggeramount = triggeramount; + thisspawn->aggressive = aggro; + thisspawn->typeoftrigger = 1; + thisspawn->limit = lim; + thisspawn->spawnkey = spawnk; + thisspawn->triggercount = 0; + thisspawn->thisnpc = GetNPCDataByID( thisspawn->montype ); + thisspawn->RefVar = refvar; + thisspawn->RefVal = refval; + if(thisspawn->thisnpc == NULL) + { + SendPM(thisclient, "Invalid montype - Spawn %i will not be added", thisspawn->id ); + delete thisspawn; + return true; + } + MapList.Index[thisspawn->map]->MonsterSpawnList.push_back( thisspawn ); + SendPM(thisclient,"Spawn successfully saved with spawn id %i.",thisspawn->id); + return true; + } + else if (strcmp(command, "refine")==0) + { + if(thisclient->Session->accesslevel < 300) + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + int slot = 7; //defaults to weapon + int tipo; + int itemrefine; + if(strcmp(tmp, "mask")==0) + { + slot=1; + } + else + if(strcmp(tmp, "cap")==0) + { + slot=2; + } + else + if(strcmp(tmp, "suit")==0) + { + slot=3; + } + else + if(strcmp(tmp, "back")==0) + { + slot=4; + } + else + if(strcmp(tmp, "glov")==0) + { + slot=5; + } + else + if(strcmp(tmp, "shoe")==0) + { + slot=6; + } + else + if(strcmp(tmp, "weap")==0) + { + slot=7; + } + else + if(strcmp(tmp, "shield")==0) + { + slot=8; + } + if ((tmp = strtok(NULL, " "))==NULL) + itemrefine =0; + else + itemrefine = atoi(tmp)<10?atoi(tmp)*16:9*16; + thisclient->items[slot].refine = itemrefine; + + BEGINPACKET( pak, 0x7a5); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, slot); + ADDWORD( pak, thisclient->items[slot].itemnum); // ITEM NUM + ADDWORD( pak, BuildItemRefine( thisclient->items[slot] )); // REFINE + ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE 2602 + SendToVisible( &pak,thisclient ); + + thisclient->UpdateInventory( slot ); + thisclient->SetStats( ); + return true; + } + else if (strcmp(command, "itemstat")==0) + { + if(thisclient->Session->accesslevel < 300) + return true; + if ((tmp = strtok(NULL, " "))==NULL)return true; + int slot = 7; //defaults to weapon + int tipo; + int itemstat; + if(strcmp(tmp, "mask")==0) + { + slot=1; + } + else + if(strcmp(tmp, "cap")==0) + { + slot=2; + } + else + if(strcmp(tmp, "suit")==0) + { + slot=3; + } + else + if(strcmp(tmp, "back")==0) + { + slot=4; + } + else + if(strcmp(tmp, "glov")==0) + { + slot=5; + } + else + if(strcmp(tmp, "shoe")==0) + { + slot=6; + } + else + if(strcmp(tmp, "weap")==0) + { + slot=7; + } + else + if(strcmp(tmp, "shield")==0) + { + slot=8; + } + if ((tmp = strtok(NULL, " "))==NULL) + itemstat = 0; + else + itemstat = atoi(tmp); + thisclient->items[slot].stats= itemstat; + if(itemstat > 300) + { + thisclient->items[slot].socketed = 1; + thisclient->items[slot].gem = itemstat; + } + else + thisclient->items[slot].gem = 0; + thisclient->items[slot].appraised = true; + + //BEGINPACKET( pak, 0x7a5); + //ADDWORD( pak, thisclient->clientid ); + //ADDWORD( pak, slot); + //ADDWORD( pak, thisclient->items[slot].itemnum); // ITEM NUM + //ADDWORD( pak, BuildItemRefine( thisclient->items[slot] )); // REFINE + //ADDWORD( pak, thisclient->Stats->Move_Speed ); // REFINE 2602 + //SendToVisible( &pak,thisclient ); + + BEGINPACKET( pak,0x7a5 ); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, slot ); + ADDDWORD( pak, BuildItemShow(thisclient->items[slot])); // ITEM + ADDWORD ( pak, thisclient->Stats->Move_Speed); + SendToVisible( &pak, thisclient ); + + thisclient->UpdateInventory( slot ); + thisclient->SetStats( ); + return true; + } + else if (strcmp(command, "item")==0)//Modified by Hiei (added refine/socket/stats) + { + if(Config.Command_Item > thisclient->Session->accesslevel) + return true; + UINT itemrefine, itemstats, itemls, itemsocket; + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemid =atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemtype =atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemamount =atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + itemrefine =0; + else + itemrefine = atoi(tmp) < 10?atoi(tmp)*16:9*16; + if ((tmp = strtok(NULL, " ")) == NULL) + itemls = 100; + else + itemls = atoi(tmp); + if ((tmp = strtok(NULL, " ")) == NULL) + itemsocket = 0; + else + itemsocket = atoi(tmp)==0?false:true; + if ((tmp = strtok(NULL, " "))==NULL) + itemstats =0; + else + itemstats =atoi(tmp); + Log( MSG_GMACTION, " %s : /item %i,%i,%i,%i,%i,%i" , thisclient->CharInfo->charname, itemid, itemtype, itemamount , itemrefine , itemsocket ,itemstats); + return pakGMItem( thisclient , itemid , itemtype , itemamount , itemrefine , itemls, itemstats , itemsocket ); + } + else if (strcmp(command, "levelup")==0 || strcmp(command,"l")==0) + { + if(Config.Command_LevelUp > thisclient->Session->accesslevel) + return true; + Log( MSG_GMACTION, " %s : /levelup" , thisclient->CharInfo->charname); + thisclient->CharInfo->Exp += thisclient->GetLevelEXP(); + } + else if (strcmp(command, "drop")==0) + { + if(Config.Command_Drop > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned itemtype=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned itemid=atoi(tmp); + unsigned int itemcount = 0; + if ((tmp = strtok(NULL, " "))==NULL) + itemcount = 1; + else + itemcount = atoi(tmp); + if(itemtype < 10 || itemtype > 12 ) + itemcount = 1; + Log( MSG_GMACTION, " %s : /drop %i,%i" , thisclient->CharInfo->charname, itemtype, itemid); + + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GetNewClientID(); + thisdrop->type = 2; + thisdrop->pos.x = thisclient->Position->current.x; + thisdrop->pos.y = thisclient->Position->current.y; + thisdrop->posMap = thisclient->Position->Map; + thisdrop->droptime = time(NULL); + + ClearItem(thisdrop->item); + thisdrop->item.itemnum = itemid; + thisdrop->item.itemtype = itemtype; + thisdrop->item.count = itemcount; + thisdrop->item.refine = 0; + thisdrop->item.durability = 35; + thisdrop->item.lifespan = 100; + thisdrop->item.appraised = true; + thisdrop->item.socketed = false; + thisdrop->item.stats = 0; + thisdrop->item.gem = 0; + + thisdrop->amount = 1; + thisdrop->owner = 0; + CMap* map = MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + else if(strcmp(command, "givezuly")==0) + { + if(Config.Command_GiveZuly > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; char* name = tmp; + if((tmp = strtok(NULL, " ")) == NULL) return true; int zuly = atoi(tmp); + Log( MSG_GMACTION, " %s : /givezuly %s, %i" , thisclient->CharInfo->charname, name, zuly); + return pakGMZulygive(thisclient, name, zuly); + } + else if(strcmp(command, "unionpoints") == 0) + { + if(Config.Command_GiveZuly > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " ")) == NULL) return true; char* name = tmp; + if((tmp = strtok(NULL, " ")) == NULL) return true; int Upoints = atoi(tmp); + Log( MSG_GMACTION, " %s : /unionpoints %s, %i" , thisclient->CharInfo->charname, name, Upoints); + + return pakGMUnionPoints(thisclient, name, Upoints); + } + else if(strcmp(command, "npc")==0) + { + if(Config.Command_Npc > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned npcid=atoi(tmp); + unsigned npcdialog = 0; + if((tmp = strtok(NULL, " "))!=NULL) + npcdialog=atoi(tmp); + Log( MSG_GMACTION, " %s : /npc %i, %i" , thisclient->CharInfo->charname, npcid, npcdialog); + return pakGMNpc(thisclient, npcid, npcdialog); + } + else if(strcmp(command, "givefairy") == 0) + { + if(Config.Command_GiveFairy > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; char* name = tmp; + if((tmp = strtok(NULL, " ")) == NULL) return true; unsigned mode = atoi(tmp); + Log( MSG_GMACTION, " %s : /givefairy %s, %i" , thisclient->CharInfo->charname, name, mode); + return pakGMFairyto(thisclient, name, mode); + } + else if(strcmp(command, "fairymode") == 0) + { + if(Config.Command_ManageFairy > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned mode=atoi(tmp); + Log( MSG_GMACTION, " %s : /fairymode mode: %i" , thisclient->CharInfo->charname, mode); + return pakGMManageFairy(thisclient, mode); + } + else if(strcmp(command, "fairywait")==0) + { + if(Config.Command_ChangeFairyWait > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned value=atoi(tmp); + Log( MSG_GMACTION, " %s : /fairywait value: %i" , thisclient->CharInfo->charname, value); + return pakGMChangeFairyWait(thisclient, value); + } + else if(strcmp(command, "fairystay")==0) + { + if(Config.Command_ChangeFairyStay > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned value=atoi(tmp); + Log( MSG_GMACTION, " %s : /fairystay value: %i" , thisclient->CharInfo->charname, value); + return pakGMChangeFairyStay(thisclient, value); + } + else if(strcmp(command, "fairytestmode")==0) + { + if(Config.Command_ChangeFairyTestMode > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned mode=atoi(tmp); + Log( MSG_GMACTION, " %s : /fairytestmode mode: %i" , thisclient->CharInfo->charname, mode); + return pakGMChangeFairyTestMode(thisclient, mode); + } + else if (strcmp(command, "move")==0) + { + if(Config.Command_Move > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned map=atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; float x=(float)atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; float y=(float)atoi(tmp); + Log( MSG_GMACTION, " %s : /move %s,%i,%i,%i" , thisclient->CharInfo->charname, name, map, x, y); + return pakGMTeleOtherPlayer(thisclient, name, map, x, y); + } + else if (strcmp(command, "goto")==0) + { + if(Config.Command_Goto > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + Log( MSG_GMACTION, " %s : /goto %s" , thisclient->CharInfo->charname, name); + return pakGMTeleToPlayer(thisclient, name); + } + else if (strcmp(command, "teletome")==0) + { + if(Config.Command_TeleToMe > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + Log( MSG_GMACTION, " %s : /teletome %s" , thisclient->CharInfo->charname, name); + if(strcmp(name,"all")==0) + { + return pakGMTeleAllHere(thisclient); + } + return pakGMTelePlayerHere(thisclient, name); + } + else if(strcmp(command, "CharInfo")==0) + { + if(Config.Command_PlayerInfo > thisclient->Session->accesslevel) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + Log( MSG_GMACTION, " %s : /CharInfo %s" , thisclient->CharInfo->charname, name); + return pakGMInfo(thisclient, name); + } + /*else if (strcmp(command, "give2")==0) + { + if(Config.Command_Give2 > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned itemid = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned itemtype = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned itemamount = atoi(tmp); + Log( MSG_GMACTION, " %s : /give2 %s,%i,%i,%i" , thisclient->CharInfo->charname, name, itemid, itemtype, itemamount); + return pakGMItemtoplayer( thisclient , name , itemid , itemtype , itemamount ); + + }*/ + else if (strcmp(command, "give2")==0) + { + if(Config.Command_Item > thisclient->Session->accesslevel) + return true; + UINT itemrefine, itemstats, itemls, itemsocket; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name = tmp; + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemid = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemtype = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; UINT itemamount = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + itemrefine =0; + else + itemrefine = atoi(tmp)<10?atoi(tmp)*16:9*16; + if ((tmp = strtok(NULL, " "))==NULL) + itemls = 100; + else + itemls = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL) + itemsocket = 0; + else + itemsocket =atoi(tmp)==0?false:true; + if ((tmp = strtok(NULL, " "))==NULL) + itemstats = 0; + else + itemstats =atoi(tmp); + Log( MSG_GMACTION, " %s : /give2 %s,%i,%i,%i,%i,%i,%i,%i" , thisclient->CharInfo->charname, name, itemid , itemtype , itemamount , itemrefine , itemls, itemstats , itemsocket); + return pakGMItemtoplayer( thisclient , name , itemid , itemtype , itemamount , itemrefine , itemls, itemstats , itemsocket ); + } + else if (strcmp(command, "ban")==0) + { + if(Config.Command_Ban > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* name=tmp; + Log( MSG_GMACTION, " %s : /ban %s" , thisclient->CharInfo->charname, name); + return pakGMBan( thisclient, name ); + } + else if (strcmp(command, "ani")==0) + { + if(Config.Command_Ani > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned anid =atoi(tmp); + Log( MSG_GMACTION, " %s : /ani %i" , thisclient->CharInfo->charname, anid); + pakGMDoEmote( thisclient, anid ); + } + else if(strcmp(command,"summon")==0) + { + if(Config.Command_Summon > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned int summon = atoi(tmp); + Log( MSG_GMACTION, " %s : /summon %i" , thisclient->CharInfo->charname, summon); + fPoint position = RandInCircle( thisclient->Position->current, 5 ); + CMap* map = MapList.Index[thisclient->Position->Map]; + map->AddMonster( summon, position, thisclient->clientid, 0,0,168 ); + //Start Animation + BEGINPACKET( pak, 0x7b2 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, 2802 ); + SendToVisible( &pak, thisclient ); + //Finish Animation + RESETPACKET( pak, 0x7bb ); + ADDWORD ( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient ); + //???? + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, thisclient->clientid); + ADDWORD ( pak, 1286 ); + SendToVisible( &pak, thisclient ); + // Add our Mob to the mobs list + } + else if (strcmp(command,"setxprate")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + unsigned int newxprate = atoi(tmp); + GServer->Config.EXP_RATE = newxprate; + Log( MSG_GMACTION, "EXP Rate set to %i" , newxprate); + } + else if (strcmp(command,"setdroprate")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + unsigned int newdroprate = atoi(tmp); + GServer->Config.DROP_RATE= newdroprate; + Log( MSG_GMACTION, "Drop Rate set to %i" , newdroprate); + } + else if (strcmp(command,"setzulyrate")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + unsigned int newzulyrate = atoi(tmp); + GServer->Config.ZULY_RATE = newzulyrate; + Log( MSG_GMACTION, "Zuly Rate set to %i" , newzulyrate); + } + else if (strcmp(command,"delwp")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + int counter = 0; + for(int i=0;i<100;i++) + { + + if(GServer->WPList[thisclient->Position->Map][i].WPType != 0) + { + GServer->WPList[thisclient->Position->Map][i].WPType = 0; + GServer->WPList[thisclient->Position->Map][i].X = 0; + GServer->WPList[thisclient->Position->Map][i].Y = 0; + counter++; + } + } + if(counter > 0) + { + SendPM(thisclient, "All WayPoints in this map have been deleted. Use /savewp to make the change in the DB too"); + return true; + } + SendPM(thisclient, "No Waypoints were found for this map"); + return true; + } + else if (strcmp(command,"setwp")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + { + SendPM(thisclient, "Sorry! You do not have access to this command"); + return true; + } + if ((tmp = strtok(NULL, " "))==NULL) + return true; + unsigned int wptype = atoi(tmp); + if(wptype > 3 ) + { + SendPM(thisclient, "Data not saved. WP Type cannot be greater than 3"); + return true; + } + if(wptype == 0) + { + SendPM(thisclient, "Data not saved. WP Type cannot be zero"); + return true; + } + SendPM(thisclient, "REMINDER. Changes made here are not permanent. please use /savewp to make the change in the DB too"); + UINT nextentry = 1; + bool firstentry = false; + bool finalentry = false; + UINT mapid = thisclient->Position->Map; + for(int i=1;i<100;i++) //let's see what is already set for this map + { + int j = GServer->WPList[mapid][i].WPType; + if(j > 0) + { + nextentry++; + if(j == 1) + firstentry = true; + if(j == 3) + finalentry = true; + } + } + if(wptype == 1) //first Way Point + { + if(firstentry == true) + { + SendPM(thisclient, "The path for this map already contains a start point. You will need to delete the list before adding a new one"); + return true; + } + GServer->WPList[mapid][nextentry].WPType = 1; + GServer->WPList[mapid][nextentry].X = thisclient->Position->current.x; + GServer->WPList[mapid][nextentry].Y = thisclient->Position->current.y; + SendPM(thisclient, "A new TDef Start point has been added for this map"); + return true; + } + if(wptype == 2) //regular Way Point + { + if(firstentry == false) + { + SendPM(thisclient, "The path for this map does not contain a start point. You will need to add one before you can add regular waypoints"); + return true; + } + if(finalentry == true) + { + SendPM(thisclient, "The path for this map already contains a Final destination point. You cannot add more regular way points until you delete it"); + return true; + } + GServer->WPList[mapid][nextentry].WPType = 2; + GServer->WPList[mapid][nextentry].X = thisclient->Position->current.x; + GServer->WPList[mapid][nextentry].Y = thisclient->Position->current.y; + SendPM(thisclient, "A new TDef Way Point has been added for this map"); + return true; + } + if(wptype == 3) //Final Way Point + { + if(finalentry == true) + { + SendPM(thisclient, "The path for this map already contains a Final destination point. You will need to delete the list before adding a new one"); + return true; + } + GServer->WPList[mapid][nextentry].WPType = 3; + GServer->WPList[mapid][nextentry].X = thisclient->Position->current.x; + GServer->WPList[mapid][nextentry].Y = thisclient->Position->current.y; + SendPM(thisclient, "A new TDef Final Point has been added for this map"); + return true; + } + SendPM(thisclient, "Unrecognised SetWP command"); + return true; //just to catch any non handled stuff + } + else if (strcmp(command,"savewp")==0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + bool firstentry = false; + bool finalentry = false; + UINT mapid = thisclient->Position->Map; + for(int i=1;i<100;i++) //let's see what is already set for this map + { + int j = GServer->WPList[mapid][i].WPType; + if(j > 0) + { + if(j == 1) + firstentry = true; + if(j == 3) + finalentry = true; + } + } + if(firstentry == false) + { + SendPM(thisclient, "Cannot save to the database. The WayPoint list for this map contains no start point."); + return true; + } + if(finalentry == false) + { + SendPM(thisclient, "Cannot save to the database. The WayPoint list for this map contains no final destination point."); + return true; + } + //save the data + GServer->DB->QExecute("DELETE FROM list_waypoints WHERE mapid=%i", mapid); + for(int j=1;j<100;j++) + { + if(GServer->WPList[mapid][j].WPType != 0) + GServer->DB->QExecute("INSERT INTO list_waypoints (mapid,wpnum,mapx,mapy,wptype) VALUES(%i,%i,%f,%f,%i)", + mapid, j, GServer->WPList[mapid][j].X, GServer->WPList[mapid][j].Y,GServer->WPList[mapid][j].WPType); + } + SendPM(thisclient, "The Waypoint list for this map has been saved to the database."); + return true; + } + else if (strcmp(command,"checkwp") == 0) + { + if(thisclient->Session->accesslevel < 900 || thisclient->CharInfo->IsDev == 0) + return true; + UINT mapid = thisclient->Position->Map; + for(unsigned int i=0;i<20;i++) + { + SendPM(thisclient, "Waypoint %i type is %i",i,GServer->WPList[mapid][i].WPType); + } + } + else if (strcmp(command, "shutdown")==0) + { + if(Config.Command_Shutdown > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned int minutes =atoi(tmp); + char text[200]; + sprintf( text, "Server will shutdown in %i minutes, Please logout NOW to be sure your information is saved correctly.", + minutes ); + BEGINPACKET( pak, 0x702 ); + ADDSTRING ( pak, thisclient->CharInfo->charname ); + ADDSTRING ( pak, "> " ); + ADDSTRING ( pak, text ); + ADDBYTE ( pak, 0x00 ); + SendToAll ( &pak ); + pthread_create( &WorldThread[SHUTDOWN_THREAD], NULL, (void *(__cdecl *)(void *))ShutdownServer, (PVOID)minutes); + Log( MSG_GMACTION, " %s : /shutdown %u" , thisclient->CharInfo->charname, shutdown); + } + else if(strcmp(command, "dquest")==0) // toggle quest debug mode + { + if(Config.Command_DQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + char buffer2[200]; + if( thisclient->questdebug ) + { + thisclient->questdebug = false; + sprintf ( buffer2, "Quest Debug Mode disabled" ); + SendPM(thisclient, buffer2); + } + else + { + thisclient->questdebug = true; + sprintf ( buffer2, "Quest Debug Mode enabled" ); + SendPM(thisclient, buffer2); + } + return true; + } + else if(strcmp(command, "debugmode")==0) //sets the GM into debugmode. PMs will be sent at various parts of the code + { + if(thisclient->Session->accesslevel <= 100) + return true; + char buffer2[200]; + if( thisclient->Session->codedebug || thisclient->CharInfo->IsDev == 0) + { + thisclient->Session->codedebug = false; + + sprintf ( buffer2, "Debug Mode disabled" ); + SendPM(thisclient, buffer2); + } + else + { + thisclient->Session->codedebug = true; + sprintf ( buffer2, "Debug Mode enabled" ); + SendPM(thisclient, buffer2); + } + return true; + } + else if(strcmp(command, "iquest")==0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + int n=1; + if ((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned int itemquest = atoi(tmp); + if ((tmp = strtok(NULL, " ")) != NULL) + n = atoi(tmp); + if( thisclient->questdebug ) + { + for(int i=0;iclient->SendPacket( &pak ); + } + } + return true; + } + else if(strcmp(command, "addquest")==0) + { + + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned int questnum =atoi(tmp); + Log( MSG_GMACTION, "add quest %i" , questnum); + for(int i=0;i<10;i++) + { + if (thisclient->quest.quests[i].QuestID != 0) continue; + thisclient->quest.quests[i].QuestID = questnum; + thisclient->quest.quests[i].StartTime = clock_t(); + Log( MSG_GMACTION, "quest added %i" , thisclient->quest.quests[i].QuestID); + + } + } + else if(strcmp(command, "listquest")==0) + { + + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + SendPM( thisclient, "Current Quests:" ); + for(int i=0;i<10;i++) + { + if (thisclient->quest.quests[i].QuestID == 0) continue; + SendPM( thisclient, "Quest[%i]: %i[%i] %i[%i] %i[%i] %i[%i] %i[%i]" , thisclient->quest.quests[i].QuestID, thisclient->quest.quests[i].Items[0].itemnum, thisclient->quest.quests[i].Items[0].count, thisclient->quest.quests[i].Items[1].itemnum, thisclient->quest.quests[i].Items[1].count, thisclient->quest.quests[i].Items[2].itemnum,thisclient->quest.quests[i].Items[2].count,thisclient->quest.quests[i].Items[3].itemnum,thisclient->quest.quests[i].Items[3].count,thisclient->quest.quests[i].Items[4].itemnum,thisclient->quest.quests[i].Items[4].count); + } + } + else if(strcmp(command, "listqflag") == 0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + SendPM( thisclient, "The following quest flags are set: "); + //for (dword i = 0; i < 0x40; i++) + //{ + // char buf2[5]; + // sprintf(buf2, "%i ", thisclient->quest.flags[i]); + // buffer.append(buf2); + // if (i > 0 && i%10 == 0) + // { + // SendPM(thisclient, (char*)buffer.c_str()); + // buffer = ""; + // } + //} + for (int i=0;i<64;i++) + { + for(int j=0;j<8;j++) + { + if(thisclient->quest.Qflags[i][j] == 1) + { + int flagval = i*8 + j; + SendPM( thisclient, "flag %i",flagval); + } + } + } + } + else if(strcmp(command, "listqswitch") == 0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + SendPM( thisclient, "The following quest switches are set: "); + char buffer[200]; + for(unsigned int j=0;j<=5;j++) + { + sprintf(buffer, "Switch [%i] = %02x ", j, thisclient->quest.quests[0].Switches[j]); + SendPM( thisclient, buffer); + } + } + else if(strcmp(command, "listqvar") == 0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + SendPM( thisclient, "Quest Variables"); + + string buffer = "Episode: "; + for(dword i = 0; i < 5; i++) + { + char buf2[5]; + sprintf(buf2, "%02x ", thisclient->quest.EpisodeVar[i]); + buffer.append(buf2); + } + SendPM( thisclient, (char*)buffer.c_str() ); + + buffer = "Job: "; + for(dword i = 0; i < 3; i++) + { + char buf2[5]; + sprintf(buf2, "%02x ", thisclient->quest.JobVar[i]); + buffer.append(buf2); + } + SendPM( thisclient, (char*)buffer.c_str()); + + buffer = "Planet: "; + for(dword i = 0; i < 7; i++) + { + char buf2[5]; + sprintf(buf2, "%02x ", thisclient->quest.PlanetVar[i]); + buffer.append(buf2); + } + SendPM( thisclient, (char*)buffer.c_str()); + + buffer = "Union: "; + for(dword i = 0; i < 10; i++) + { + char buf2[5]; + sprintf(buf2, "%02x ", thisclient->quest.UnionVar[i]); + buffer.append(buf2); + } + SendPM( thisclient, (char*)buffer.c_str()); + + for (dword j = 0; j < 10; j++) + { + if (thisclient->quest.quests[j].QuestID == 0) continue; + buffer = "Quest: "; + char buf3[5]; + sprintf(buf3, "%i ",thisclient->quest.quests[j].QuestID); + buffer.append(buf3); + for(dword i = 0; i < 10; i++) + { + char buf2[5]; + sprintf(buf2, "%02x ", thisclient->quest.quests[j].Variables[i]); + buffer.append(buf2); + } + SendPM( thisclient, (char*)buffer.c_str()); + } + } + else if(strcmp(command, "setqvar") == 0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned int vartype = atoi(tmp); + if ((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned int var = atoi(tmp); + if ((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned int val = atoi(tmp); + if (vartype > 3) return true; + switch (vartype) + { + case 0: + if (var >= 5) return true; + thisclient->quest.EpisodeVar[var] = val; + break; + case 1: + if (var >= 3) return true; + thisclient->quest.JobVar[var] = val; + break; + case 2: + if (var >= 7) return true; + thisclient->quest.PlanetVar[var] = val; + break; + case 3: + if (var >= 10) return true; + thisclient->quest.UnionVar[var] = val; + break; + default: + return true; + break; + } + SendPM( thisclient, "Set Var[%i][%i] = %i", vartype, var, val); + } + else if (strcmp(command, "qtrigger")==0) + { + if(Config.Command_IQuest > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + char* tempName; + int mode; + int success; + if ((tmp = strtok(NULL, " ")) == NULL)return true; tempName = tmp; + dword hash = MakeStrHash(tempName); + if ((tmp = strtok(NULL, " ")) == NULL) + mode = 0; + else + mode = 1; + SendPM(thisclient, "Execute Quest Trigger %s hash: [%08x]", tempName, hash); + success = thisclient->ExecuteQuestTrigger(hash); + SendPM(thisclient, "Quest returned %i", success); + if(mode = 1) + success = atoi(tmp); + SendPM(thisclient, "Quest value sent %i", success); + CPacket pakout(0x730); + pakout.AddByte(success); + pakout.AddByte(0); + pakout.AddDWord(hash); + thisclient->client->SendPacket(&pakout); + } + else if (strcmp(command, "shoptype")==0) + { + if(Config.Command_ShopType > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) return true; + unsigned int shoptype = atoi(tmp); + thisclient->Shop->ShopType = shoptype; + Log( MSG_GMACTION, " %s : /shoptype %i" , thisclient->CharInfo->charname, shoptype); + } + else if (strcmp(command, "stat")==0) /// Code By Minoc + { + if(Config.Command_Stat > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; char* statname =(char*)tmp; + if ((tmp = strtok(NULL, " "))==NULL) return true; int statvalue = atoi(tmp); + Log( MSG_GMACTION, " %s : /stat %s,%i" , thisclient->CharInfo->charname, statname, statvalue); + return pakGMStat( thisclient , statname , statvalue); + } + else if (strcmp(command, "statp")==0) + { + if (Config.Command_Stat > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; int statvalue = atoi(tmp); + char* name; + if ((tmp = strtok(NULL, " "))==NULL) + name = thisclient->CharInfo->charname; + else + name = tmp; + CPlayer* otherclient = GetClientByCharName(name); + if (otherclient == NULL) + return true; + if ((int)otherclient->CharInfo->StatPoints + statvalue < 0) + otherclient->CharInfo->StatPoints = 0; + else + otherclient->CharInfo->StatPoints += statvalue; + BEGINPACKET( pak, 0x720 ); + ADDWORD( pak, 32 ); + ADDWORD( pak, statvalue ); + ADDWORD( pak, 0 ); + otherclient->client->SendPacket( &pak ); + RESETPACKET( pak, 0x0730 ); + ADDWORD( pak, 5 ); + ADDWORD( pak, 0xa24d ); + ADDWORD( pak, 0x40b3 ); + otherclient->client->SendPacket( &pak ); + Log(MSG_GMACTION, " %s : /stats %i %s", thisclient->CharInfo->charname, statvalue, name); + return true; + } + else if (strcmp(command, "skillp")==0) + { + if (Config.Command_Stat > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; int skillp = atoi(tmp); + char* name; + if ((tmp = strtok(NULL, " "))==NULL) + name = thisclient->CharInfo->charname; + else + name = tmp; + CPlayer* otherclient = GetClientByCharName(name); + if (otherclient == NULL) + return true; + if ((int)otherclient->CharInfo->SkillPoints + skillp < 0) + otherclient->CharInfo->SkillPoints = 0; + else + otherclient->CharInfo->SkillPoints += skillp; + BEGINPACKET( pak, 0x720 ); + ADDWORD( pak, 37 ); + ADDWORD( pak, skillp ); + ADDWORD( pak, 0 ); + otherclient->client->SendPacket( &pak ); + RESETPACKET( pak, 0x0730 ); + ADDWORD( pak, 5 ); + ADDWORD( pak, 0xa24d ); + ADDWORD( pak, 0x40b3 ); + otherclient->client->SendPacket( &pak ); + Log(MSG_GMACTION, " %s : /skillp %i %s", thisclient->CharInfo->charname, skillp, name); + return true; + } + else if (strcmp(command, "killinrange")==0) + { + if(Config.Command_KillInRange > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned range=atoi(tmp); + Log( MSG_GMACTION, " %s : /killinrange %i" , thisclient->CharInfo->charname, range); + return pakGMKillInRange( thisclient, range ); + } + else if (strcmp(command, "gotomap")==0) // *** TELEPORT WITH MAP ID ***** + { // credits to Blackie + if(Config.Command_GoToMap > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; unsigned map=atoi(tmp); + Log( MSG_GMACTION, " %s : /gotomap %i" , thisclient->CharInfo->charname, map); + return pakGMGotomap(thisclient, map); + } + else if(strcmp(command, "heal")==0) + { + int hpamount = 0; + int mpamount = 0; + if(Config.Command_Heal > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " ")) == NULL) + { + hpamount = thisclient->Stats->MaxHP; + } + else + { + hpamount = atoi(tmp); + } + if ((tmp = strtok(NULL, " ")) == NULL) + { + mpamount = thisclient->Stats->MaxMP; + } + else + { + mpamount = atoi(tmp); + } + Log( MSG_GMACTION, " %s : /heal", thisclient->CharInfo->charname ); + return pakGMHeal( thisclient, hpamount, mpamount ); + } + else if(strcmp(command, "serverinfo")==0) + { + if(Config.Command_ServerInfo > thisclient->Session->accesslevel) + return true; + Log( MSG_GMACTION, " %s : /serverinfo" , thisclient->CharInfo->charname); + return pakGMServerInfo( thisclient ); + } + else if(strcmp(command, "targetinfo")==0) + { + if(Config.Command_TargetInfo > thisclient->Session->accesslevel) + return true; + return GMShowTargetInfo( thisclient ); + } + else if (strcmp(command, "hide")==0) + { + if(Config.Command_Hide > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + int mode= atoi( tmp ); + return pakGMHide( thisclient, mode ); + } + else if (strcmp(command, "class")==0) + { + if(Config.Command_Class > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) + return true; + char* classid = (char*)tmp; + Log( MSG_GMACTION, " %s : /class %s" , thisclient->CharInfo->charname, classid); + return pakGMClass( thisclient, classid ); + } + else if(strcmp(command, "convert")==0) + { + if(Config.Command_Convert > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if(thisclient->Battle->target==0) return true; + if ((tmp = strtok(NULL, " " ))==NULL) + return true; + UINT newmon = atoi(tmp); + if(newmon==0) return true; + CMonster* thismon = GetMonsterByID( thisclient->Battle->target, thisclient->Position->Map ); + if(thismon == NULL) + { + CNPC* thisnpc = GetNPCByID( thisclient->Battle->target, thisclient->Position->Map ); + if(thisnpc==NULL) return true; + MapList.Index[thisclient->Position->Map]->ConverToMonster( thisnpc, newmon ); + } + else + { + MapList.Index[thisclient->Position->Map]->ConverToMonster( thismon, newmon ); + } + return true; + } + + else if(strcmp(command, "who2")==0) + { + if(Config.Command_Who2 > thisclient->Session->accesslevel) + return true; + SendPM(thisclient, "The following players are currently connected;"); + int count=1; + int hiddenam=0; + char line0[200]; + while(count <= (ClientList.size()-1)) + { + CPlayer* whoclient = (CPlayer*)ClientList.at(count)->player; + if(whoclient != NULL) + { + if(whoclient->Session->accesslevel > 100) + { + //sprintf(line0, "%s - GM[%i]", whoclient->CharInfo->charname, whoclient->Session->accesslevel); + sprintf(line0, "%s - (GM %i, lvl %u), map %u[%.2f,%.2f]", whoclient->CharInfo->charname, whoclient->Session->accesslevel, + whoclient->Stats->Level, + whoclient->Position->Map, + whoclient->Position->current.x, + whoclient->Position->current.y); + + } + else + { + //sprintf(line0, "%s", whoclient->CharInfo->charname); + + sprintf(line0, "%s (job %u, lvl %u), map %u[%.2f,%.2f]", whoclient->CharInfo->charname, + whoclient->CharInfo->Job, + whoclient->Stats->Level, + whoclient->Position->Map, + whoclient->Position->current.x, + whoclient->Position->current.y); + + } + if(!whoclient->isInvisibleMode || thisclient->Session->accesslevel >= whoclient->Session->accesslevel) + { + SendPM(thisclient, line0 ); + } + else + { + hiddenam++; + } + count++; + } + } + sprintf(line0, "There are currently %i players connected!", ((ClientList.size()-1)-hiddenam)); + Log( MSG_GMACTION, " %s : /who2" , thisclient->CharInfo->charname); + SendPM(thisclient, line0 ); + return true; + } + else if(strcmp(command, "rate")==0) //Highly expandable function to change most config values related to rates + { + if(Config.Command_Rate > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + char* type = tmp; + if(strcmp(type,"death")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.DeathDelay = atoi(tmp); + SendPM(thisclient, "Monster Death Delay has been reset to %i",Config.DeathDelay ); + } + else + if(strcmp(type,"drop")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.DROP_RATE = atoi(tmp); + SendPM(thisclient, "Drop rate has been reset to %i",Config.DROP_RATE ); + } + else + if(strcmp(type,"exp")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.EXP_RATE = atoi(tmp); + SendPM(thisclient, "EXP rate has been reset to %i",Config.EXP_RATE ); + } + else + if(strcmp(type,"blue")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.BlueChance = atoi(tmp); + SendPM(thisclient, "Blue item conversion rate has been reset to %i",Config.BlueChance ); + } + if(strcmp(type,"attkmult")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.AttkMult = atoi(tmp); + SendPM(thisclient, "Attack delay multiplier has been reset to %i",Config.AttkMult ); + } + if(strcmp(type,"skillmult")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.SkillMult = atoi(tmp); + SendPM(thisclient, "Skill delay multiplier has been reset to %i",Config.SkillMult ); + } + if(strcmp(type,"dropdelay")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.DropDelay = atoi(tmp); + SendPM(thisclient, "Drop delay multiplier has been reset to %i",Config.DropDelay ); + } + if(strcmp(type,"floordrop")==0) + { + if ((tmp = strtok(NULL, " "))==NULL) return true; + Config.BASE_DROP_CHANCE = atoi(tmp); + SendPM(thisclient, "Minimum drop rate has been reset to %i",Config.BASE_DROP_CHANCE ); + } + return true; + } + else if(strcmp(command, "moveto")==0) + { + if(Config.Command_Moveto > thisclient->Session->accesslevel) + return true; + fPoint position; + if ((tmp = strtok(NULL, " "))==NULL) return true; + position.x = atof(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; + position.y = atof(tmp); + if ((tmp = strtok(NULL, " "))==NULL) return true; + position.z = atof(tmp); + pakGMMoveTo( thisclient, position ); + } + else if(strcmp(command, "settime")==0) + { + if(Config.Command_Settime > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned int time = atoi(tmp); + if(MapList.Index[thisclient->Position->Map]!=NULL) + { + switch(time) + { + case MORNING: + MapList.Index[thisclient->Position->Map]->MapTime = MapList.Index[thisclient->Position->Map]->morningtime; + SendSysMsg( thisclient, "Time changed!, rejoin to see change [MORNING]" ); + break; + case DAY: + MapList.Index[thisclient->Position->Map]->MapTime = MapList.Index[thisclient->Position->Map]->daytime; + SendSysMsg( thisclient, "Time changed!, rejoin to see change [DAY]" ); + break; + case EVENING: + MapList.Index[thisclient->Position->Map]->MapTime = MapList.Index[thisclient->Position->Map]->eveningtime; + SendSysMsg( thisclient, "Time changed!, rejoin to see change [EVENING]" ); + break; + case NIGHT: + MapList.Index[thisclient->Position->Map]->MapTime = MapList.Index[thisclient->Position->Map]->nighttime; + SendSysMsg( thisclient, "Time changed!, rejoin to see change [NIGHT]" ); + break; + default: + SendSysMsg( thisclient, "Time: 0=MORNING | 1=DAY | 2=EVENING | 3=NIGHT" ); + } + } + } + else if(strcmp(command, "transx")==0) + { + if(Config.Command_Transx > thisclient->Session->accesslevel) + return true; + thisclient->CharInfo->Sex = thisclient->CharInfo->Sex==0?1:0; + BEGINPACKET( pak, 0x720 ); + ADDWORD( pak, 2 ); + ADDWORD( pak, thisclient->CharInfo->Sex ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + RESETPACKET( pak, 0x0730 ); + ADDWORD( pak, 5 ); + ADDWORD( pak, 0xa24d ); + ADDWORD( pak, 0x40b3 ); + thisclient->client->SendPacket( &pak ); + } + else if (strcmp(command, "partylvl")==0) + { + if(Config.Command_Partylvl > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; int level= atoi( tmp ); + return pakGMPartylvl( thisclient, level ); + } + else if(strcmp(command, "pdmg")==0) + { + if(Config.Command_Pdmg > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned rate=atoi(tmp); + Log( MSG_GMACTION, " Rate for Player Dmg is now set at %i by %s" , rate, thisclient->CharInfo->charname); + return pakGMChangePlayerDmg(thisclient, rate); + } + else if(strcmp(command, "mdmg")==0) + { + if(Config.Command_Mdmg > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned rate=atoi(tmp); + Log( MSG_GMACTION, " Rate for Monster Dmg is now set at %i by %s" , rate, thisclient->CharInfo->charname); + return pakGMChangeMonsterDmg(thisclient, rate); + } + else if(strcmp(command, "rules")==0) // Rules Command by Matt + { + if(Config.Command_Rules > thisclient->Session->accesslevel) + return true; + SendPM(thisclient, "Please follow the following rules to ensure you have a fun time on this server!"); + SendPM(thisclient, "Rule 1: No hacking"); + SendPM(thisclient, "Rule 2: Do not abuse any bugs in the game"); + SendPM(thisclient, "Rule 3: Be respectful to all players and GMs"); + SendPM(thisclient, "Rule 4: Do not beg for items"); + SendPM(thisclient, "Rule 5: Do not harass any players or GM's"); + SendPM(thisclient, "Rule 6: Do not abuse any bugs in the game"); + } + else if(strcmp(command, "cfmode")==0) + { + if(Config.Command_Cfmode > thisclient->Session->accesslevel || thisclient->CharInfo->IsDev == 0) + return true; + if((tmp = strtok(NULL, " "))==NULL) return true; unsigned mode=atoi(tmp); + Log( MSG_GMACTION, " Clan field mode changed to %i by %s" , mode, thisclient->CharInfo->charname); + return pakGMChangeCfmode(thisclient, mode); + } + else if(strcmp(command, "skill")==0) + { + if (Config.Command_KillInRange > thisclient->Session->accesslevel) + return true; + if ((tmp = strtok(NULL, " "))==NULL) return true; + unsigned skillid = atoi(tmp); + Log(MSG_GMACTION, "%s cast skill %i", thisclient->CharInfo->charname, skillid); + SendPM(thisclient, "Casting skill %i", skillid); + CCharacter* Target = thisclient->GetCharTarget( ); + CSkills* thisskill = GetSkillByID(skillid); + if(thisskill == NULL)return true; + thisclient->UseSkill(thisskill, Target); + } + else if(strcmp(command, "sendhp")==0) + { + SendPM(thisclient, "Testing HP send"); + // SET PLAYER HEALTH + thisclient->Stats->HP = thisclient->Stats->MaxHP; + BEGINPACKET( pak, 0x79f ); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, thisclient->Stats->HP ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + } + else if (strcmp(command, "dialog")==0 && thisclient->Session->accesslevel >= 300 || thisclient->CharInfo->IsDev == 0) + { + //can be a very dangerous tool to use + UINT npc; + UINT dialog; + UINT eventid; + if ((tmp = strtok(NULL, " "))==NULL)return true; + npc = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL)return true; + dialog = atoi(tmp); + if ((tmp = strtok(NULL, " "))==NULL)return true; + eventid = atoi(tmp); + if(thisclient->ChangeDialog(npc, dialog, eventid)) + { + char buffer2[200]; + sprintf ( buffer2, "dialog for npc %i set to %i event set to %i", npc, dialog, eventid ); + SendPM(thisclient, buffer2); + } + else + { + char buffer2[200]; + sprintf ( buffer2, "dialog and event for npc %i not changed", npc ); + SendPM(thisclient, buffer2); + } + //thisclient->refresh = true; //doesn't exist in osprose. trying a different approach first + //thisclient->VisiblityList( ); + } + else + { + Log( MSG_WARNING, "Invalid GM Command '%s' by '%s'", command, thisclient->CharInfo->charname); + } + return true; +} + +// GM: Announcment +bool CWorldServer::pakGMAnn( CPlayer* thisclient, CPacket *P ) +{ + BEGINPACKET( pak, 0x702 ); + ADDSTRING( pak, thisclient->CharInfo->charname ); + ADDSTRING( pak, "> " ); + ADDSTRING( pak, (&P->Buffer[5])); + ADDBYTE( pak, 0x00); + SendToAll( &pak ); + + return true; +} + +// GM: Spawn x mobs +bool CWorldServer::pakGMMon( CPlayer* thisclient, int montype, int moncount, int monAI ) +{ + CMap* map = MapList.Index[thisclient->Position->Map]; //find current map + if(monAI == 331) //it's a TD mob + { + SendPM(thisclient,"%i monsters of type %i summoned in Map: %i ",moncount,montype,map->id); + for (int i=0; iTDMobList[map->TDNextMon] == 0) + { + map->TDMobList[map->TDNextMon] = montype; + map->TDNextMon ++; + if(map->TDNextMon > 100)map->TDNextMon = 0; + } + else + { + SendPM(thisclient,"Sorry! Cannot add monster %i. the TD Spawn list is full",i); + } + } + } + else // normal spawn + { + for (int i=0; iPosition->current, 10 ); + map->AddMonster( montype, position, 0, 0, 1, 0, 0, monAI ); + } + SendPM(thisclient,"%i monsters of type %i summoned in Map: %i using AI %i",moncount,montype,map->id,monAI); + } + + SendPM(thisclient,"Spawning successful"); + return true; +} + +// GM: Teleport somewhere +bool CWorldServer::pakGMTele( CPlayer* thisclient, int map, float x, float y ) +{ + fPoint coord; + coord.x = x; + coord.y = y; + CMap* mapid = GServer->MapList.Index[map]; + if(mapid == NULL) + { + SendPM(thisclient,"%i is not a valid map id",map); + return true; + } + if(thisclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + MapList.Index[map]->TeleportPlayer( thisclient, coord, false ); + return true; +} + +// GM: apply Status effects for a skill id defined in 'effect' +bool CWorldServer::pakGMEffect(CPlayer* thisclient, int effect) +{ + if(thisclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + // build buffs first + BYTE buff1 = 0; + BYTE buff2 = 0; + BYTE buff3 = 0; + BYTE buff4 = 0; + //Build Debuffs and Buffs + //Up + if(effect == 18)//A_ATTACK: + buff2 += ATTACK_UP; + if(effect == 20)//A_DEFENSE: + buff2 += DEFENSE_UP; + if(effect == 22)//A_MDEFENSE_UP + buff2 += MDEFENSE_UP; + if(effect == 24)//A_ACCUR: + buff3 += HITRATE_UP; + if(effect == 14)//A_DASH: + buff1 += DASH_UP; + if(effect == 16)//A_HASTE: + buff2 += HASTE_UP; + if(effect == 12)//A_HP: + buff1 += HP_UP; + if(effect == 13)//A_MP: + buff1 += MP_UP; + if(effect == 26)//A_CRITICAL: + buff3 += CRITICAL_UP; + if(effect == 28)//A_DODGE: + buff1 += DODGE_UP; + if(effect == 36)//A_Extra_Damage: + buff4 += DAMAGE_UP; + //Down + if(effect == 19) // A_ATTACK: + buff2 += ATTACK_DOWN; + if(effect == 21)//A_DEFENSE: + buff2 += DEFENSE_DOWN; + if(effect == 23) + buff2 += MDEFENSE_DOWN; + if(effect == 25)//A_ACCUR: + buff3 += HITRATE_DOWN; + if(effect == 15)//A_DASH: + buff1 += DASH_DOWN; + if(effect == 17)//A_HASTE: + buff2 += HASTE_DOWN; + if(effect == 27)//A_CRITICAL: + buff3 += CRITICAL_DOWN; + if(effect == 32)//A_STUN + buff4+= STUN; + if(effect >= 7 && effect <= 11)//A_POISON + buff1+= POISONED; + if(effect == 30)//A_MUTE + buff3+= MUTED; + int thisbuff = (buff1 * 0x01) + (buff2 * 0x100 ) + (buff3 * 0x10000) + (buff4 * 0x1000000); + + BEGINPACKET( pak,0x7b7 ); + ADDWORD ( pak, thisclient->clientid ); + ADDDWORD ( pak, thisbuff ); + GServer->SendToVisible( &pak, thisclient ); + SendPM(thisclient,"Buff effect applied %i",thisbuff); +} + +// [by Paul_T] [Thanks to AridTag for the packet :D] +bool CWorldServer::pakGMMute( CPlayer* thisclient, char* name, int time) +{ + CPlayer* otherclient = GetClientByCharName ( name ); + if(otherclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + BEGINPACKET( pak, 0x70d ); + ADDBYTE ( pak, 0 ); + ADDWORD ( pak, time ); + ADDSTRING ( pak, thisclient->CharInfo->charname ); + ADDBYTE ( pak, 0 ); + otherclient->client->SendPacket( &pak ); + return true; +} + +// GM: Item - Modified by Hiei (added refine/socket/stats) +bool CWorldServer::pakGMItem( CPlayer* thisclient, UINT itemid, UINT itemtype, UINT itemamount, UINT itemrefine, UINT itemls, UINT itemstats, UINT itemsocket ) +{ + CItem item; + item.count = itemamount; + item.durability = 40; + item.itemnum = itemid; + item.itemtype = itemtype; + item.lifespan = itemls; + item.refine = itemrefine; + item.stats = itemstats; + item.socketed = itemsocket; + item.appraised = 1; + item.gem = 0; + unsigned newslot = thisclient->GetNewItemSlot( item ); + if (newslot != 0xffff) + { + thisclient->items[newslot] = item; + thisclient->UpdateInventory( newslot ); + char buffer[200]; + sprintf( buffer, "Item added! (ID: %i) (Type: %i) (Refine: %i) (Socket: %i)", item.itemnum, item.itemtype, item.refine, item.socketed ); + BEGINPACKET ( pak, 0x702 ); + ADDSTRING( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + + }else{ + BEGINPACKET( pak, 0x7a7 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 5 ); + thisclient->client->SendPacket( &pak ); + + RESETPACKET ( pak, 0x702 ); + ADDSTRING( pak, "No free slot !" ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + } + return true; +} + +// GM: Kick +bool CWorldServer::pakGMKick( CPlayer* thisclient, char* name ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if(otherclient == NULL) + return true; + BEGINPACKET( pak, 0x702 ); + ADDSTRING( pak, "You were disconnected from the server !" ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); + + RESETPACKET( pak, 0x707 ); + ADDWORD( pak, 0 ); + otherclient->client->SendPacket( &pak ); + + otherclient->client->isActive = false; + + return true; +} + +// GM: Ban +bool CWorldServer::pakGMBan( CPlayer* thisclient, char* name ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if(otherclient == NULL) + return true; + otherclient->Session->accesslevel = -1; + DB->QExecute( "UPDATE accounts SET accesslevel='0' WHERE id=%i", otherclient->Session->userid); + BEGINPACKET( pak, 0x702 ); + ADDSTRING( pak, "You were banned from the server !" ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); + + RESETPACKET( pak, 0x707 ); + ADDWORD( pak, 0 ); + otherclient->client->SendPacket( &pak ); + + otherclient->client->isActive = false; + + return true; +} + +// GM: Add/Remove/Drop/Set zuly +bool CWorldServer::pakGMZuly( CPlayer* thisclient, int mode, int amount, char* charname ) +{ + CPlayer* otherclient = GetClientByCharName(charname); + if(otherclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + if (mode == 1) + { + //ADD + otherclient->CharInfo->Zulies += amount; + BEGINPACKET( pak, 0x71d ); + ADDQWORD( pak, otherclient->CharInfo->Zulies ); + otherclient->client->SendPacket( &pak ); + } + if (mode == 2) + { + //REMOVE + otherclient->CharInfo->Zulies -= amount; + BEGINPACKET( pak, 0x71d ); + ADDQWORD( pak, otherclient->CharInfo->Zulies ); + otherclient->client->SendPacket( &pak ); + } + if (mode == 3) + { + //DROP + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GetNewClientID(); + thisdrop->type = 1; // ZULY + thisdrop->pos.x = otherclient->Position->current.x; + thisdrop->pos.y = otherclient->Position->current.y; + thisdrop->posMap = otherclient->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = amount; + thisdrop->owner = 0; + CMap* map = MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + } + if (mode == 4) + { + //SET + otherclient->CharInfo->Zulies = amount; + BEGINPACKET( pak, 0x71d ); + ADDQWORD( pak, otherclient->CharInfo->Zulies ); + otherclient->client->SendPacket( &pak ); + } + return true; +} + +// Change player Level +bool CWorldServer::pakGMLevel( CPlayer* thisclient, int level, char* name ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if(otherclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + if (((int)otherclient->Stats->Level + level) > 0) + otherclient->Stats->Level += level; + else + otherclient->Stats->Level = 1; + if (otherclient->Stats->Level > 250) + otherclient->Stats->Level = 250; + otherclient->CharInfo->Exp = 0; + if (level < 0) { + otherclient->CharInfo->StatPoints = 0; + otherclient->CharInfo->SkillPoints = 0; + if(otherclient->Stats->Level > 1) + { + for(int s = 2; s <= otherclient->Stats->Level; s++) + { + otherclient->CharInfo->StatPoints += 10; + otherclient->CharInfo->StatPoints += s / 2; + if (s >= 10) + { + switch (s) { + case 10: + case 14: + otherclient->CharInfo->SkillPoints += 2; + break; + case 18: + otherclient->CharInfo->SkillPoints += 3; + break; + case 22: + otherclient->CharInfo->SkillPoints += 4; + break; + default: + break; + } + // 5 skill points every 4 levels from 22 + if (s > 22 && s < 99 && ((s - 22) % 4) == 0) + otherclient->CharInfo->SkillPoints += 5; + // 5 skill points every 2 levels from 98 + if (s > 98 && (s % 2) == 0) + otherclient->CharInfo->SkillPoints += 5; + } + } + } + pakGMStat(otherclient, "str", 15); + pakGMStat(otherclient, "dex", 15); + pakGMStat(otherclient, "con", 15); + pakGMStat(otherclient, "int", 15); + pakGMStat(otherclient, "cha", 10); + pakGMStat(otherclient, "sen", 10); + } else { + for(int s = (otherclient->Stats->Level - level + 1); s <= otherclient->Stats->Level; s++) + { + otherclient->CharInfo->StatPoints += 10; + otherclient->CharInfo->StatPoints += s / 2; + if (s >= 10) + { + switch (s) + { + case 10: + case 14: + otherclient->CharInfo->SkillPoints += 2; + break; + case 18: + otherclient->CharInfo->SkillPoints += 3; + break; + case 22: + otherclient->CharInfo->SkillPoints += 4; + break; + default: + break; + } + // 5 skill points every 4 levels from 22 + if (s > 22 && s < 99 && ((s - 22) % 4) == 0) + otherclient->CharInfo->SkillPoints += 5; + // 5 skill points every 2 levels from 98 + if (s > 98 && (s % 2) == 0) + otherclient->CharInfo->SkillPoints += 5; + } + } + } + BEGINPACKET( pak, 0x79e ); + ADDWORD( pak, otherclient->clientid ); + ADDWORD( pak, otherclient->Stats->Level ); + ADDDWORD( pak, otherclient->CharInfo->Exp ); + ADDWORD( pak, otherclient->CharInfo->StatPoints ); + ADDWORD( pak, otherclient->CharInfo->SkillPoints ); + otherclient->client->SendPacket( &pak ); + + RESETPACKET( pak, 0x79e ); + ADDWORD( pak, otherclient->clientid ); + SendToVisible( &pak, otherclient, false ); + + otherclient->SetStats( ); + otherclient->Stats->HP = otherclient->Stats->MaxHP; + otherclient->Stats->MP = otherclient->Stats->MaxMP; + return true; +} + + +// Reload Mob Spawn +bool CWorldServer::ReloadMobSpawn( CPlayer* thisclient, int id ) +{ + CSpawnArea* thisspawn = new (nothrow) CSpawnArea; + if(thisspawn == NULL) + { + Log(MSG_WARNING, "Error allocing memory" ); + return true; + } + thisspawn->thisnpc = GetNPCDataByID( thisclient->GMRespawnPoints.mobID ); + if(thisspawn->thisnpc == NULL) + { + SendSysMsg( thisclient, "Invalid Respawn" ); + return true; + } + thisspawn->id = id; + thisspawn->map = thisclient->GMRespawnPoints.map; + thisspawn->montype = thisclient->GMRespawnPoints.mobID; + thisspawn->min = thisclient->GMRespawnPoints.min; + thisspawn->max = thisclient->GMRespawnPoints.max; + thisspawn->respawntime = thisclient->GMRespawnPoints.respawntime; + thisspawn->pcount = thisclient->GMRespawnPoints.n; + thisspawn->points = thisclient->GMRespawnPoints.points; + CMap* map = MapList.Index[thisspawn->map]; + for(int j=0; jmax; j++) + { + fPoint position = RandInPoly( thisspawn->points, thisspawn->pcount ); + map->AddMonster( thisspawn->montype, position, 0, thisspawn->id, 1 ); + } + thisspawn->lastRespawnTime=clock(); + return true; +} + +// Teleport To player to other place +bool CWorldServer::pakGMTeleOtherPlayer( CPlayer *thisclient, char* name, int map, float x, float y ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if (otherclient != NULL) + { + fPoint coord; + coord.x = x; + coord.y = y; + MapList.Index[map]->TeleportPlayer( otherclient, coord, false ); + } + else + { + SendSysMsg( thisclient, "User does not exist or is not online!" ); + } + return true; +} + +// Teleport Me to player +bool CWorldServer::pakGMTeleToPlayer( CPlayer* thisclient, char* name ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if (otherclient != NULL) + { + MapList.Index[otherclient->Position->Map]->TeleportPlayer( thisclient, otherclient->Position->current, false ); + } + else + { + SendSysMsg( thisclient, "User does not exist or is not online!" ); + } + return true; +} + +// Teleport Player Here +bool CWorldServer::pakGMTelePlayerHere( CPlayer* thisclient, char* name ) +{ + CPlayer* otherclient = GetClientByCharName( name ); + if (otherclient != NULL) + { + CMap* map = MapList.Index[thisclient->Position->Map]; + map->TeleportPlayer( otherclient, thisclient->Position->current, false ); + } + else + { + SendSysMsg( thisclient, "User does not exist or is not online!"); + } + return true; +} + +// Show Player info +bool CWorldServer::pakGMInfo(CPlayer* thisclient, char* name) +{ + CPlayer* otherclient = GetClientByCharName(name); + if(otherclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + char buffer[200]; + const char* jobname; + + if(otherclient != 0) { + if((otherclient->CharInfo->Job - 0) == 0) { jobname = "Visitor"; } + else if((otherclient->CharInfo->Job - 0) == 111) { jobname = "Soldier"; } + else if((otherclient->CharInfo->Job - 0) == 121) { jobname = "Knight"; } + else if((otherclient->CharInfo->Job - 0) == 122) { jobname = "Champion"; } + else if((otherclient->CharInfo->Job - 0) == 211) { jobname = "Muse"; } + else if((otherclient->CharInfo->Job - 0) == 221) { jobname = "Mage"; } + else if((otherclient->CharInfo->Job - 0) == 222) { jobname = "Cleric"; } + else if((otherclient->CharInfo->Job - 0) == 311) { jobname = "Hawker"; } + else if((otherclient->CharInfo->Job - 0) == 321) { jobname = "Raider"; } + else if((otherclient->CharInfo->Job - 0) == 322) { jobname = "Scout"; } + else if((otherclient->CharInfo->Job - 0) == 411) { jobname = "Dealer"; } + else if((otherclient->CharInfo->Job - 0) == 421) { jobname = "Bourgeois"; } + else if((otherclient->CharInfo->Job - 0) == 422) { jobname = "Artisan"; } + else { jobname = "Unknown"; } + + BEGINPACKET(pak, 0x702); + + sprintf(buffer, "Info about <%s>" , otherclient->CharInfo->charname); + ADDSTRING(pak, buffer); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + + RESETPACKET(pak, 0x702); + sprintf(buffer, "Account: %s | ID: %i" , otherclient->Session->username, otherclient->Session->userid); + ADDSTRING(pak, buffer); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + + RESETPACKET(pak, 0x702); + sprintf(buffer, "Level: %i | Job: %s" , (otherclient->Stats->Level-0) , jobname); + ADDSTRING(pak, buffer); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + + RESETPACKET(pak, 0x702); + sprintf(buffer,"Map: %i , X: %i , Y: %i", (otherclient->Position->Map-0), (int)otherclient->Position->current.x, (int)otherclient->Position->current.y); + ADDSTRING(pak, buffer); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + } + else { + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User does not exist or is not online!"); + ADDBYTE(pak, 0) + thisclient->client->SendPacket(&pak); + } + return true; +} + +// Add Fairy +bool CWorldServer::pakGMFairyto(CPlayer* thisclient, char* name, int mode) +{ + if (GServer->Config.FairyMode== 0){ + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "Fairy feature is de-activated."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; + } + CPlayer* otherclient = GetClientByCharName (name); + if(otherclient == NULL){ + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User does not exist or is not online."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; + } + if(mode == 0 && otherclient->Fairy == false){ + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User already not fairied."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; + } + if(mode == 1 && otherclient->Fairy == true) + { + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User already fairied."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; + } + if(!otherclient->Fairy && mode == 1) + { + int FairyIndex=100; + for (int i=0; iConfig.FairyMax; i++) + { + if (GServer->FairyList.at(i)->assigned == false) + { + FairyIndex=i; + i=GServer->Config.FairyMax; + } + } + if (FairyIndex == 100) + { + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "No free Fairy."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; + } + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User fairied."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + int ListIndex; + for (int i=0; iClientList.at(i)->player == otherclient) + { + ListIndex = i; + i = GServer->ClientList.size(); + } + } + otherclient->Fairy = true; + otherclient->FairyListIndex = FairyIndex; + GServer->FairyList.at(FairyIndex)->ListIndex = ListIndex; + GServer->FairyList.at(FairyIndex)->assigned = true; + GServer->FairyList.at(FairyIndex)->LastTime = clock(); + GServer->FairyList.at(FairyIndex)->WaitTime = GServer->Config.FairyWait; + GServer->DoFairyStuff( otherclient, mode ); + otherclient->SetStats(); + Log( MSG_INFO, "HP: %i MP: %i ATK: %i DEF: %i CRI: %i MSPD: %i", otherclient->Stats->MaxHP, otherclient->Stats->MaxMP, otherclient->Stats->Attack_Power, otherclient->Stats->Defense, otherclient->Stats->Critical, otherclient->Stats->Move_Speed); + } + if(otherclient->Fairy && mode == 0){ + GServer->DoFairyFree(otherclient->FairyListIndex); + GServer->FairyList.at(otherclient->FairyListIndex)->WaitTime = GServer->Config.FairyWait; + otherclient->Fairy = false; + otherclient->FairyListIndex = 0; + GServer->DoFairyStuff( otherclient, mode ); + BEGINPACKET(pak, 0x702); + ADDSTRING(pak, "User unfairied."); + ADDBYTE(pak, 0); + otherclient->client->SendPacket(&pak); + otherclient->SetStats(); + Log( MSG_INFO, "HP: %i MP: %i ATK: %i DEF: %i CRI: %i MSPD: %i", otherclient->Stats->MaxHP, otherclient->Stats->MaxMP, otherclient->Stats->Attack_Power, otherclient->Stats->Defense, otherclient->Stats->Critical, otherclient->Stats->Move_Speed); + } + otherclient->SetStats(); + return true; +} + +// Activate de-activate Fairy mode in game +bool CWorldServer::pakGMManageFairy(CPlayer* thisclient, int mode) +{ + BEGINPACKET (pak, 0x702); + if(mode == 0) + { + if (GServer->Config.FairyMode != mode) + { + GServer->Config.FairyMode = 0; + ADDSTRING(pak, "You have de-activated the Fairy mode ingame."); + } + else + { + ADDSTRING(pak, "The Fairy mode is already de-activated."); + } + } + else + { + if (GServer->Config.FairyMode != mode) + { + GServer->Config.FairyMode = 1; + for (int i=0; iConfig.FairyMax; i++) + { + GServer->DoFairyFree(i); + GServer->FairyList.at(i)->WaitTime = GServer->Config.FairyWait; + } + ADDSTRING(pak, "You have activated the Fairy mode ingame."); + } + else + { + ADDSTRING(pak, "The Fairy mode is already activated."); + } + } + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + + return true; +} + +// Change FairyWait +bool CWorldServer::pakGMChangeFairyWait(CPlayer* thisclient, int newvalue) +{ + GServer->Config.FairyWait = newvalue; + for (int i=0; iFairyList.size(); i++) + { + GServer->FairyList.at(i)->WaitTime = newvalue; + } + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the time between each Fairies"); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +// Change FairyStay +bool CWorldServer::pakGMChangeFairyStay(CPlayer* thisclient, int newvalue) +{ + GServer->Config.FairyStay = newvalue; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the time of Fairies for Buffing "); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +// Change Fairy Test mode. +// 0 -> normal random x * Fairywait +// 1 -> Test mode activated: 1 * Fairywait only +bool CWorldServer::pakGMChangeFairyTestMode(CPlayer* thisclient, int mode) +{ + GServer->Config.FairyTestMode = mode; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the Fairy test mode."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +// Give Zuly +bool CWorldServer::pakGMZulygive(CPlayer* thisclient, char* name, int zuly) +{ + CPlayer* otherclient = GetClientByCharName (name); + if(otherclient == NULL) + { + SendPM(thisclient,"Target Player does not exist or is offline"); + return true; + } + otherclient->CharInfo->Zulies += zuly; + BEGINPACKET(pak, 0x7a7); + ADDWORD(pak, otherclient->clientid); + ADDWORD(pak, 0); + ADDBYTE(pak, 0); + ADDWORD(pak, 0xccdf );//0xccccccdf); + ADDDWORD(pak, zuly); + otherclient->client->SendPacket(&pak); + + return true; +} + +// Spawn a NPC +bool CWorldServer::pakGMNpc(CPlayer* thisclient, int npcid,int dialogid) +{ + CNPC* thisnpc = new CNPC; + assert(thisnpc); + thisnpc->clientid = GetNewClientID(); + thisnpc->dir = 0; + thisnpc->npctype = npcid; + thisnpc->pos = thisclient->Position->current; + thisnpc->posMap = thisclient->Position->Map; + thisnpc->thisnpc = GetNPCDataByID( npcid ); + if( thisnpc->thisnpc == NULL ) + { + SendPM(thisclient,"Cannot create NPC"); + delete thisnpc; + return true; + } + thisnpc->thisnpc->dialogid = dialogid; + CMap* map = MapList.Index[thisclient->Position->Map]; + map->AddNPC( thisnpc ); + SendPM(thisclient,"NPC type %i added to map %i",npcid,thisnpc->posMap); + return true; +} + +// Give Item to Player +bool CWorldServer::pakGMItemtoplayer(CPlayer* thisclient, char* name , UINT itemid, UINT itemtype, UINT itemamount, UINT itemrefine, UINT itemls, UINT itemstats, UINT itemsocket) +{ + CItem item; + item.count = itemamount; + item.durability = 40; + item.itemnum = itemid; + item.itemtype = itemtype; + item.lifespan = 100; //itemls Set lifespan to 100 + item.refine = itemrefine; + item.stats = itemstats; + item.socketed = itemsocket; + item.appraised = 1; + if(itemstats > 300) + { + item.gem = itemstats; + } + else + { + item.gem = 0; + } + + CPlayer* otherclient = GetClientByCharName ( name ); + + if(otherclient != NULL) + { + unsigned newslot = otherclient->GetNewItemSlot( item ); + if(newslot != 0xffff) + { + otherclient->items[newslot] = item; + otherclient->UpdateInventory( newslot ); + + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have recieved an item from a GM !"); + ADDBYTE(pak, 0); + otherclient->client->SendPacket(&pak); + + RESETPACKET (pak, 0x702); + ADDSTRING(pak, "Item has been given!"); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + } + else + { + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "No free slot !"); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + } + } + + return true; +} + +// Do Emotion +bool CWorldServer::pakGMDoEmote( CPlayer* thisclient, int emotionid ) +{ + ClearBattle( thisclient->Battle ); + BEGINPACKET( pak, 0x781 ); + ADDDWORD( pak, emotionid ); + ADDWORD( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient ); + return true; +} + +// Change GM Stats (Coded by Minoc) +bool CWorldServer::pakGMStat( CPlayer* thisclient, const char* statname, int statvalue ) +{ + int statid; + if (strcmp( statname, "Str" )==0 || strcmp( statname, "str" )==0) + { + thisclient->Attr->Str = statvalue; + statid = 0; + } + else if (strcmp( statname, "Dex" )==0 || strcmp( statname, "dex" )==0) + { + thisclient->Attr->Dex = statvalue; + statid = 1; + } + else if (strcmp( statname, "Int" )==0 || strcmp( statname, "int" )==0) + { + thisclient->Attr->Int = statvalue; + statid = 2; + } + else if (strcmp( statname, "Con" )==0 || strcmp( statname, "con" )==0) + { + thisclient->Attr->Con = statvalue; + statid = 3; + } + else if (strcmp( statname, "Cha" )==0 || strcmp( statname, "cha" )==0) + { + thisclient->Attr->Cha = statvalue; + statid = 4; + } + else if (strcmp( statname, "Sen" )==0 || strcmp( statname, "sen" )==0) + { + thisclient->Attr->Sen = statvalue; + statid = 5; + } + else + { + return true; + } + BEGINPACKET( pak, 0x7a9 ); + ADDBYTE( pak, statid ); + ADDWORD( pak, (unsigned short)statvalue ); + thisclient->client->SendPacket( &pak ); + thisclient->SetStats( ); + return true; +} + +// GM: Teleport using map id credits to Blackie +bool CWorldServer::pakGMGotomap( CPlayer* thisclient, int map ) +{ + CMap* thismap = GServer->MapList.Index[map]; + if(thismap == NULL) + { + SendSysMsg( thisclient, "Invalid map id" ); + return true; + } + CRespawnPoint* thisrespawn = MapList.Index[map]->GetFirstRespawn( ); + if(thisrespawn==NULL) + { + SendSysMsg( thisclient, "This map have no respawn" ); + return true; + } + MapList.Index[map]->TeleportPlayer( thisclient, thisrespawn->dest, false ); + return true; +} + +// Heal a player ( by rl2171 ) +bool CWorldServer::pakGMHeal( CPlayer* thisclient, int hpamount, int mpamount ) +{ + //this one works with the modified trose. pak 0x7ec does not work with base 112 client + thisclient->Stats->HP = hpamount; + thisclient->Stats->MP = mpamount; + BEGINPACKET( pak, 0x7ec ); + ADDWORD( pak, hpamount ); + ADDWORD( pak, mpamount ); + thisclient->client->SendPacket( &pak ); + + //experimental heal command. Might not work but if it does then we are sorted. + //BEGINPACKET( pak, 0x7d5 ); + //ADDDWORD ( pak, thisclient->CharInfo->charid ); + //ADDWORD ( pak, thisclient->clientid ); + //ADDWORD ( pak, thisclient->Stats->MaxHP ); + //ADDWORD ( pak, thisclient->Stats->HP ); + //ADDDWORD ( pak, 0x01000000 ); + //ADDDWORD ( pak, 0x0000000f ); + //ADDWORD ( pak, 0x1388 ); + // didn't work either + + //let's try this + //BEGINPACKET( pak, 0x753 ); + //ADDWORD ( pak, thisclient->clientid ); // USER ID + //ADDWORD ( pak, thisclient->Stats->HP ); // CURRENT HP + //ADDWORD ( pak, thisclient->Stats->MP ); // CURRENT MP + //well it kind of works. You get healed OK but with a pillar of light. + //works Ok here but we need a heal without effects for other places + //also resets the zone time lol. Apparently this is not a good plan + + //thisclient->client->SendPacket( &pak ); + Log(MSG_INFO,"GM Heal used for HP %i MP %i",hpamount,mpamount); + SendPM(thisclient, "Set HP to %i and MP to %i",hpamount,mpamount); + return true; +} + +// GM: Server Information ( by rl2171 ) modified by me +bool CWorldServer::pakGMServerInfo( CPlayer* thisclient ) +{ + char buffer[200]; + // Players Online + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[SYS]ServerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, "SERVER INFORMATION" ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Online Players: %i", (int)ClientList.size()-1 );// -1 (we don't count charserver) + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[SYS]ServerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + // Exp / Zulies / Drop rates + sprintf( buffer, "Exp %i | Zulies %i | Drops %i", Config.EXP_RATE, Config.ZULY_RATE, Config.DROP_RATE ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[SYS]ServerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + // Send map time + if(MapList.Index[thisclient->Position->Map]!=NULL) + { + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[SYS]ServerInfo" ); + ADDBYTE( pak, 0 ); + switch(MapList.Index[thisclient->Position->Map]->CurrentTime) + { + case MORNING: + sprintf( buffer, "The Time is: Morning[%i]", MapList.Index[thisclient->Position->Map]->MapTime%MapList.Index[thisclient->Position->Map]->dayperiod ); + ADDSTRING( pak, buffer ); + break; + case DAY: + sprintf( buffer, "The Time is: Day[%i]", MapList.Index[thisclient->Position->Map]->MapTime%MapList.Index[thisclient->Position->Map]->dayperiod ); + ADDSTRING( pak, buffer ); + break; + case EVENING: + sprintf( buffer, "The Time is: Evening[%i]", MapList.Index[thisclient->Position->Map]->MapTime%MapList.Index[thisclient->Position->Map]->dayperiod ); + ADDSTRING( pak, buffer ); + break; + case NIGHT: + sprintf( buffer, "The Time is: Night[%i]", MapList.Index[thisclient->Position->Map]->MapTime%MapList.Index[thisclient->Position->Map]->dayperiod ); + ADDSTRING( pak, buffer ); + break; + default: + sprintf( buffer, "Invalid Time is the End of world [%i]", MapList.Index[thisclient->Position->Map]->MapTime ); + ADDSTRING( pak, buffer ); + } + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + } + return true; +} + +// Show Target Info +bool CWorldServer::GMShowTargetInfo( CPlayer* thisclient ) +{ + if(thisclient->Battle->target==0) return true; + char buffer[200]; + CMonster* monster = GetMonsterByID( thisclient->Battle->target, thisclient->Position->Map ); + if(monster == NULL) return true; + float dist = distance( thisclient->Position->current, monster->Position->current ); + sprintf( buffer, "Target Position: %.4f, %.4f", monster->Position->current.x, monster->Position->current.y ); + BEGINPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Distance: %.0f", dist ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target Defense: %i", monster->Stats->Defense ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target Level: %i", monster->thisnpc->level ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target Attack Power: %i", monster->Stats->Attack_Power ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target Attack Speed: %.0f", monster->Stats->Attack_Speed ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target Move Speed: %i", monster->Stats->Move_Speed ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + sprintf( buffer, "Target HP/MAXHP: %i/%i", monster->Stats->HP, monster->Stats->MaxHP ); + RESETPACKET( pak, 0x784 ); + ADDSTRING ( pak, "[SYS]TargetInfo" ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, buffer ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// GM: Make yourself invisible from tomiz +bool CWorldServer::pakGMHide( CPlayer* thisclient, int mode ) +{ + BEGINPACKET( pak, 0x702 ); + if ( mode == 1 ) + { + thisclient -> isInvisibleMode = true; + ADDSTRING( pak, "You are now invisible !" ); + Log( MSG_GMACTION, " %s : /hide invisible" , thisclient->CharInfo->charname); + } + else + { + thisclient -> isInvisibleMode = false; + ADDSTRING( pak, "You are now visible !" ); + Log( MSG_GMACTION, " %s : /hide visible" , thisclient->CharInfo->charname); + } + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket ( &pak ); + return true; +} +// GM : Change the party lvl +bool CWorldServer::pakGMPartylvl( CPlayer* partyclient, int level ) +{ + if (partyclient->Party->party == NULL) return true; + if (level < 0 || level >50) return true; + partyclient->Party->party->PartyLevel = level; + if( partyclient->Party->party->PartyLevel == 50) + partyclient->Party->party->Exp = 0; + //Send Party Level and Party Exp + BEGINPACKET( pak, 0x7d4 ); // + ADDBYTE ( pak, partyclient->Party->party->PartyLevel); + ADDDWORD ( pak, partyclient->Party->party->Exp ); + partyclient->Party->party->SendToMembers( &pak ); + return true; +} + +// GM: Kill all mobs in a range of x-Fields +bool CWorldServer::pakGMKillInRange( CPlayer* thisclient, int range ) +{ + for(UINT j = 0; j < MapList.Index[thisclient->Position->Map]->MonsterList.size(); j++) + { + CMonster* thismon = MapList.Index[thisclient->Position->Map]->MonsterList.at(j); + if(thismon == NULL) + { + //SendPM(thisclient,"Target Monster does not exist"); + continue; + } + if( IsMonInCircle( thisclient, thismon, (float)range )) + { + //Kill the mob + thismon->Stats->HP = -1; + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, thismon->clientid ); + ADDWORD ( pak, thismon->clientid ); + ADDWORD ( pak, 0x8005); + //ADDDWORD ( pak, thismon->thisnpc->hp*thismon->thisnpc->level ); + //ADDDWORD ( pak, 16 ); + SendToVisible( &pak, thismon ); + CMap* map = MapList.Index[thisclient->Position->Map]; + map->DeleteMonster( thismon ); + } + } + return true; +} + +// GM: Change Class (from Crash) +bool CWorldServer::pakGMClass( CPlayer* thisclient, char* classid ) +{ + int classid_new; + + if ( strcmp ( classid , "Visitor" ) == 0 || strcmp ( classid , "visitor" ) == 0) + { + classid_new = 0; + } + else if ( strcmp ( classid , "Soldier" ) == 0 || strcmp ( classid , "soldier" ) == 0) + { + classid_new = 111; + } + else if ( strcmp ( classid , "Knight" ) == 0 || strcmp ( classid , "knight" ) == 0) + { + classid_new = 121; + } + else if ( strcmp ( classid , "Champion" ) == 0 || strcmp ( classid , "champion" ) == 0) + { + classid_new = 122; + } + else if ( strcmp ( classid , "Muse" ) == 0 || strcmp ( classid , "muse" ) == 0) + { + classid_new = 211; + } + else if ( strcmp ( classid , "Mage" ) == 0 || strcmp ( classid , "mage" ) == 0) + { + classid_new = 221; + } + else if ( strcmp ( classid , "Cleric" ) == 0 || strcmp ( classid , "cleric" ) == 0) + { + classid_new = 222; + } + else if ( strcmp ( classid , "Hawker" ) == 0 || strcmp ( classid , "hawker" ) == 0) + { + classid_new = 311; + } + else if ( strcmp ( classid , "Raider" ) == 0 || strcmp ( classid , "raider" ) == 0) + { + classid_new = 321; + } + else if ( strcmp ( classid , "Scout" ) == 0 || strcmp ( classid , "scout" ) == 0) + { + classid_new = 322; + } + else if ( strcmp ( classid , "Dealer" ) == 0 || strcmp ( classid , "dealer" ) == 0) + { + classid_new = 411; + } + else if ( strcmp ( classid , "Bourgeois" ) == 0 || strcmp ( classid , "bourgeois" ) == 0) + { + classid_new = 421; + } + else if ( strcmp ( classid , "Artisan" ) == 0 || strcmp ( classid , "artisan" ) == 0) + { + classid_new = 422; + } + else + { + return true; + } + // Set the quest vars for the new class + dword jVarSw = classid_new % 10; + thisclient->quest.JobVar[0] = 0; + thisclient->quest.JobVar[1] = 0; + if (jVarSw == 1) + { + thisclient->quest.JobVar[0] = 1; + } + else if (jVarSw == 2) + { + thisclient->quest.JobVar[0] = 1; + thisclient->quest.JobVar[1] = 2; + } + thisclient->CharInfo->Job = classid_new; + BEGINPACKET(pak, 0x0721); + ADDWORD(pak,4); + ADDWORD(pak, thisclient->CharInfo->Job); + ADDWORD(pak,0); + thisclient->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730);//05 00 2f f5 64 29 + ADDWORD(pak, 5); + ADDWORD(pak, 0xf52f);//ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x2964);//ADDWORD(pak, 0x40b3); + thisclient->client->SendPacket(&pak); + SendPM(thisclient, "Class changed!" ); + + return true; +} + +bool CWorldServer::pakGMTeleAllHere( CPlayer* thisclient ) +{ + int count=1; + while(count <= (ClientList.size()-1)) + { + CPlayer* otherclient = (CPlayer*)ClientList.at(count)->player; + if ((otherclient != NULL) && (otherclient != thisclient)) + { + CMap* map = MapList.Index[thisclient->Position->Map]; + map->TeleportPlayer( otherclient, thisclient->Position->current, false ); + } + count++; + } + return true; +} + +bool CWorldServer::pakGMMoveTo( CPlayer* thisclient, fPoint position ) +{ + if( thisclient->Shop->open || (!thisclient->Ride->Drive && thisclient->Ride->Ride) || !thisclient->Status->CanMove ) + return true; + if( thisclient->Status->Stance==1 ) + thisclient->Status->Stance=3; + thisclient->Position->destiny = position; + ClearBattle( thisclient->Battle ); + BEGINPACKET( pak, 0x79a ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisclient->Battle->target ); + ADDWORD ( pak, thisclient->Stats->Move_Speed ); + ADDFLOAT ( pak, thisclient->Position->destiny.x*100 ); + ADDFLOAT ( pak, thisclient->Position->destiny.y*100 ); + ADDFLOAT ( pak, thisclient->Position->destiny.z*100 ); + SendToVisible( &pak, thisclient ); + return true; +} +//change the player dmg rate +bool CWorldServer::pakGMChangePlayerDmg(CPlayer* thisclient, int rate) +{ + GServer->Config.PlayerDmg = rate; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the player dmg rate."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} +//change the monster dmg rate +bool CWorldServer::pakGMChangeMonsterDmg(CPlayer* thisclient, int rate) +{ + GServer->Config.MonsterDmg = rate; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the Monster dmg rate."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +//change the mSpeedModif || For test only +bool CWorldServer::pakGMChangeMSpeedModif(CPlayer* thisclient, int modif) +{ + GServer->MOVE_SPEED_MOD = modif; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "mSpeedModif Changed."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +//change the Cfmode +bool CWorldServer::pakGMChangeCfmode(CPlayer* thisclient, int mode) +{ + if(mode<0) mode = 0; + if(mode>1) mode = 1; + GServer->Config.Cfmode = mode; + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "You have changed the Clan Field Mode."); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + return true; +} + +//LMA: Giving union points to a player. +bool CWorldServer::pakGMUnionPoints(CPlayer* thisclient, char* name, int nb_points) +{ + CPlayer* otherclient = GetClientByCharName (name); + if(otherclient == NULL) + { + SendPM(otherclient,"Target Player does not exist or is offline"); + return true; + } + if(otherclient->Union_s->unionvar[0] == 0) + { + // Otherclient not in a union + Log(MSG_INFO,"Could not give union Points as this player is not in a Union"); + return true; + } + + otherclient->Union_s->unionvar[otherclient->Union_s->unionvar[0]] += nb_points; + int new_amount = otherclient->Union_s->unionvar[otherclient->Union_s->unionvar[0]]; + + //Need to run a quest trigger to make this happen client side too. Have to write the QSD first though. + + BEGINPACKET( pak, 0x720 ); // i think this should be 720 and not 721 as Lmame had it. 721 will change your union ID + ADDWORD( pak, otherclient->Union_s->unionvar[0] ); + ADDWORD( pak, new_amount ); + ADDWORD( pak, 0x0000 ); + otherclient->client->SendPacket( &pak ); + RESETPACKET( pak, 0x730 ); + ADDWORD ( pak, 0x0005 ); + ADDDWORD ( pak, 0x40b3a24d ); + otherclient->client->SendPacket( &pak ); + + SendPM(otherclient, "You have been given %i Faction Points by %s",nb_points,thisclient->CharInfo->charname); + Log( MSG_GMACTION, "%i Faction points given to %s by %s" , nb_points,name,thisclient->CharInfo->charname); + + + return true; +} diff --git a/Src/World Server/itemdata.cpp b/Src/World Server/itemdata.cpp new file mode 100644 index 0000000..52d460d --- /dev/null +++ b/Src/World Server/itemdata.cpp @@ -0,0 +1,45 @@ +// Added By Raven0123 + +#include "datatypes.h" + +unsigned CItem::GetVisInfo( ) +{ + if ( count == 0 ) Clear( ); + return ( ( refine >> 4 ) << 20 ) | ( socketed << 19 ) | ( stats << 10 ) | itemnum; +} + +unsigned short CItem::GetPakHeader( ) +{ + if ( count == 0 ) Clear( ); + return ( ( itemnum << 5 ) | ( itemtype ) ); +} + +unsigned CItem::GetPakData( ) +{ + unsigned retval = 0; + if ( count == 0 ) Clear( ); + if ( itemtype >= 10 && itemtype <= 13 || itemtype == 0x1f ) { + retval = count; + } else { + retval |= (refine>>4) << 28; + retval |= (appraised&1) << 27; + retval |= (socketed&1) << 26; + retval |= (lifespan*10) << 16; + retval |= durability << 9; + retval |= stats; + } + return retval; +} + +void CItem::Clear( ) +{ + itemnum = 0; + itemtype = 0; + count = 0; + refine = 0; + appraised = 0; + socketed = 0; + lifespan = 0; + durability = 0; + stats = 0; +} diff --git a/Src/World Server/ltbhandler.cpp b/Src/World Server/ltbhandler.cpp new file mode 100644 index 0000000..81627f4 --- /dev/null +++ b/Src/World Server/ltbhandler.cpp @@ -0,0 +1,168 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007,2008 OSRose Team http://www.dev-osrose.com + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +//lmame: LTB file reader. + +#include "ltbhandler.h" + + +void LTBStoreData(char* filename, LTBData* data) +{ + long int first_data = 0; + long int cur_offset =0; + DWORD nb_box = 0; + WORD nb_dial_box = 0; + int nb_dialogs = 0; + char templma[2]; + //char tempfield[1024]; + + + FILE* fh = fopen( filename, "rb" ); + if ( fh == NULL ) + { + Log(MSG_WARNING,"Could not load LTB %s",filename); + return; + } + + //How many dialogs? + fread( &nb_box, 4, 1, fh); + fread(&nb_dial_box,2,1,fh); + nb_dialogs = nb_box*nb_dial_box; + + data->nb_sentence = nb_box; + + //reading location of first data. + fseek(fh,8,SEEK_SET); + fread(&first_data,4,1,fh); + cur_offset=8; + fseek(fh,cur_offset,SEEK_SET); + + + //making the offset list. + long int cpt = 0; + map list_dialog; + + while(cur_offset < first_data) + { + Dialog temp_dial; + + //dialog. + //offset is 4 bytes, lg is 2 bytes. + temp_dial.dialognb = cpt; + fread(&temp_dial.offsetd,4,1,fh); + fread(&temp_dial.lengthd,2,1,fh); + list_dialog[cpt] = temp_dial; //I know, it's faster with .insert... + cur_offset += 6; + cpt++; + + if(temp_dial.offsetd == 0 && temp_dial.lengthd == 0) + { + continue; + } + + } + + //Getting dialogs... + int lang=0; + int no_block=0; + NPCLTB templtb; + string korean_save = ""; + + for (unsigned int k=0;k < list_dialog.size();k++) + { + lang = k%nb_box; + no_block = int(k/nb_box); + + if(lang == LTBINDEX) + { + templtb.name = "NoName"; + templtb.sentence = "NoSentence"; + korean_save = ""; + } + + if(list_dialog[k].offsetd == 0) + { + if (lang == LTBENGLISH) + { + //did we have Korean sentence? + if (korean_save != "") + { + templtb.sentence = korean_save; + } + + data->record.push_back(templtb); + } + + continue; + } + + //reading data + //lg*2 because of unicode :) + string temp; + fseek(fh,list_dialog[k].offsetd,SEEK_SET); + + if(lang!=LTBENGLISH&&lang!=LTBINDEX&&lang!=LTBKOREAN) + { + //LMA: jumping... + //fread(&tempfield,list_dialog[k].lengthd*2,1,fh); + fseek(fh,list_dialog[k].lengthd*2,SEEK_CUR); + continue; + } + + //unicode is NOT our friend :( + for(int j=0;j<(list_dialog[k].lengthd*2);j++) + { + fread(&templma,1,1,fh); + if (templma[0]=='\0') + continue; + temp+=templma[0]; + } + + //We save Korean as well... + switch (lang) + { + case LTBKOREAN: + { + korean_save = temp; + } + break; + case LTBENGLISH: + { + templtb.sentence = temp; + data->record.push_back(templtb); + } + break; + default: + { + templtb.name = temp; + } + break; + } + + } + + //closing the file... + fclose(fh); + + + return; +} + diff --git a/Src/World Server/ltbhandler.h b/Src/World Server/ltbhandler.h new file mode 100644 index 0000000..b1c7596 --- /dev/null +++ b/Src/World Server/ltbhandler.h @@ -0,0 +1,28 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.dev-osrose.com + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +//lmame +#include "worldserver.h" +#include +using namespace std; + + +void LTBStoreData(char* filename, LTBData* data); diff --git a/Src/World Server/main.cpp b/Src/World Server/main.cpp new file mode 100644 index 0000000..5562319 --- /dev/null +++ b/Src/World Server/main.cpp @@ -0,0 +1,81 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#define REVISION 32 +#include "worldserver.h" +//#pragma comment(lib,"ws2_32.lib") + +CWorldServer* GServer; + +unsigned char LOG_THISSERVER; + +// Main server function +int main(int argc, char *argv[]) +{ + srand( (unsigned)time(NULL) ); + srand( rand()*time(NULL) ); + LOG_THISSERVER = LOG_WORLD_SERVER; + InitWinSocket( ); + string fileconf = "worldserver.conf"; + if(argc>1) + { + fileconf = argv[1]; + } + CWorldServer *server = new (nothrow) CWorldServer( fileconf ); + if(server==NULL) + return -1; + pthread_attr_init(&server->at); + pthread_attr_setdetachstate(&server->at, PTHREAD_CREATE_JOINABLE); + // Connect mysql + MYSQL mysql; + server->DB = new CDatabase( server->Config.SQLServer.pcServer, + server->Config.SQLServer.pcUserName, + server->Config.SQLServer.pcPassword, + server->Config.SQLServer.pcDatabase, + server->Config.SQLServer.pcPort, &mysql ); + server->port = server->Config.CharPort; + if(server->Config.usethreads) + Log( MSG_INFO, "Using Threads" ); + // Start server + server->port = server->Config.WorldPort; + + if(server->DB->Connect( )==0) Log( MSG_INFO, "Connected to MySQL server" ); + else + { + delete server; + #ifdef _WIN32 + system("pause"); + #endif + return -1; + } + + server->LoadEncryption(); + + server->StartServer(); + // Close server + pthread_attr_destroy(&server->at); + server->DB->Disconnect( ); + CloseWinSocket( ); + Log(MSG_INFO, "Cleaning memory, please wait..." ); + delete server; + return EXIT_SUCCESS; +} + +// ----------------------------------------------------------------------------------------- diff --git a/Src/World Server/mapfunctions.cpp b/Src/World Server/mapfunctions.cpp new file mode 100644 index 0000000..376188a --- /dev/null +++ b/Src/World Server/mapfunctions.cpp @@ -0,0 +1,550 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +#include "worldmap.h" + +// convert a monster to other +CMonster* CMap::ConverToMonster( CMonster* monster, UINT newmontype, bool heal ) +{ + CNPCData* thisnpc = GServer->GetNPCDataByID( newmontype ); + if(thisnpc == NULL)// invalid type + return NULL; + monster->montype = newmontype; + monster->thisnpc = thisnpc; + monster->monAI = monster->thisnpc->AI; + monster->SetStats( ); + monster->Stats->MaxHP = monster->GetMaxHP( ); + monster->Stats->HP = monster->Stats->MaxHP; + monster->Stats->MaxMP = monster->GetMaxMP( ); + monster->Stats->MP = monster->Stats->MaxMP; + if(heal) + monster->Stats->HP = monster->Stats->MaxHP; + if(monster->owner == 0) + { + monster->MonsterDrop->mobdrop = GServer->GetDropData( monster->thisnpc->dropid ); + monster->MonsterDrop->mapdrop = GServer->GetDropData( id ); + } + BEGINPACKET( pak, 0x774 ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, newmontype ); + GServer->SendToVisible( &pak, monster ); + //monster->OnSpawn( false ); + return monster; +} + +// convert a npc to other [not working] +CMonster* CMap::ConverToMonster( CNPC* npc, UINT newmontype ) +{ + CMonster* monster = AddMonster( newmontype, npc->pos, 0, 0, 1 ); + if(monster == NULL) // invalid montype + return NULL; + GServer->ClearClientID( monster->clientid ); + monster->clientid = npc->clientid; + for(UINT i=0;iclientid ); + ADDWORD ( pak, newmontype ); + GServer->SendToVisible( &pak, monster ); + return monster; +} + +// Teleport player to this map and this coord. +bool CMap::TeleportPlayer( CPlayer* player, fPoint coord, bool TelePassenger ) +{ + GServer->MapList.Index[player->Position->Map]->RemovePlayer( player, false ); + player->Position->Map = id; + player->Position->current = coord; + player->Position->destiny = coord; + Log(MSG_DEBUG,"(TeleportPlayer) Destiny set to target position X: %f Y: %f.",player->Position->current.x,player->Position->current.y); + player->Session->inGame = false; + player->Position->lastMoveTime = clock(); + if(player->Stats->HP < 1)player->Stats->HP = player->Stats->MaxHP * 10 / 100; + if(!allowpat || !TelePassenger) + { + if(!allowpat) + player->Status->Stance = 0x03; + player->Ride->Drive = false; + player->Ride->charid= 0; + player->Ride->Ride = false; + } + AddPlayer( player ); + BEGINPACKET( pak, 0x07a8 ); + ADDWORD ( pak, player->clientid ); + ADDWORD ( pak, player->Position->Map ); + ADDFLOAT ( pak, player->Position->current.x * 100 ); + ADDFLOAT ( pak, player->Position->current.y * 100 ); + ADDWORD ( pak, (player->Status->Stance == 0x04?0x0201:0x0001) ); + player->client->SendPacket( &pak ); + if( player->Ride->Drive && player->Ride->charid != 0 ) + { + CPlayer* otherclient = GServer->GetClientByCID( player->Ride->charid ); + if( otherclient != NULL ) + { + if(TelePassenger) + { + TeleportPlayer( otherclient, coord ); + } + else + { + otherclient->Ride->Drive = false; + otherclient->Ride->charid = 0; + otherclient->Ride->Ride = false; + } + } + } + GServer->pakClearUser( player ); + GServer->ClearClientID( player->clientid ); + player->RestartPlayerVal( ); + return true; +} + +bool CMap::pakTeleport( CPlayer* player, unsigned int Map, fPoint coord, bool TelePassenger ) +{ + GServer->MapList.Index[player->Position->Map]->RemovePlayer( player, false ); + player->Position->Map = Map; + player->Position->current = coord; + player->Position->destiny = coord; + Log(MSG_DEBUG,"(pakTeleport) Destiny set to target position X: %f Y: %f.",player->Position->current.x,player->Position->current.y); + player->Session->inGame = false; + player->Position->lastMoveTime = clock(); + if(!allowpat || !TelePassenger) + { + if(!allowpat) + player->Status->Stance=0x03; + player->Ride->Drive = false; + player->Ride->charid= 0; + player->Ride->Ride = false; + } + AddPlayer( player ); + BEGINPACKET( pak, 0x07a8 ); + ADDWORD ( pak, player->clientid ); + ADDWORD ( pak, player->Position->Map ); + ADDFLOAT ( pak, player->Position->current.x*100 ); + ADDFLOAT ( pak, player->Position->current.y*100 ); + ADDWORD ( pak, (player->Status->Stance == 0x04?0x0201:0x0001) ); + player->client->SendPacket( &pak ); + if( player->Ride->Drive && player->Ride->charid!=0 ) + { + CPlayer* otherclient = GServer->GetClientByCID( player->Ride->charid ); + if( otherclient != NULL ) + { + if(TelePassenger) + { + TeleportPlayer( otherclient, coord ); + } + else + { + otherclient->Ride->Drive = false; + otherclient->Ride->charid= 0; + otherclient->Ride->Ride = false; + } + } + } + GServer->pakClearUser( player ); + GServer->ClearClientID( player->clientid ); + player->RestartPlayerVal( ); + return true; +} + +// return near respawn [NULL if there are no respawn] +CRespawnPoint* CMap::GetNearRespawn( CPlayer* player ) +{ + CRespawnPoint* respawn = NULL; + float distance = 0xffff; + for(UINT i=0;idistance( player->Position->current, RespawnList.at(i)->dest ); + if( resdist < distance ) + { + respawn = RespawnList.at(i); + distance = resdist; + } + } + return respawn; +} + +// Return "start" respawn +CRespawnPoint* CMap::GetStartRespawn( ) +{ + for( UINT i = 0; i < RespawnList.size(); i++ ) + { + if( RespawnList.at(i)->masterdest ) + return RespawnList.at(i); + } + return NULL; +} + +// Return first respawn +CRespawnPoint* CMap::GetFirstRespawn( ) +{ + CRespawnPoint* respawn = NULL; + for(UINT i=0;iclientid == id ) + return PlayerList.at(i); + } + return NULL; +} + +// Search a client by charid +CPlayer* CMap::GetCharIDInMap( UINT id ) +{ + for(UINT i=0;iCharInfo->charid == id ) + return PlayerList.at(i); + } + return NULL; +} + +// Search a monster by clientid +CMonster* CMap::GetMonsterInMap( UINT id ) +{ + for(UINT i=0;iclientid == id ) + return MonsterList.at(i); + } + return NULL; +} + +CDrop* CMap::GetDropInMap( UINT id ) +{ + for(UINT i=0;iclientid == id ) + return DropsList.at(i); + } + return NULL; +} + +CNPC* CMap::GetNPCInMap( UINT id ) +{ + for(UINT i=0;iclientid == id ) + return NPCList.at(i); + } + return NULL; +} + +void CMap::UpdateTime( ) +{ + if(id == 0) return; + UINT etime = (UINT)GServer->round((clock() - LastUpdate)/1000); + if(etime < 10)return; + MapTime += (UINT)floor((double)(etime/10)); + int Hourlength = int(0xffffffff / 24); + int Minutelength = int(Hourlength / 60); + if(MapTime == 0xffffffff) + { + MapTime = 0; + Date ++; + } + ZoneTime = int(MapTime / 17895697); + + if(dayperiod == 0) + dayperiod = 1; + UINT ctime = MapTime%dayperiod; + if(ctime >= morningtime && ctime < daytime) + { + CurrentTime = MORNING; + } + else + if(ctime >= daytime && ctime < eveningtime) + { + CurrentTime = DAY; + } + else + if(ctime >= eveningtime && ctime < nighttime) + { + CurrentTime = EVENING; + } + else + if(ctime >= nighttime) + { + CurrentTime = NIGHT; + } + hour = int(MapTime / Hourlength); + minute = int((MapTime - (hour * Hourlength)) / Minutelength); + LastUpdate = clock(); +} + +// Clean Drops +void CMap::CleanDrops( ) +{ + for(UINT j=0;jdroptime >= 50 ) + DeleteDrop( thisdrop ); + } +} + +// Respawn a Monster +void CMap::RespawnMonster( ) +{ + if(GServer->Config.SpawnType == 1) + { + /*for(UINT k=0;krespawntime<2)thisspawn->respawntime=2; + clock_t rtime = clock() - thisspawn->lastRespawnTime; + if((rtime > thisspawn->respawntime*CLOCKS_PER_SEC && thisspawn->amonmax)|| thisspawn->amonmin ) + { + fPoint position = GServer->RandInPoly( thisspawn->points, thisspawn->pcount ); + AddMonster( thisspawn->montype, position, 0, thisspawn->mobdrop, thisspawn->mapdrop, thisspawn->id ); + thisspawn->lastRespawnTime = clock(); + } + }*/ + //PY new spawn code + for(UINT k=0;klastRespawnTime; + if(thisspawn->limit == 1 && IsNight()) //spawn limit is set to day only + continue; + if(thisspawn->RefVar != 0 && thisspawn->RefVal != 0) //only allow this spawn if the correct ObjVar is set to non zero + { + if(GServer->ObjVar[thisspawn->RefVar][0] == thisspawn->RefVal) + continue; + } + if(thisspawn->limit == 2 && !IsNight()) //spawn limit is set to night only + continue; + switch (thisspawn->type) + { + case 0: + // no spawn. this spawn area is inactive + break; + case 1: //normal spawn + if(rtime > (thisspawn->respawntime * CLOCKS_PER_SEC) && thisspawn->amon < thisspawn->max) + { + fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius ); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + thisspawn->lastRespawnTime = clock(); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + thisspawn->triggercount = 0; + } + break; + case 2: //normal but fill the spawn + if(rtime > (thisspawn->respawntime * CLOCKS_PER_SEC) && thisspawn->amon < thisspawn->max) + { + for(int i=1;imax;i++) + { + fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius ); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + } + } + break; + case 3: //cascade spawn + if(thisspawn->triggercount > thisspawn->triggeramount && thisspawn->amon < thisspawn->max) + { + fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius ); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + thisspawn->lastRespawnTime = clock(); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + thisspawn->triggercount = 0; + } + break; + case 4: //cascade to max + if(thisspawn->triggercount > thisspawn->triggeramount && thisspawn->amon < thisspawn->max) + { + for(int i=1;imax;i++) + { + fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius ); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + } + thisspawn->lastRespawnTime = clock(); + thisspawn->triggercount = 0; + } + break; + case 5: //sub spawns. needs to trigger from a specific montype + if(rtime > (thisspawn->respawntime * CLOCKS_PER_SEC) && thisspawn->amon < thisspawn->max) + { + //find if any of the trigger mobs exist + for(UINT j=0;jmontype == thisspawn->triggertype && (thisspawn->spawnkey == 4 || thisspawn->triggercount >= thisspawn->triggeramount)) + if(monster->montype == thisspawn->triggertype) + { + //found the right monster. now spawn the servant mob in a circle around it + fPoint position = GServer->RandInCircle(monster->Position->current,5); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + thisspawn->triggercount = 0; + thisspawn->lastRespawnTime = clock(); + } + } + } + break; + case 6: + if(rtime > (thisspawn->respawntime * CLOCKS_PER_SEC) && thisspawn->amon < thisspawn->max) + { + //find if any of the trigger mobs exist + for(UINT j=0;jmontype == thisspawn->triggertype && (thisspawn->spawnkey == 4 || thisspawn->triggercount >= thisspawn->triggeramount)) + if(monster->montype == thisspawn->triggertype) + { + //found the right monster. now spawn the servant mobs around it up to the spawn max + for(int i=thisspawn->amon;imax;i++) + { + fPoint position = GServer->RandInCircle(monster->Position->current,5); + UINT aggro = thisspawn->aggressive; + AddMonster( thisspawn->montype, position, 0, thisspawn->id, aggro, false, thisspawn->limit ); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 1); // update the counters + } + thisspawn->lastRespawnTime = clock(); + thisspawn->triggercount = 0; + } + } + } + break; + case 7: //TDEF spawn. Spawns thisspawn->max monsters one by one using triggercount to track them + if(rtime > (thisspawn->respawntime * CLOCKS_PER_SEC) && thisspawn->triggercount < thisspawn->max) + { + fPoint position = GServer->RandInCircle( thisspawn->point, thisspawn->radius ); + AddMonster( thisspawn->montype, position, 0, thisspawn->id, 0, true, 0 ); + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 3); // update the counters + thisspawn->lastRespawnTime = clock(); + } + break; + default: + break; + } + } + } + if(GServer->Config.SpawnType == 0) + { + for (UINT j = 0; j < MobGroupList.size(); j++) + { + CMobGroup* thisgroup = MobGroupList.at(j); + clock_t rtime = clock() - thisgroup->lastRespawnTime; + if (rtime < thisgroup->respawntime*CLOCKS_PER_SEC || thisgroup->active >= thisgroup->limit) + continue; + for (UINT k = thisgroup->active; k < thisgroup->limit; k++) + { + CMob *thismob; + if (thisgroup->tacMobs.size() > 0 && thisgroup->basicKills >= thisgroup->tacticalpoints) + { + thismob = thisgroup->tacMobs.at(thisgroup->curTac); + thisgroup->basicKills = 0; + thisgroup->curTac++; + if (thisgroup->curTac >= thisgroup->tacMobs.size()) thisgroup->curTac = 0; + } else { + thismob = thisgroup->basicMobs.at(thisgroup->curBasic); + thisgroup->curBasic++; + if (thisgroup->curBasic >= thisgroup->basicMobs.size()) thisgroup->curBasic = 0; + } + for (UINT i = 0; i < thismob->amount; i++) + { + fPoint position = GServer->RandInCircle( thisgroup->point, thisgroup->range ); + AddMonster( thismob->mobId, position, 0, thisgroup->id, 1 ); + } + } + } + } +} + +//update the trigger counts in cascade spawns +void CMap::UpdateSpawnCounter( UINT montype, UINT spawnid, UINT key ) +{ + if(key != 0) //kick back if key is undefined + { + for(UINT k=0;ktypeoftrigger) + { + case 0: //no trigger + continue; + break; + case 1: //at any location + //specific monster spawns or dies or is attacked anywhere in the current map + if(thisspawn->triggertype == montype && thisspawn->spawnkey == key) + { + thisspawn->triggercount++; + } + case 2: //at a specific spawn id + //specific monster spawns or dies or is attacked at this specific spawn area + if(thisspawn->triggertype == spawnid && thisspawn->spawnkey == key) + { + thisspawn->triggercount++; + } + break; + case 3: //towerdef + thisspawn->triggercount++; + break; + default: + break; + } + } + } +} + +// Return true if is night +bool CMap::IsNight( ) +{ + if(CurrentTime==NIGHT) + return true; + return false; +} + +CCharacter* CMap::GetCharInMap( unsigned int id ) +{ + CMonster* monster = GetMonsterInMap( id ); + //Log(MSG_DEBUG, "getcharinmap %i", id); + if(monster != NULL) + { + //Log(MSG_DEBUG, "Monster found type = %i", monster->montype); + return (CCharacter*) monster; + } + CPlayer* player = GetPlayerInMap( id ); + if(player != NULL) + return (CCharacter*) player; + return NULL; +} diff --git a/Src/World Server/monsterfunctions.cpp b/Src/World Server/monsterfunctions.cpp new file mode 100644 index 0000000..14befe1 --- /dev/null +++ b/Src/World Server/monsterfunctions.cpp @@ -0,0 +1,151 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldmonster.h" + +// check is a player is in range +bool CMonster::PlayerInRange() +{ + CMap* map = GServer->MapList.Index[Position->Map]; + for(int i=0; iPlayerList.size(); i++) + { + CPlayer* thisclient = map->PlayerList.at(i); + if(GServer->IsVisible(thisclient, this)) return true; + } + return false; +} + +// get the near player +CPlayer* CMonster::GetNearPlayer( UINT mdist ) +{ + CPlayer* thisplayer = NULL; + float distance = 0xffff; + for(UINT i=0;iMapList.Index[Position->Map]->PlayerList.size();i++) + { + CPlayer* thisclient = GServer->MapList.Index[Position->Map]->PlayerList.at(i); + float tempdist = GServer->distance( Position->current, thisclient->Position->current ); + if(tempdistmdist) + return NULL; // near player is too far + return thisplayer; +} + +// return true if is a ghost +bool CMonster::IsGhost( ) +{ + // candle host, elec ghost, yigore ghost + if((montype>710 && montype<751) || (montype>679 && montype<693)) + return true; + return false; +} + +// return true if is a ghost seed +bool CMonster::IsGhostSeed( ) +{ + if((montype>700 && montype<711) && montype!=703) + return true; + return false; +} + +// return true if current monster can move +bool CMonster::CanMove( ) +{ + if((montype<41 || montype>45) && (montype<325 || montype>329) && montype!=659 && (montype<771 || montype>810 ) && montype!=992 && (montype<1474 || montype>1489)) + return true; + return false; +} + +// return true if this monster is ready to move +bool CMonster::ReadyToMove( ) +{ + return true; +} + +// return a player pointers +CPlayer* CMonster::GetOwner( ) +{ + if(!IsSummon( )) return NULL; + CPlayer* ownerclient = GServer->GetClientByID( owner, Position->Map ); + return ownerclient; +} + +bool CMonster::IsMonster( ) +{ + return true; +} + +void CMonster::AddDamage( CCharacter* enemy, long int hitpower) +{ + int MaxDamage = Stats->MaxHP * 2; + //Log(MSG_DEBUG,"Max Damage = %i",MaxDamage); + CPlayer* player = NULL; + if(enemy->IsMonster( )) + { + CMonster* monster = (CMonster*) enemy; + if(!enemy->IsSummon( )) + return; + player = monster->GetOwner( ); + if(player == NULL) + return; + } + else + { + player = (CPlayer*) enemy; + } + if(MonsterDrop->firsthit == 0 && hitpower > 0) + { + MonsterDrop->firsthit = player->CharInfo->charid; + MonsterDrop->firstlevel = player->Stats->Level; + if( player->Party->party != NULL ) + thisparty = player->Party->party; + else + thisparty = NULL; + } + for(UINT i=0;icharid == player->CharInfo->charid) + { + if(hitpower > MaxDamage) + hitpower = MaxDamage; + thisplayer->damage += hitpower; + if(thisplayer->damage > MaxDamage) + thisplayer->damage = MaxDamage; + return; + } + } + MonsterDamage* newplayer = new MonsterDamage; + newplayer->charid = player->CharInfo->charid; + if( hitpower > MaxDamage ) + hitpower = MaxDamage; + newplayer->damage = hitpower; + PlayersDamage.push_back( newplayer ); + //Log(MSG_DEBUG,"Player Damage added= %i",hitpower); +} + +CDrop* CMonster::GetDrop( ) +{ + return GServer->GetNewDrop( this ); +} diff --git a/Src/World Server/party.h b/Src/World Server/party.h new file mode 100644 index 0000000..f0084e3 --- /dev/null +++ b/Src/World Server/party.h @@ -0,0 +1,52 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef __ROSE_PARY__ +#define __ROSE_PARY__ +#include "worldserver.h" +#include "datatypes.h" + +class CParty +{ + public: + CParty( ); + ~CParty( ); + + CPlayer* GetMember( UINT id ); + CPlayer* GetMemberByCharID( UINT charid ); + void SendToMembers( CPacket* pak, CPlayer* exclient=NULL ); + void SendToVisible( CPacket* pak, CPlayer* thisclient ); + bool AddPlayer( CPlayer* player ); + bool RemovePlayer( CPlayer* player ); + bool counted; //flag to help assign party exp + + UINT Pending_Exp; //temporary value to hold exp during calculations + UINT PartyLevel; + UINT Exp; + UINT Option; + UINT LastItem; + UINT LastETC; + UINT LastZulies; + UINT Capacity; + vector Members; + CPartyExp* PartyExp; +}; + +#endif diff --git a/Src/World Server/partyfunctions.cpp b/Src/World Server/partyfunctions.cpp new file mode 100644 index 0000000..d2ff0bc --- /dev/null +++ b/Src/World Server/partyfunctions.cpp @@ -0,0 +1,120 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +#include "party.h" + +CParty::CParty( ) +{ + PartyLevel = 1; + Exp = 0; + Option =0; + LastItem = 0; + LastETC = 0; + LastZulies = 0; + Capacity = 5; + Members.clear(); + PartyExp = new CPartyExp; +} + +CParty::~CParty( ) +{ + delete PartyExp; +} + +CPlayer* CParty::GetMember( UINT id ) +{ + for(UINT i=0;iclientid == id) + return member; + } + return NULL; +} + +CPlayer* CParty::GetMemberByCharID( UINT id ) +{ + for(UINT i=0;iCharInfo->charid == id) + return member; + } + return NULL; +} + +void CParty::SendToMembers( CPacket* pak, CPlayer* explayer ) +{ + for(UINT i=0;iclient->SendPacketCpy( pak ); + } +} + +void CParty::SendToVisible( CPacket* pak, CPlayer* thisclient ) +{ + for(UINT i=0;iIsVisible( thisclient, member )) + member->client->SendPacketCpy( pak ); + } +} + +bool CParty::AddPlayer( CPlayer* player ) +{ + player->Party->party = this; + Members.push_back( player ); + return true; +} + +bool CParty::RemovePlayer( CPlayer* player ) +{ + for(UINT i=0;iParty->IsMaster = true; + player->Party->party = NULL; + return true; + } + } + return false; +} + + + + + diff --git a/Src/World Server/player.cpp b/Src/World Server/player.cpp new file mode 100644 index 0000000..6b6d272 --- /dev/null +++ b/Src/World Server/player.cpp @@ -0,0 +1,519 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "player.h" +#include "worldserver.h" + +CPlayer::CPlayer( CClientSocket* CLIENT ) +{ + client = CLIENT; + // USED ITEM + UsedItem = new USEDITEM; + assert(UsedItem); + UsedItem->lastRegTime = 0; + UsedItem->usevalue = 0; + UsedItem->usetype = 0; + UsedItem->userate = 0; + UsedItem->used = 0; + // CHARINFO + CharInfo = new INFO; + assert(CharInfo); + memset( &CharInfo->charname, '\0', 17 ); + CharInfo->charid = 0; + CharInfo->Sex = 0; + CharInfo->Face = 0; + CharInfo->Hair = 0; + CharInfo->Exp = 0; + CharInfo->Job = 0; + CharInfo->Zulies = 0; + CharInfo->Storage_Zulies = 0; + CharInfo->LastGlobal = 0; + CharInfo->StatPoints = 0; + CharInfo->SkillPoints = 0; + CharInfo->stamina = 0; + + // RIDE + Ride = new RIDE; + assert(Ride); + Ride->Drive = false; + Ride->Ride = false; + Ride->charid = 0; + // TRADE + Trade = new TRADE; + assert(Trade); + for(int i=0;i<10;i++) + Trade->trade_itemid[i] = 0; + for(int i=0;i<11;i++) + Trade->trade_count[i] = 0; + Trade->trade_status = 0; + Trade->trade_target = 0; + //PARTY + Party = new PARTY; + assert(Party); + Party->party = NULL; + Party->IsMaster = true; + // SHOP + Shop = new SHOP; + assert(Shop); + Shop->open = false; + memset( &Shop->name, '\0',64 ); + for(int i=0;i<30;i++) + { + ClearItem(Shop->BuyingList[i].item); + Shop->BuyingList[i].slot = 0; + Shop->BuyingList[i].count = 0; + Shop->BuyingList[i].price = 0; + Shop->SellingList[i].slot = 0; + Shop->SellingList[i].count = 0; + Shop->SellingList[i].price = 0; + } + Shop->Buying = 0; + Shop->Selling = 0; + Shop->ShopType = 0; + // SESSION + Session = new SESSION; + assert(Session); + Session->userid = 0; + memset( &Session->username, '\0', 17 ); + memset( &Session->password, '\0', 33 ); + Session->accesslevel = 0; + Session->isLoggedIn = false; + Session->inGame = false; + // Inventory / storage + for(unsigned int i=0;iclanid = 0; + Clan->clanrank = 0; + Clan->grade = 0; + Clan->CP = 0; + Clan->logo = 0; + Clan->back = 0; + memset( &Clan->clanname, '\0', 17 ); + //union + Union_s = new UNION_s; + // ATTRIBUTES + Attr = new ATTRIBUTES; + assert(Attr); + Attr->Str = 0; + Attr->Dex = 0; + Attr->Int = 0; + Attr->Con = 0; + Attr->Cha = 0; + Attr->Sen = 0; + Attr->Estr = 0; + Attr->Edex = 0; + Attr->Eint = 0; + Attr->Econ = 0; + Attr->Echa = 0; + Attr->Esen = 0; + + CharType = TPLAYER; + questdebug = false; + Session->codedebug = false; + Saved = false; + isInvisibleMode = false; + Fairy = false; + //FairyTime = 0; + nstorageitems = 0; + p_skills = 0; + + for(int i=0;iRide && !Ride->Drive ) //I'm a passenger in somebody else's cart + { + CPlayer* otherclient = GServer->GetClientByCID( Ride->charid, Position->Map ); //find the cart's driver + if( otherclient != NULL ) // found the driver + { + if( otherclient->Status->Stance != 0x04 ) //not driving any more + { + otherclient->Ride->Drive = false; + otherclient->Ride->Ride = false; + otherclient->Ride->charid = 0; + Ride->Drive = false; + Ride->Ride = false; + Ride->charid = 0; + Position->destiny = Position->current; + Log(MSG_DEBUG,"(UpdateValues) not in cart any more. Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + } + else //still driving + { + Position->current = otherclient->Position->current; + Position->destiny = otherclient->Position->destiny; + Log(MSG_DEBUG,"(UpdateValues) Passenger in Cart Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + Position->lastMoveTime = otherclient->Position->lastMoveTime; + return false; // will not update the player position + } + } + else //no driver found so reset values to NOT be a passenger any more + { + Ride->Drive = false; + Ride->Ride = false; + Ride->charid = 0; + Position->destiny = Position->current; + Log(MSG_DEBUG,"(UpdateValues) No other client. Destiny set to current position X: %f Y: %f.",Position->current.x,Position->current.y); + } + } + return true; +} + +bool CPlayer::RespawnPlayer( CPlayer* thisclient ) +{ + CMap* map = GServer->MapList.Index[thisclient->Position->Map]; + CRespawnPoint* thisrespawn = NULL; + thisclient->Stats->HP = thisclient->Stats->MaxHP * 10 / 100; + byte respawn = thisclient->Session->RespawnChoice; + Log( MSG_INFO, "RespawnZones choice: %i", respawn ); + //1 - Current / 2 - save point/dungeon entrance + if(respawn == 1 || map->dungeon) + { + if (respawn != 1) // This is a dungeon map, spawn at start + thisrespawn = map->GetStartRespawn( ); + else + thisrespawn = map->GetNearRespawn( thisclient ); + if(thisrespawn!=NULL) + { + map->TeleportPlayer( thisclient, thisrespawn->dest, false ); + } + } + else // This is a field, spawn in save town + { + thisrespawn = GServer->GetRespawnByMap( thisclient->Position->saved ); + if(thisrespawn != NULL) + { + fPoint coord = thisrespawn->dest; + GServer->MapList.Index[thisclient->Position->saved]->TeleportPlayer( thisclient, coord, false ); + } + } + + if(thisrespawn == NULL) + { + fPoint coord; + coord.x = 5200; + coord.y = 5200; + GServer->MapList.Index[2]->TeleportPlayer( thisclient, coord, false ); + } + Log( MSG_INFO, "Player teleported successfully" ); + for(unsigned int i=0;i<30;i++) + { // Clean Buffs + thisclient->MagicStatus[i].Duration = 0; + } + Log( MSG_INFO, "Buffs Cleaned" ); + return true; +} + +// Spawn Another User on the Screen +bool CPlayer::SpawnToPlayer( CPlayer* player, CPlayer* otherclient ) +{ +/* if( Party->party!=NULL && Party->party == player->Party->party) + { + BEGINPACKET( pak, 0x7d5 ); + ADDDWORD ( pak, CharInfo->charid ); + ADDWORD ( pak, clientid ); + ADDWORD ( pak, GetMaxHP( ) ); + ADDWORD ( pak, Stats->HP ); + ADDDWORD ( pak, 0x01000000 ); + ADDDWORD ( pak, 0x0000000f ); + ADDWORD ( pak, 0x1388 ); + player->client->SendPacket( &pak ); + } +*/ +/* + if( Ride->Ride ) + { + CPlayer* rideclient = GServer->GetClientByCID( Ride->charid, Position->Map ); + if(rideclient==NULL) + { + Ride->Ride = false; + Ride->Drive= false; + Ride->charid = 0; + return true; + } + if( GServer->IsVisible( player, rideclient ) || player->CharInfo->charid == rideclient->CharInfo->charid ) + { + BEGINPACKET( pak, 0x796 ); + if( Ride->Drive ) + { + ADDWORD ( pak, rideclient->clientid ); + ADDFLOAT ( pak, rideclient->Position->current.x*100 ); + ADDFLOAT ( pak, rideclient->Position->current.y*100 ); + } + else + { + ADDWORD ( pak, clientid ); + ADDFLOAT ( pak, Position->current.x*100 ); + ADDFLOAT ( pak, Position->current.y*100 ); + } + ADDWORD ( pak, 0x0000 ); + player->client->SendPacket( &pak ); + + RESETPACKET( pak, 0x7dd ); + ADDBYTE ( pak, 0x02 ); + if( rideclient->Ride->Drive ) + { + ADDWORD ( pak, rideclient->clientid ); + ADDWORD ( pak, clientid ); + } + else + { + ADDWORD ( pak, clientid ); + ADDWORD ( pak, rideclient->clientid ); + } + player->client->SendPacket( &pak ); + } + } + return true; +*/ + + BEGINPACKET( pak, 0x793 ); + ADDWORD( pak, clientid); // USER ID + ADDFLOAT( pak, Position->current.x*100 ); // POS X + ADDFLOAT( pak, Position->current.y*100 ); // POS Y + ADDFLOAT( pak, Position->destiny.x*100 ); // GOING TO X + ADDFLOAT( pak, Position->destiny.y*100 ); // GOINT TO Y + if(Shop->open) + { + ADDWORD( pak, 0x000b ); + ADDWORD( pak, 0x0000 ); + } + else + if(Stats->HP <= 0) + { + ADDWORD( pak, 0x0003 ); + ADDWORD( pak, 0x0000 ); + } + else + // if(Status->Stance == SITTING) + // { + // ADDWORD( pak, 0x000a ); + // ADDWORD( pak, 0x0000 ); + // } + // else + if (Battle->atktarget!=0) + { + ADDWORD( pak, 0x0002 ); + ADDWORD( pak, Battle->atktarget ); + } + else + if(Position->destiny.x != Position->current.x || Position->destiny.y != Position->current.y) + { + ADDWORD( pak, 0x0001 ); + ADDWORD( pak, 0x0000 ); + } + else + { + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + } + ADDBYTE( pak, Ride->Drive == true?2:1); + ADDWORD( pak, Stats->HP); + ADDBYTE( pak, 0x00); + //ADDWORD( pak, 0x6401 ); //?? + //ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0200 ); + ADDWORD( pak, 0x0000 ); + ADDBYTE( pak, 0x00 ); + ADDDWORD( pak, GServer->BuildBuffs( this ) ); // BUFFS + ADDBYTE( pak, CharInfo->Sex ); // GENDER + ADDWORD( pak, Stats->Mspd_base ); // WALK SPEED MAYBE? + ADDBYTE( pak, 0x00 ); // ?? + ADDBYTE( pak, 0x00 ); // ?? + ADDBYTE( pak, 0x01 ); // ?? + ADDDWORD( pak, CharInfo->Face ); // FACE TYPE + ADDDWORD( pak, CharInfo->Hair ); // HAIR TYPE + ADDDWORD( pak, GServer->BuildItemShow( items[2] )); // CAP + ADDDWORD( pak, GServer->BuildItemShow( items[3] )); // BODY + ADDDWORD( pak, GServer->BuildItemShow( items[5] )); // GLOVES + ADDDWORD( pak, GServer->BuildItemShow( items[6] )); // BOOTS + ADDDWORD( pak, GServer->BuildItemShow( items[1] )); // FACE + ADDDWORD( pak, GServer->BuildItemShow( items[4] )); // BACK + ADDDWORD( pak, GServer->BuildItemShow( items[7] )); // WEAPON + ADDDWORD( pak, GServer->BuildItemShow( items[8] )); // SUBWEAPON + ADDWORD( pak, GServer->BuildItemHead(items[132] )); + ADDWORD( pak, GServer->BuildItemHead(items[133] )); + ADDWORD( pak, GServer->BuildItemHead(items[134] )); + ADDWORD( pak, CharInfo->Job ); + ADDBYTE( pak, Stats->Level); + if(items[135].itemnum>0){ + ADDWORD( pak, (items[135].itemnum +1024));} + else{ + ADDWORD( pak, 0x0000 );} + ADDWORD( pak, 0x0000 ); + if(items[136].itemnum>0){ + ADDWORD( pak, (items[136].itemnum +1024));} + else{ + ADDWORD( pak, 0x0000 );} + ADDWORD( pak, 0x0000 ); + if(items[137].itemnum>0){ + ADDWORD( pak, (items[137].itemnum +1024));} + else{ + ADDWORD( pak, 0x0000 );} + ADDWORD( pak, 0x0000 ); + if(items[138].itemnum>0){ + ADDWORD( pak, (items[138].itemnum +1024));} + else{ + ADDWORD( pak, 0x0000 );} + ADDWORD( pak, 0x0000 ); + + ADDWORD( pak, (Stats->HP<=0)?0x0:0xCDCD ); + if(Shop->open) + { + ADDWORD( pak, 0x0002 ); + } + else + { + ADDWORD( pak, 0x0000 ); + } + ADDWORD( pak, 0x0000 ); + ADDDWORD( pak, 0x00000000 ); + ADDSTRING( pak, CharInfo->charname ); + ADDBYTE( pak, 0x00 ); + if(Status->Dash_up != 0xff) + ADDWORD ( pak, (MagicStatus[Status->Dash_up].Value)); + if(Status->Haste_up!= 0xff) + ADDWORD ( pak, (MagicStatus[Status->Haste_up].Value)); + if(Shop->open) + { + ADDBYTE( pak, Shop->ShopType); + ADDBYTE( pak, 0x00); + ADDSTRING(pak, Shop->name); + ADDBYTE( pak, 0x00); + } + if(Clan->clanid!=0) + { + ADDWORD ( pak, Clan->clanid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, Clan->back ); + ADDWORD ( pak, Clan->logo ); + ADDBYTE ( pak, Clan->grade ); + ADDBYTE ( pak, Clan->clanrank ); + ADDSTRING ( pak, Clan->clanname ); + } + ADDWORD( pak, 0x0000 ); + player->client->SendPacket(&pak); + return true; +} +// ----------------------------------------------------------------------------------------- +// Returns new skill slot //bskill askill pskill +// ----------------------------------------------------------------------------------------- + +int CPlayer::GetNewSkillSlot(char skilltype) +{ + switch (skilltype) + { + case 0: + for(char s=0;s<30;s++) + { + if(bskills[s]==0)return s; + } + case 1: + for(char t=0;t<30;t++) + { + if(askill[t]==0)return t; + } + case 2: + for(char u=0;u<30;u++) + { + if(pskill[u]==0)return u; + } + default: + break; + } + return 0xffff; +} + +void CPlayer::onCheckForMonsterQuests_event (CMonster * Monster) +{ + // We had First hit, and the monster was killed. + for (int q = 0; q < 10; q++) + { + // Give Quest Item + if (this->quest.quests[q].QuestID != 0) + { + //Log(MSG_DEBUG,"Giving quest reward item for quest %i Killed monster type %i",thisclient->quest.quests[q].QuestID,thismon->montype); + + //PY: thismon->thisnpc->die_quest contains the hash needed to complete the trigger + int success = this->ExecuteQuestTrigger (Monster->thisnpc->die_quest); + if (success == QUEST_SUCCESS) + { + CPacket pakout (0x730); + pakout.AddByte ((byte)success); + pakout.AddByte (0); + pakout.AddDWord (Monster->thisnpc->die_quest); + this->client->SendPacket (&pakout); + } + break; + } + } +} + diff --git a/Src/World Server/player.h b/Src/World Server/player.h new file mode 100644 index 0000000..a328103 --- /dev/null +++ b/Src/World Server/player.h @@ -0,0 +1,170 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef _ROSE_PLAYER_ +#define _ROSE_PLAYER_ +#include "character.h" + +/// Forward Declarations +class CMonster; // Forward declare +class CPlayer: public CCharacter +{ + + public: + CPlayer( CClientSocket* ); + ~CPlayer( ); + + // + CClientSocket* client; + bool questdebug;// Send PM's with quest information + bool Saved;// already save this player?(used ondisconnect/returnchar) + bool isInvisibleMode; + + // Fairy + bool Fairy; + UINT FairyListIndex; + + // variables + USEDITEM* UsedItem; + INFO* CharInfo; + RIDE* Ride; + TRADE* Trade; + PARTY* Party; + SHOP* Shop; + SESSION* Session; + CLAN* Clan; + UNION_s* Union_s; + ATTRIBUTES* Attr; + CRespawnPoints GMRespawnPoints; + + // Inventory/storage + CItem storageitems[MAX_STORAGE]; + unsigned int nstorageitems; + CItem items[MAX_INVENTORY]; + + // skills/quickbar + SKILLS cskills[MAX_SKILL]; + UINT bskills[MAX_BASICSKILL]; + UINT quickbar[MAX_QUICKBAR]; + unsigned short askill[30]; + unsigned char askilllvl[30]; + unsigned short pskill[30]; + unsigned char pskilllvl[30]; + int p_skills; + + // ExJam Quest Code - Info + DWORD ActiveQuest; //active quest # + int CheckQuest; // The currently checked quest. + strings TriggerName[10]; + SQuestData quest; // Character's quest information + int NPCvar; // holds reference value for ObjVar global structure + int RefVar; // holds reference value for second part of ObjVar structure + + // Time + clock_t lastRegenTime; + clock_t lastSaveTime; + clock_t lastGG; + + // Visible Lists + vector VisiblePlayers; // Visible players + vector VisibleDrops; // Visible drops + vector VisibleMonsters; // Visible monsters + vector VisibleNPCs; // Visible npcs + + // Battle Events + void onCheckForMonsterQuests_event (CMonster* Monster); // Called if the character had first hit and monster was killed. + + // Functions + bool CheckPlayerLevelUP( ); + bool canUseSkill(CSkills* skill); + void SetStats( ); + bool GetPlayerInfo( ); + bool IsMonster( ); + bool CleanPlayerVector( ); + bool loaddata( ); + void savedata( ); + void quicksave( ); + void saveinventory( ); + void loadinventory( ); + void savestorage( ); + void SaveBackupStorage( int logstate ); + void loadstorage( ); + UINT GetNewStorageItemSlot( CItem thisitem ); + UINT GetNewItemSlot( CItem thisitem ); + bool ClearObject( unsigned int otherclientid ); + void RestartPlayerVal( ); + bool Regeneration( ); + bool PlayerHeal( ); + bool VisiblityList( ); + bool ChangeDialog( unsigned int npc, unsigned int dialog, unsigned int eventid = 0 ); + bool SpawnToPlayer( CPlayer* player, CPlayer* otherclient ); + bool RespawnPlayer(CPlayer* thisclient); + bool UpdateValues( ); + void ReduceABC( ); + CParty* GetParty( ); + CLAN* GetClan( ); + unsigned int AddItem( CItem item ); + unsigned int Getnstorageitems( CPlayer* thisclient ); + void UpdateInventory(unsigned int slot1, unsigned int slot2=0xffff ); + + // ExJam Quest Code + void savequests( CPlayer* thisclient ); + SQuest* GetActiveQuest( ); + int GetQuestVar(short nVarType, short nVarNO); + void SetQuestVar(short nVarType, short nVarNO, short nValue); + int ExecuteQuestTrigger(dword hash); + //PY Quest stuff + void SetQuestFlag(dword flagid, bool value); + void GetQuestFlag(dword falgid); + + + // Player Stats + unsigned int GetAttackPower( ); + unsigned int GetDefense( ); + unsigned int GetDodge( ); + unsigned int GetXPRate( ); + unsigned int GetItemDropRate( ); + unsigned int GetItemDropCountRate( ); + unsigned int GetZulyDropRate( ); + unsigned int GetGreyDrops( ); + unsigned int GetAccury( ); + unsigned int GetMagicDefense( ); + unsigned int GetMagicAttack( ); + unsigned int GetCritical( ); + unsigned int GetAttackSpeed( ); + unsigned int GetMoveSpeed( ); + unsigned int GetMaxHP( ); + unsigned int GetMaxMP( ); + float GetAttackDistance( ); + unsigned int GetHPRegenAmount( ); + unsigned int GetMPRegenAmount( ); + unsigned int GetMaxWeight( ); + unsigned int GetCurrentWeight( ); + unsigned int GetMPReduction( ); + unsigned int GetMaxSummonGauge( ); + void GetExtraStats( ); + unsigned int GetLevelEXP( ); + unsigned int GetInt( ); + // Skill + int GetNewSkillSlot(char skilltype); + void takeSkillCost( CSkills* skill ); +}; + +#endif diff --git a/Src/World Server/playerdata.cpp b/Src/World Server/playerdata.cpp new file mode 100644 index 0000000..a8ee690 --- /dev/null +++ b/Src/World Server/playerdata.cpp @@ -0,0 +1,1379 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "player.h" +#include "worldserver.h" + +unsigned char GetCharVal( char mychar ) +{ + switch( mychar ) + { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + case 'A': return 10; + case 'B': return 11; + case 'C': return 12; + case 'D': return 13; + case 'E': return 14; + case 'F': return 15; + default: + break; + } + return 0; +} + +void EncodeBinary( char* encoded, unsigned char* data, unsigned datalen ) +{ + for( unsigned short i = 0, j = 0; i < datalen; i++ ) + { + encoded[ j++ ] = "0123456789ABCDEF"[ data[ i ] >> 4 ]; + encoded[ j++ ] = "0123456789ABCDEF"[ data[ i ] & 0xf ]; + } + encoded[ datalen * 2 ] = 0; +} + +void DecodeBinary( char* encoded, unsigned char* data ) +{ + unsigned curbit = 0; + for( unsigned i = 0; i < (unsigned)strlen(encoded); i+=2 ) + { + data[curbit] = GetCharVal( encoded[i] ) << 4; + data[curbit] += GetCharVal( encoded[i+1] ); + curbit++; + } +} + +// Load this characters userdata from the database +bool CPlayer::loaddata( ) +{ + //Log(MSG_INFO,"Loading character data"); + MYSQL_ROW row; + MYSQL_RES *result = GServer->DB->QStore("SELECT level,face,hairStyle,sex,classid,zuly,str,dex,_int,con,cha,sen,curHp,curMp,id,statp,skillp,exp,stamina,quickbar,basic_skills,askill,askilllvl,pskill,pskilllvl,respawnid,clanid,clan_rank,townid,isgm,isdev FROM characters WHERE char_name='%s'", CharInfo->charname); + if(result==NULL) return false; + if(mysql_num_rows(result)!=1) + { + Log( MSG_WARNING, "Number of user with charname '%s' is %i", CharInfo->charname,mysql_num_rows(result)); + return false; + } + row = mysql_fetch_row(result); + Stats->Level = atoi(row[0]); + CharInfo->Face = atoi(row[1]); + CharInfo->Hair = atoi(row[2]); + CharInfo->Sex = atoi(row[3]); + CharInfo->Job = atoi(row[4]); + CharInfo->Zulies = atol(row[5]); + Attr->Str = atoi(row[6]); + Attr->Dex = atoi(row[7]); + Attr->Int = atoi(row[8]); + Attr->Con = atoi(row[9]); + Attr->Cha = atoi(row[10]); + Attr->Sen = atoi(row[11]); + Stats->HP = atoi(row[12]); + Stats->MP = atoi(row[13]); + CharInfo->charid = atoi(row[14]); + CharInfo->StatPoints = atoi(row[15]); + CharInfo->SkillPoints = atoi(row[16]); + CharInfo->Exp = atoi(row[17]); + CharInfo->stamina = atoi(row[18]); + Position->respawn = atoi(row[25]); + Clan->clanid = atoi(row[26]); + Clan->clanrank = atoi(row[27]); + Position->saved = atoi(row[28]); + Stats->xprate = GetXPRate(); + CharInfo->IsGM = atoi(row[29]); + CharInfo->IsDev = atoi(row[30]); + Log(MSG_DEBUG,"Selected Character %s Class %i GM status: %i DEV status %i", CharInfo->charname, CharInfo->Job, CharInfo->IsGM, CharInfo->IsDev); + p_skills = 0; + for(char i=0;i<32;i++) + { + char* tmp=strtok((i==0?row[19]:NULL), ","); + if (tmp!=NULL) quickbar[i]=atoi(tmp); + else + quickbar[i]=0; + } + + for(char i=0;i<30;i++) + { + char* tmp=strtok((i==0?row[20]:NULL), ","); + if (tmp!=NULL) bskills[i]=atoi(tmp); + else + bskills[i]=0; + + } + for(char i=0;i<30;i++) + { + char* tmp=strtok((i==0?row[21]:NULL), ","); + if (tmp!=NULL) askill[i]=atoi(tmp); + else + askill[i]=0; + } + for(char i=0;i<30;i++) + { + char* tmp=strtok((i==0?row[22]:NULL), ","); + if (tmp!=NULL) askilllvl[i]=atoi(tmp); + else + askilllvl[i]=0; + } + for(char i=0;i<30;i++) + { + char* tmp=strtok((i==0?row[23]:NULL), ","); + if (tmp!=NULL) pskill[i]=atoi(tmp); + else + pskill[i]=0; + } + for(char i=0;i<30;i++) + { + char* tmp=strtok((i==0?row[24]:NULL), ","); + if (tmp!=NULL) pskilllvl[i]=atoi(tmp); + else + pskilllvl[i]=0; + } + + GServer->DB->QFree( ); + CharInfo->Pending_Exp = 0; + Log(MSG_INFO,"Loading union data"); + result = GServer->DB->QStore("SELECT unionid,union1points,union2points,union3points,union4points,union5points,union6points,union7points,union8points,union9points,union10points FROM characters WHERE char_name='%s'", CharInfo->charname); + if(result == NULL) return false; + if(mysql_num_rows(result)!=1) + { + Log( MSG_WARNING, "Number of user with charname '%s' is %i", CharInfo->charname,mysql_num_rows(result)); + return false; + } + row = mysql_fetch_row(result); + for (char points=0; points<11;points++) + { + if(points == 9) + { + Union_s->unionvar[points] = Session->KTPoints; + } + else + { + Union_s->unionvar[points] = atoi(row[points]); + } + } + GServer->DB->QFree( ); + // code to reset players back to the correct faction after using the KTRose shop + if(Union_s->unionvar[0] == 9) + { + //Log(MSG_DEBUG, "UnionVar 7 used to reset player Union from %i to %i",Union_s->unionvar[0],Union_s->unionvar[7] ); + Union_s->unionvar[0] = Union_s->unionvar[7]; + Union_s->unionvar[7] = 0; + } + // PY loadup code + //Log(MSG_INFO,"Loading inventory data"); + result = GServer->DB->QStore("SELECT itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,trans_action FROM charitems WHERE owner=%i", CharInfo->charid); + if(result == NULL) + { + Log(MSG_WARNING, "Something a bit wrong with this query"); + return false; + } + const char *tmpid; + const char *itmp; + int itemid; + while( row = mysql_fetch_row(result) ) + { + tmpid = strtok( row[0] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].itemnum = atoi(itmp); + } + tmpid = strtok( row[1] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].itemtype = atoi(itmp); + } + tmpid = strtok( row[2] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].refine = atoi(itmp); + } + tmpid = strtok( row[3] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].durability = atoi(itmp); + } + tmpid = strtok( row[4] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].lifespan = atoi(itmp); + } + tmpid = strtok( row[5] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + { + items[i].count = atoi(itmp); + //Log(MSG_INFO,"slot: %i itemid: %i type: %i count: %i",i, items[i].itemnum,items[i].itemtype,items[i].count); //PAT items seem to load ok here + } + } + tmpid = strtok( row[6] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + { + items[i].stats = atoi(itmp); + if(items[i].stats >300) + { + items[i].gem = items[i].stats; + } + else + items[i].gem = 0; + } + } + tmpid = strtok( row[7] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].socketed = (atoi(itmp)==1)?true:false; + //just in case there is a discrepency in the database this will fix sockets. + if(items[i].gem != 0) + items[i].socketed = true; + } + tmpid = strtok( row[8] , "|"); + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + items[i].appraised = (atoi(itmp)==1)?true:false; + } + for(unsigned int i=1;i<=MAX_INVENTORY; i++) + { + // first delete bad items + if(items[i].itemnum > GServer->EquipList[items[i].itemtype].STBMax) + { + Log(MSG_INFO,"deleting item in slot: %i itemid: %i type: %i count: %i",i, items[i].itemnum,items[i].itemtype,items[i].count); + //Item number too high + items[i].itemnum = 0; + items[i].itemtype = 0; + items[i].count = 0; + items[i].durability = 0; + + } + // find item type for crafting + if(items[i].itemtype > 0 && items[i].itemtype < 10) + items[i].type = GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->type; + if(items[i].itemtype == 10) + items[i].type = GServer->UseList.Index[items[i].itemnum]->type; + if(items[i].itemtype == 11) + items[i].type = GServer->JemList.Index[items[i].itemnum]->type; + if(items[i].itemtype == 12) + items[i].type = GServer->NaturalList.Index[items[i].itemnum]->type; + if(items[i].itemtype == 14) + items[i].type = GServer->PatList.Index[items[i].itemnum]->type; + //Log(MSG_INFO,"slot: %i itemid: %i type: %i count: %i",i, items[i].itemnum,items[i].itemtype,items[i].count); + } + + } + //PY end + /* + result = GServer->DB->QStore("SELECT itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem FROM items WHERE owner=%i", CharInfo->charid); + if(result==NULL) return false; + while(row = mysql_fetch_row(result)) + { + if(!GServer->IsValidItem( atoi(row[1]), atoi(row[0]) ) || atoi(row[6])==0) + { + Log(MSG_WARNING, "char %s have a invalid or empty item in inventory: %i-%i [%i], this item will be deleted", CharInfo->charname, atoi(row[1]), atoi(row[0]), atoi(row[6]) ); + continue; + } + UINT itemnum = atoi(row[5]); + items[itemnum].itemnum = atoi(row[0]); + items[itemnum].itemtype = atoi(row[1]); + items[itemnum].refine = atoi(row[2]); + items[itemnum].durability = atoi(row[3]); + items[itemnum].lifespan = atoi(row[4]); + items[itemnum].count = atoi(row[6]); + items[itemnum].stats = atoi(row[7]); + items[itemnum].socketed = (atoi(row[8])==1)?true:false; + items[itemnum].appraised = (atoi(row[9])==1)?true:false; + items[itemnum].gem = atoi(row[10])>3999?0:atoi(row[10]); + //extra code to fix bugged gem items + if(items[itemnum].gem > 0) + { + items[itemnum].stats = items[itemnum].gem; + } + // find item type for crafting + if(items[itemnum].itemtype < 10) + items[itemnum].type = GServer->EquipList[items[itemnum].itemtype].Index[items[itemnum].itemnum]->type; + if(items[itemnum].itemtype == 10) + items[itemnum].type = GServer->UseList.Index[items[itemnum].itemnum]->type; + if(items[itemnum].itemtype == 11) + items[itemnum].type = GServer->JemList.Index[items[itemnum].itemnum]->type; + if(items[itemnum].itemtype == 12) + items[itemnum].type = GServer->NaturalList.Index[items[itemnum].itemnum]->type; + if(items[itemnum].itemtype == 14) + items[itemnum].type = GServer->PatList.Index[items[itemnum].itemnum]->type; + + }*/ + //Log( MSG_DEBUG, "loaded inventory info"); + GServer->DB->QFree( ); + + // PY loadup code for storage + //Log(MSG_INFO,"Loading storage data"); + result = GServer->DB->QStore("SELECT itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,trans_action FROM charstorage WHERE owner=%i", Session->userid); + if(result == NULL) + { + Log(MSG_WARNING, "Something a bit wrong with this query"); + return false; + } + tmpid = ""; + itmp = 0; + while( row = mysql_fetch_row(result) ) + { + tmpid = strtok( row[0] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].itemnum = atoi(itmp); + //if (Session->userid == 8) //test code delete later + //{ + // if(i < 4) + // Log(MSG_DEBUG,"Storage item %i loaded. Value = %i",i,atoi(itmp)); + //} + } + tmpid = strtok( row[1] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].itemtype = atoi(itmp); + } + tmpid = strtok( row[2] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].refine = atoi(itmp); + } + tmpid = strtok( row[3] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].durability = atoi(itmp); + } + tmpid = strtok( row[4] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].lifespan = atoi(itmp); + } + tmpid = strtok( row[5] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].count = atoi(itmp); + } + tmpid = strtok( row[6] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + { + storageitems[i].stats = atoi(itmp); + if(storageitems[i].stats > 300) + { + storageitems[i].gem = items[i].stats; + } + else + storageitems[i].gem = 0; + } + } + tmpid = strtok( row[7] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].socketed = (atoi(itmp)==1)?true:false; + //just in case there is a discrepency in the database this will fix sockets. + if(storageitems[i].gem != 0) + storageitems[i].socketed = true; + } + tmpid = strtok( row[8] , "|"); + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + if((itmp = strtok( NULL , "|"))!= NULL) + storageitems[i].appraised = (atoi(itmp)==1)?true:false; + } + for(unsigned int i=0;i<=MAX_STORAGE; i++) + { + // find item type for crafting + if(storageitems[i].itemtype > 0 && storageitems[i].itemtype < 10) + storageitems[i].type = GServer->EquipList[storageitems[i].itemtype].Index[storageitems[i].itemnum]->type; + if(storageitems[i].itemtype == 10) + storageitems[i].type = GServer->UseList.Index[storageitems[i].itemnum]->type; + if(storageitems[i].itemtype == 11) + storageitems[i].type = GServer->JemList.Index[storageitems[i].itemnum]->type; + if(storageitems[i].itemtype == 12) + storageitems[i].type = GServer->NaturalList.Index[storageitems[i].itemnum]->type; + if(storageitems[i].itemtype == 14) + storageitems[i].type = GServer->PatList.Index[storageitems[i].itemnum]->type; + } + + } + //PY end + + /* + //original storage load code + GServer->DB->QFree( ); + Log(MSG_INFO,"Loading storage data"); + result = GServer->DB->QStore("SELECT itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem FROM storage WHERE owner=%i", Session->userid); + if(result == NULL) return false; + nstorageitems = mysql_num_rows(result); + while(row = mysql_fetch_row(result)) + { + if(!GServer->IsValidItem( atoi(row[1]), atoi(row[0]) ) || atoi(row[6])==0) + { + Log(MSG_WARNING, "char %s have a invalid or empty item in storage: %i%i [%i], this item will be deleted", CharInfo->charname, atoi(row[1]), atoi(row[0]), atoi(row[6]) ); + continue; + } + UINT itemnum = atoi(row[5]); + storageitems[itemnum].itemnum = atoi(row[0]); + storageitems[itemnum].itemtype = atoi(row[1]); + storageitems[itemnum].refine = atoi(row[2]); + storageitems[itemnum].durability = atoi(row[3]); + storageitems[itemnum].lifespan = atoi(row[4]); + storageitems[itemnum].count = atoi(row[6]); + storageitems[itemnum].stats = atoi(row[7]); + storageitems[itemnum].socketed = (atoi(row[8])==1)?true:false; + storageitems[itemnum].appraised = (atoi(row[9])==1)?true:false; + storageitems[itemnum].gem = atoi(row[10]); + //extra code to fix bugged gem items + if(storageitems[itemnum].gem > 0) + { + storageitems[itemnum].stats = storageitems[itemnum].gem; + } + // find item type for crafting + if(storageitems[itemnum].itemtype < 10) + storageitems[itemnum].type = GServer->EquipList[items[itemnum].itemtype].Index[items[itemnum].itemnum]->type; + if(storageitems[itemnum].itemtype == 10) + storageitems[itemnum].type = GServer->UseList.Index[items[itemnum].itemnum]->type; + if(storageitems[itemnum].itemtype == 11) + storageitems[itemnum].type = GServer->JemList.Index[items[itemnum].itemnum]->type; + if(storageitems[itemnum].itemtype == 12) + storageitems[itemnum].type = GServer->NaturalList.Index[items[itemnum].itemnum]->type; + if(storageitems[itemnum].itemtype == 14) + storageitems[itemnum].type = GServer->PatList.Index[items[itemnum].itemnum]->type; + + } + */ + //End of original storage load code. + + + GServer->DB->QFree( ); + //Log(MSG_INFO,"saving backup storage data."); + SaveBackupStorage(1); // makes a copy of the account's storage at the moment of login + //Log( MSG_DEBUG, "Saved backup storage data"); + if(Clan->clanid!=0) + { + result = GServer->DB->QStore("SELECT logo,back,name,cp,grade FROM list_clan where id=%i", Clan->clanid); + if(result==NULL) return false; + if(mysql_num_rows(result)!=1) + { + Log(MSG_WARNING, "There are %i clan(s) with id %i", mysql_num_rows(result), Clan->clanid ); + Clan->clanid=0; + } + else + { + row = mysql_fetch_row(result); + Clan->logo = atoi(row[0]); + Clan->back = atoi(row[1]); + strcpy(Clan->clanname,row[2]); + Clan->CP = atoi(row[3]); + Clan->grade = atoi(row[4]); + } + GServer->DB->QFree( ); + } + memset(&quest, 0, sizeof(SQuestData)); + //Log( MSG_DEBUG, "loaded clan info"); + UINT QuestToggle = 0; + UINT rowcount = 0; + /* + result = GServer->DB->QStore("SELECT quests from list_quest where owner=%i",CharInfo->charid ); + if(result!=NULL) + { + while(row = mysql_fetch_row(result)) + { + if( row[0] != NULL ) + { + memcpy(&quest, row[0], sizeof(SQuestData)); + rowcount++; + } + } + if(rowcount == 0) //no data detected + { + //Log( MSG_DEBUG, "No quest data detected with Exjam method. using PY method instead"); + QuestToggle = 1; // set toggle to use PY method + GServer->DB->QFree( ); + } + if(QuestToggle == 0) + { + //delete the old quest entry after loading it. + //GServer->DB->QExecute( "DELETE FROM list_quest WHERE owner=%i",thisclient->CharInfo->charid ); + //now convert quest flags to new system + + for(int flagid=0;flagid<512;flagid++) + { + dword byteid = flagid / 8; + dword bitid = flagid % 8; + if (((quest.flags[byteid] >> bitid) & 1) == 1) + quest.Qflags[byteid][bitid] = 1; + else + quest.Qflags[byteid][bitid] = 0; + } + GServer->DB->QFree( ); + Log( MSG_DEBUG, "loaded quests via Exjam method"); + savequests(this); + Log( MSG_DEBUG, "Converted flags and re-saved quests info"); + } + } + else + { + GServer->DB->QFree( ); + } + if(QuestToggle = 1) + */ + { // PY quest loading. Only performed if no old quests are found. Won't be any since there are no old characters + Log( MSG_DEBUG, "starting PY quest load"); + result = GServer->DB->QStore("SELECT questid,starttime,vbl,switches,items,count,episodevar,jobvar,planetvar,unionvar from player_quests where owner=%i",CharInfo->charid ); + //Log( MSG_DEBUG, "ran the query"); + tmpid = ""; + itmp = ""; + if(result != NULL) + { + UINT Index = 0; + while(row = mysql_fetch_row(result)) + { + quest.quests[Index].QuestID = atoi(row[0]); + quest.quests[Index].StartTime = atoi(row[1]); + + tmpid = strtok( row[2] , "|"); + for(unsigned int j=0;j<=10;j++) //quest variables + { + if(tmpid != NULL) + quest.quests[Index].Variables[j] = atoi(tmpid); + else + quest.quests[Index].Variables[j] = 0; + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest Var[%i][%i] = %i",Index ,j ,quest.quests[Index].Variables[j]); + } + tmpid = strtok( row[3] , "|"); //quest switches + for(unsigned int j=0;j<=4;j++) + { + if(tmpid != NULL) + quest.quests[Index].Switches[j] = atoi(tmpid); + else + quest.quests[Index].Switches[j] = 0; + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest Switch[%i][%i] = %i",Index ,j ,quest.quests[Index].Switches[j]); + } + tmpid = strtok( row[4] , "|"); //quest items + for(unsigned int j=0;j<=5;j++) + { + if(tmpid != NULL) + { + itemid = atoi(tmpid); + quest.quests[Index].Items[j].itemnum = itemid % 1000; + quest.quests[Index].Items[j].itemtype = itemid / 1000; + } + else + { + quest.quests[Index].Items[j].itemnum = 0; + quest.quests[Index].Items[j].itemtype = 0; + } + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest Item[%i][%i] = %i",Index , j ,quest.quests[Index].Items[j].itemnum); + } + tmpid = strtok( row[5] , "|"); //quest item count + for(unsigned int j=0;j<=5;j++) + { + if(tmpid != NULL) + quest.quests[Index].Items[j].count = atoi(tmpid); + else + quest.quests[Index].Items[j].count = 0; + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest Item Count[%i][%i] = %i",Index ,j ,quest.quests[Index].Items[j].count); + } + tmpid = strtok( row[6] , "|"); + for(unsigned int j=0;j<=5;j++) //episode variables + { + if(tmpid != NULL) + quest.EpisodeVar[j] = atoi(tmpid); + else + quest.EpisodeVar[j] = 0; + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest EpisodeVar[%i][%i] = %i",Index ,j ,quest.EpisodeVar[j]); + } + tmpid = strtok( row[7] , "|"); //job variables + for(unsigned int j=0;j<=3;j++) + { + if(tmpid != NULL) + quest.JobVar[j] = atoi(tmpid); + else + quest.JobVar[j] = 0; + tmpid = strtok(NULL, ",|"); + //Log( MSG_DEBUG, "Quest JobVar[%i][%i] = %i",Index ,j ,quest.JobVar[j]); + } + tmpid = strtok( row[8] , "|"); //Planet variables + for(unsigned int j=0;j<=7;j++) + { + if(tmpid != NULL) + quest.PlanetVar[j] = atoi(tmpid); + else + quest.PlanetVar[j] = 0; + tmpid = strtok(NULL, ",|"); + } + tmpid = strtok( row[9] , "|"); //Planet variables + for(unsigned int j=0;j<=10;j++) + { + if(tmpid != NULL) + quest.UnionVar[j] = atoi(tmpid); + else + quest.UnionVar[j] = 0; + tmpid = strtok(NULL, ",|"); + } + Index ++; + } + } + //Log( MSG_DEBUG, "loaded the regular quest variables"); + GServer->DB->QFree( ); + // PY method load quest flags + //Log( MSG_DEBUG, "starting PY flag load"); + result = GServer->DB->QStore("SELECT flags from questflags where owner=%i",CharInfo->charid ); + //Log( MSG_DEBUG, "ran the query"); + if(result != NULL) + { + tmpid = ""; + itmp = 0; + UINT Index = 0; + UINT flagCount = 0; + //first set all quest flags to zero + for(int i=0;i<64;i++) + { + for(int j=0;j<8;j++) + quest.Qflags[i][j] = 0; + } + while(row = mysql_fetch_row(result)) + { + int flagid = atoi(row[0]); + dword byteid = flagid / 8; + dword bitid = flagid % 8; + quest.Qflags[byteid][bitid] = 1; + flagCount++; + } + //Log( MSG_DEBUG, "Flag count = %i",flagCount); + } + GServer->DB->QFree( ); + //Log( MSG_DEBUG, "loaded quests info via PY method"); + } + Session->isLoggedIn = true; + + + + + // Timestamp for last login + time_t rtime; + time(&rtime); + char *timestamp = ctime(&rtime); + timestamp[ strlen(timestamp)-1 ] = ' '; + //Log(MSG_DEBUG,"setting time stamp fo login: %s for userid: %u",timestamp,Session->userid ); + GServer->DB->QExecute("UPDATE accounts SET online=1, lastlogin='%s' where id=%u", timestamp,Session->userid ); + Session->LogTime = clock(); + //Log(MSG_INFO,"set datestamp"); + + // Calculate how many fairies are available online, according to amount of players + int oldFairyMax = GServer->Config.FairyMax; + GServer->Config.FairyMax = (int)ceil((float)GServer->ClientList.size() / 50.0) + GServer->Config.ScrollFairy; //1 fairy for every 50 player + the number of fairies summoned by scrolls + if( oldFairyMax < GServer->Config.FairyMax ){ + CFairy* thisfairy = new (nothrow) CFairy; + thisfairy->LastTime = clock(); + thisfairy->assigned = 0; + thisfairy->ListIndex = 0; + thisfairy->WaitTime = GServer->Config.FairyWait; + GServer->FairyList.push_back( thisfairy ); + } + //die_quest = 0; + + return true; +} + +// Trying a new save method PY (nothing changed yet. just made a copy of savedata for safety +void CPlayer::savedata( ) +{ + lastSaveTime = clock(); + if(Session->userid!=0) + { + CMap* map = GServer->MapList.Index[Position->Map]; + CRespawnPoint* thisrespawn = map->GetNearRespawn( this ); + CPlayer* thisclient = GServer->GetClientByID( clientid ); + if(thisrespawn == NULL) + Position->respawn = 4; + else + Position->respawn = thisrespawn->id; + char quick[1024]; + char basic[1024]; + //char sclass[1024]; + //char slevel[1024]; + char active[1024]; + char activelvl[1024]; + char pasive[1024]; + char pasivelvl[1024]; + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&basic[i], "%i",bskills[i]); + else + sprintf(&basic[strlen(basic)], ",%i",bskills[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&active[i], "%i",askill[i]); + else + sprintf(&active[strlen(active)], ",%i",askill[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&activelvl[i], "%i",askilllvl[i]); + else + sprintf(&activelvl[strlen(activelvl)], ",%i",askilllvl[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&pasive[i], "%i",pskill[i]); + else + sprintf(&pasive[strlen(pasive)], ",%i",pskill[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&pasivelvl[i], "%i",pskilllvl[i]); + else + sprintf(&pasivelvl[strlen(pasivelvl)], ",%i",pskilllvl[i]); + } + for(UINT i=0;i<32;i++) + { + if(i==0) + sprintf(&quick[i], "%i",quickbar[i]); + else + sprintf(&quick[strlen(quick)], ",%i",quickbar[i]); + } + long int hp = Stats->HP; + if(hp<=0) + hp=Stats->MaxHP * 10 / 100; + if(Stats->MP<0) + Stats->MP=0; + + GServer->DB->QExecute("UPDATE characters SET level=%i,face=%i,hairStyle=%i,sex=%i,classid=%i,zuly=%i,str=%i,dex=%i,_int=%i, con=%i,cha=%i,sen=%i,curHp=%i,curMp=%i,id=%i,statp=%i,skillp=%i,exp=%i,stamina=%i,quickbar='%s',basic_skills='%s',askill='%s',askilllvl='%s',pskill='%s',pskilllvl='%s',respawnid=%i,clanid=%i,clan_rank=%i,townid=%i WHERE char_name='%s'", + Stats->Level, CharInfo->Face, CharInfo->Hair, CharInfo->Sex, CharInfo->Job, CharInfo->Zulies, Attr->Str, Attr->Dex, Attr->Int, Attr->Con, Attr->Cha, Attr->Sen, hp, Stats->MP, CharInfo->charid, CharInfo->StatPoints, CharInfo->SkillPoints, + CharInfo->Exp, CharInfo->stamina, quick, basic, active, activelvl, pasive, pasivelvl, Position->respawn, Clan->clanid, Clan->clanrank, Position->saved, CharInfo->charname ); + + GServer->DB->QExecute("UPDATE characters SET unionid=%i,union1points=%i,union2points=%i,union3points=%i,union4points=%i,union5points=%i,union6points=%i,union7points=%i,union8points=%i,union9points=%i,union10points=%i WHERE char_name='%s'", + Union_s->unionvar[0], Union_s->unionvar[1],Union_s->unionvar[2], Union_s->unionvar[3], Union_s->unionvar[4], Union_s->unionvar[5], Union_s->unionvar[6], Union_s->unionvar[7], Union_s->unionvar[8], Union_s->unionvar[9], Union_s->unionvar[10], CharInfo->charname ); + + //save the inventory + saveinventory(); + //save the storage items + savestorage(); + + //this lot to be removed later + //if(!GServer->DB->QExecute("DELETE FROM items WHERE owner=%i", CharInfo->charid)) return; + //for(UINT i=0;i 0) + // { + // GServer->DB->QExecute("INSERT INTO items (owner,itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem) VALUES(%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i)", + // CharInfo->charid, items[i].itemnum, items[i].itemtype,items[i].refine, items[i].durability, + // items[i].lifespan, i, items[i].count, items[i].stats, (items[i].socketed?1:0), + // (items[i].appraised?1:0),items[i].gem ); + // } + //} + + + //Why is this still here and active? + //if savestorage is live and functional then this antiquated crap needs to be removed + + if(!GServer->DB->QExecute("DELETE FROM storage WHERE owner=%i", Session->userid)) return; + for(UINT i=0;i<160;i++) + { + if (storageitems[i].count > 0) + { + GServer->DB->QExecute("INSERT INTO storage (owner,itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem) VALUES(%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i)", + Session->userid, storageitems[i].itemnum, storageitems[i].itemtype,storageitems[i].refine, storageitems[i].durability, + storageitems[i].lifespan, i, storageitems[i].count, storageitems[i].stats, (storageitems[i].socketed?1:0), + (storageitems[i].appraised?1:0), storageitems[i].gem ); + } + } + + savequests( this ); + GServer->DB->QExecute("UPDATE accounts SET zulystorage = %i, logtime = %i,ktpoints=%i WHERE id = %i", CharInfo->Storage_Zulies, Session->logtime,Session->KTPoints, Session->userid); + Log(MSG_INFO, "Data Saved for char '%s' ", CharInfo->charname ); + } +} + +//PY Character save +void CPlayer::quicksave( ) +{ + GServer->DB->QExecute("UPDATE characters SET level=%i,face=%i,hairStyle=%i,sex=%i,classid=%i,zuly=%i,str=%i,dex=%i,_int=%i, con=%i,cha=%i,sen=%i,curHp=%i,curMp=%i,id=%i,statp=%i,skillp=%i,exp=%i,stamina=%i,respawnid=%i,clanid=%i,clan_rank=%i,townid=%i WHERE char_name='%s'", + Stats->Level, CharInfo->Face, CharInfo->Hair, CharInfo->Sex, CharInfo->Job, CharInfo->Zulies, Attr->Str, Attr->Dex, Attr->Int, Attr->Con, Attr->Cha, Attr->Sen, Stats->HP, Stats->MP, CharInfo->charid, CharInfo->StatPoints, CharInfo->SkillPoints, + CharInfo->Exp, CharInfo->stamina, Position->respawn, Clan->clanid, Clan->clanrank, Position->saved, CharInfo->charname ); + Log(MSG_INFO, "Data Quick Saved for char '%s' ", CharInfo->charname ); +} + +//PY Inventory save +void CPlayer::saveinventory( ) +{ + //build the strings + char tmp1[1024] = ""; + char tmp2[1024] = ""; + char tmp3[1024] = ""; + char tmp4[1024] = ""; + char tmp5[1024] = ""; + char tmp6[1024] = ""; + char tmp7[1024] = ""; + char tmp8[1024] = ""; + char tmp9[1024] = ""; + char slot[1024] = ""; + long tran = 0; + for(unsigned int i=0;i<=MAX_INVENTORY; i++) //inventory holds 140 + { + int socketed = 0; + int appraised = 0; + tran += items[i].itemnum + items[i].itemtype + items[i].refine; + //tran += items[i].durability + items[i].lifespan + items[i].count + items[i].stats; + //Log(MSG_INFO,"slot: %i itemid: %i type: %i count: %i",i, items[i].itemnum,items[i].itemtype,items[i].count); + if(i == 0) + { + if(items[i].count == 0) + { + sprintf(&tmp1[strlen(tmp1)], "0"); + sprintf(&tmp2[strlen(tmp2)], "0"); + sprintf(&tmp3[strlen(tmp3)], "0"); + sprintf(&tmp4[strlen(tmp4)], "0"); + sprintf(&tmp5[strlen(tmp5)], "0"); + sprintf(&tmp6[strlen(tmp6)], "0"); + sprintf(&tmp7[strlen(tmp7)], "0"); + sprintf(&tmp8[strlen(tmp8)], "0"); + sprintf(&tmp9[strlen(tmp9)], "0"); + sprintf(&slot[strlen(slot)], "%i",i); + } + else + { + if(items[i].socketed == true) + socketed = 1; + if(items[i].appraised == true) + appraised = 1; + sprintf(&tmp1[strlen(tmp1)], "%i",items[i].itemnum); + sprintf(&tmp2[strlen(tmp2)], "%i",items[i].itemtype); + sprintf(&tmp3[strlen(tmp3)], "%i",items[i].refine); + sprintf(&tmp4[strlen(tmp4)], "%i",items[i].durability); + sprintf(&tmp5[strlen(tmp5)], "%i",items[i].lifespan); + sprintf(&tmp6[strlen(tmp6)], "%i",items[i].count); + sprintf(&tmp7[strlen(tmp7)], "%i",items[i].stats); + sprintf(&tmp8[strlen(tmp8)], "%i",socketed); + sprintf(&tmp9[strlen(tmp9)], "%i",appraised); + sprintf(&slot[strlen(slot)], "%i",i); + } + } + else + { + if(items[i].count == 0) + { + sprintf(&tmp1[strlen(tmp1)], "|0"); + sprintf(&tmp2[strlen(tmp2)], "|0"); + sprintf(&tmp3[strlen(tmp3)], "|0"); + sprintf(&tmp4[strlen(tmp4)], "|0"); + sprintf(&tmp5[strlen(tmp5)], "|0"); + sprintf(&tmp6[strlen(tmp6)], "|0"); + sprintf(&tmp7[strlen(tmp7)], "|0"); + sprintf(&tmp8[strlen(tmp8)], "|0"); + sprintf(&tmp9[strlen(tmp9)], "|0"); + sprintf(&slot[strlen(slot)], "|%i",i); + } + else + { + if(items[i].socketed == true) + socketed = 1; + if(items[i].appraised == true) + appraised = 1; + if(items[i].gem != 0 && items[i].gem != items[i].stats) + items[i].stats = items[i].gem; + sprintf(&tmp1[strlen(tmp1)], "|%i",items[i].itemnum); + sprintf(&tmp2[strlen(tmp2)], "|%i",items[i].itemtype); + sprintf(&tmp3[strlen(tmp3)], "|%i",items[i].refine); + sprintf(&tmp4[strlen(tmp4)], "|%i",items[i].durability); + sprintf(&tmp5[strlen(tmp5)], "|%i",items[i].lifespan); + sprintf(&tmp6[strlen(tmp6)], "|%i",items[i].count); + sprintf(&tmp7[strlen(tmp7)], "|%i",items[i].stats); + sprintf(&tmp8[strlen(tmp8)], "|%i",socketed); + sprintf(&tmp9[strlen(tmp9)], "|%i",appraised); + sprintf(&slot[strlen(slot)], "|%i",i); + } + } + } + //Log(MSG_INFO,"Save string1 = %s ", tmp1); + //Log(MSG_INFO,"Save string2 = %s ", tmp2); + //Log(MSG_INFO,"Save string3 = %s ", tmp3); + //Log(MSG_INFO,"Save string4 = %s ", tmp4); + //Log(MSG_INFO,"Save string5 = %s ", tmp5); + //Log(MSG_INFO,"Save string6 = %s ", tmp6); + //Log(MSG_INFO,"Save string7 = %s ", tmp7); + //Log(MSG_INFO,"Save string8 = %s ", tmp8); + //Log(MSG_INFO,"Save string9 = %s ", tmp9); + //check if a record exists for this player + MYSQL_RES *result = GServer->DB->QStore("SELECT itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,slot,trans_action FROM charitems WHERE owner=%i", CharInfo->charid); + MYSQL_ROW row; + bool doesexist = false; + while(row = mysql_fetch_row(result)) + { + doesexist = true; + } + GServer->DB->QFree( ); + if(doesexist == true) + { + //record exists so update it + //Log(MSG_INFO,"record exists.... updating"); + GServer->DB->QExecute("UPDATE charitems SET itemid='%s',itemtype='%s',refine='%s',durability='%s',lifespan='%s',count='%s',stats='%s',socketed='%s',appraised='%s',slot='%s',trans_action=%i WHERE owner='%i'", + tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,slot,tran,CharInfo->charid); + } + else + { + //record does not exist so make a new one + //Log(MSG_INFO,"record does not exist.... creating new record"); + //GServer->DB->QExecute("INSERT INTO charitems (owner,trans_action) VALUES ('%i','%i')",CharInfo->charid,tran); + GServer->DB->QExecute("INSERT INTO charitems (owner,itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,trans_action) VALUES('%i','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%i')", + CharInfo->charid,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,slot,tran); + } + //save zuly + GServer->DB->QExecute("UPDATE characters SET zuly=%i WHERE char_name='%s'", + CharInfo->Zulies, CharInfo->charname ); + //Log(MSG_INFO,"Inventory saved"); + return; +} + +//PY Storage save +void CPlayer::savestorage( ) +{ + //build the strings. As we need a slot zero for storage we are going to have to add a buffer entry first + //for some bizarre reason when we load teh storage it ignores the leading zero so stuff gets shifted left without this + char tmp1[1024] = "0"; + char tmp2[1024] = "0"; + char tmp3[1024] = "0"; + char tmp4[1024] = "0"; + char tmp5[1024] = "0"; + char tmp6[1024] = "0"; + char tmp7[1024] = "0"; + char tmp8[1024] = "0"; + char tmp9[1024] = "0"; + char slot[1024] = "0"; + long tran = 0; + for(unsigned int i=0;i<=MAX_STORAGE; i++) //Storage holds 160 + { + int socketed = 0; + int appraised = 0; + tran += storageitems[i].itemnum + storageitems[i].itemtype + storageitems[i].refine; + tran += storageitems[i].durability + storageitems[i].lifespan + storageitems[i].count + storageitems[i].stats; + + if(storageitems[i].count == 0) + { + sprintf(&tmp1[strlen(tmp1)], "|0"); + sprintf(&tmp2[strlen(tmp2)], "|0"); + sprintf(&tmp3[strlen(tmp3)], "|0"); + sprintf(&tmp4[strlen(tmp4)], "|0"); + sprintf(&tmp5[strlen(tmp5)], "|0"); + sprintf(&tmp6[strlen(tmp6)], "|0"); + sprintf(&tmp7[strlen(tmp7)], "|0"); + sprintf(&tmp8[strlen(tmp8)], "|0"); + sprintf(&tmp9[strlen(tmp9)], "|0"); + if(i != 0) //don't write the zero value for slot since we already started the string with a leading zero + sprintf(&slot[strlen(slot)], "|%i",i); + } + else + { + if(storageitems[i].socketed == true) + socketed = 1; + if(storageitems[i].appraised == true) + appraised = 1; + sprintf(&tmp1[strlen(tmp1)], "|%i",storageitems[i].itemnum); + sprintf(&tmp2[strlen(tmp2)], "|%i",storageitems[i].itemtype); + sprintf(&tmp3[strlen(tmp3)], "|%i",storageitems[i].refine); + sprintf(&tmp4[strlen(tmp4)], "|%i",storageitems[i].durability); + sprintf(&tmp5[strlen(tmp5)], "|%i",storageitems[i].lifespan); + sprintf(&tmp6[strlen(tmp6)], "|%i",storageitems[i].count); + sprintf(&tmp7[strlen(tmp7)], "|%i",storageitems[i].stats); + sprintf(&tmp8[strlen(tmp8)], "|%i",socketed); + sprintf(&tmp9[strlen(tmp9)], "|%i",appraised); + if(i != 0) //don't write the zero value for slot since we already started the string with a leading zero + sprintf(&slot[strlen(slot)], "|%i",i); + } + } + //check if a record exists for this player + MYSQL_RES *result = GServer->DB->QStore("SELECT itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,slot,trans_action FROM charstorage WHERE owner=%i", Session->userid); + MYSQL_ROW row; + bool doesexist = false; + while(row = mysql_fetch_row(result)) + { + doesexist = true; + } + GServer->DB->QFree( ); + if(doesexist == true) + { + //record exists so update it + //Log(MSG_INFO,"record exists.... updating"); + GServer->DB->QExecute("UPDATE charstorage SET itemid='%s',itemtype='%s',refine='%s',durability='%s',lifespan='%s',count='%s',stats='%s',socketed='%s',appraised='%s',slot='%s',trans_action=%i WHERE owner='%i'", + tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,slot,tran,Session->userid); + } + else + { + //record does not exist so make a new one + //Log(MSG_INFO,"record does not exist.... creating new record"); + //GServer->DB->QExecute("INSERT INTO charitems (owner,trans_action) VALUES ('%i','%i')",CharInfo->charid,tran); + GServer->DB->QExecute("INSERT INTO charstorage (owner,itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,slot,trans_action) VALUES('%i','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%i')", + Session->userid,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,slot,tran); + } + //save zuly storage + GServer->DB->QExecute("UPDATE accounts SET zulystorage = %i WHERE id = %i", CharInfo->Storage_Zulies, Session->userid); + //Log(MSG_INFO,"Storage saved"); + return; +} + +//PY Storage save +void CPlayer::SaveBackupStorage( int logstate ) +{ + //build the strings. As we need a slot zero for storage we are going to have to add a buffer entry first + //for some bizarre reason when we load the storage it ignores the leading zero so stuff gets shifted left without this + char tmp1[1024] = "0"; + char tmp2[1024] = "0"; + char tmp3[1024] = "0"; + char tmp4[1024] = "0"; + char tmp5[1024] = "0"; + char tmp6[1024] = "0"; + char tmp7[1024] = "0"; + char tmp8[1024] = "0"; + char tmp9[1024] = "0"; + char slot[1034] = "0"; + long tran = 0; + for(unsigned int i=0;i<=MAX_STORAGE; i++) //Storage holds 160 + { + int socketed = 0; + int appraised = 0; + if(storageitems[i].count == 0) + { + sprintf(&tmp1[strlen(tmp1)], "|0"); + sprintf(&tmp2[strlen(tmp2)], "|0"); + sprintf(&tmp3[strlen(tmp3)], "|0"); + sprintf(&tmp4[strlen(tmp4)], "|0"); + sprintf(&tmp5[strlen(tmp5)], "|0"); + sprintf(&tmp6[strlen(tmp6)], "|0"); + sprintf(&tmp7[strlen(tmp7)], "|0"); + sprintf(&tmp8[strlen(tmp8)], "|0"); + sprintf(&tmp9[strlen(tmp9)], "|0"); + if(i != 0) //don't write the zero value for slot since we already started the string with a leading zero + sprintf(&slot[strlen(slot)], "|%i",i); + } + else + { + if(storageitems[i].socketed == true) + socketed = 1; + if(storageitems[i].appraised == true) + appraised = 1; + sprintf(&tmp1[strlen(tmp1)], "|%i",storageitems[i].itemnum); + sprintf(&tmp2[strlen(tmp2)], "|%i",storageitems[i].itemtype); + sprintf(&tmp3[strlen(tmp3)], "|%i",storageitems[i].refine); + sprintf(&tmp4[strlen(tmp4)], "|%i",storageitems[i].durability); + sprintf(&tmp5[strlen(tmp5)], "|%i",storageitems[i].lifespan); + sprintf(&tmp6[strlen(tmp6)], "|%i",storageitems[i].count); + sprintf(&tmp7[strlen(tmp7)], "|%i",storageitems[i].stats); + sprintf(&tmp8[strlen(tmp8)], "|%i",socketed); + sprintf(&tmp9[strlen(tmp9)], "|%i",appraised); + if(i != 0) //don't write the zero value for slot since we already started the string with a leading zero + sprintf(&slot[strlen(slot)], "|%i",i); + } + } + MYSQL_RES *result = GServer->DB->QStore("SELECT thisrev FROM backupstorage WHERE owner=%i", Session->userid); + MYSQL_ROW row; + int thisrev = 0; + int tmprev = 0; + while(row = mysql_fetch_row(result)) + { + tmprev = atoi(row[0]); + if(tmprev > thisrev)thisrev = tmprev; + } + thisrev ++; // add 1 to the highest existing rev number for this user + GServer->DB->QFree( ); + // Timestamp + time_t rtime; + time(&rtime); + char *timestamp = ctime(&rtime); + timestamp[ strlen(timestamp)-1 ] = ' '; + GServer->DB->QExecute("INSERT INTO backupstorage (owner,itemid,itemtype,refine,durability,lifespan,count,stats,socketed,appraised,slot,trans_action,datestamp,thisrev) VALUES('%i','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%i','%s','%i')", + Session->userid,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6,tmp7,tmp8,tmp9,slot,logstate,timestamp,thisrev); + //Log(MSG_INFO,"backup Storage saved for User %i revision number %i logstate %i",Session->userid, thisrev, logstate); + return; +} + + +// This is a REALLY bad way of saving the character data, but it works ^^ +/*void CPlayer::savedata( ) +{ + lastSaveTime = clock(); + if(Session->userid!=0) + { + CMap* map = GServer->MapList.Index[Position->Map]; + CRespawnPoint* thisrespawn = map->GetNearRespawn( this ); + CPlayer* thisclient = GServer->GetClientByID( clientid ); + if(thisrespawn == NULL) + Position->respawn = 4; + else + Position->respawn = thisrespawn->id; + char quick[1024]; + char basic[1024]; + //char sclass[1024]; + //char slevel[1024]; + char active[1024]; + char activelvl[1024]; + char pasive[1024]; + char pasivelvl[1024]; + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&basic[i], "%i",bskills[i]); + else + sprintf(&basic[strlen(basic)], ",%i",bskills[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&active[i], "%i",askill[i]); + else + sprintf(&active[strlen(active)], ",%i",askill[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&activelvl[i], "%i",askilllvl[i]); + else + sprintf(&activelvl[strlen(activelvl)], ",%i",askilllvl[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&pasive[i], "%i",pskill[i]); + else + sprintf(&pasive[strlen(pasive)], ",%i",pskill[i]); + } + for(UINT i=0;i<30;i++) + { + if(i==0) + sprintf(&pasivelvl[i], "%i",pskilllvl[i]); + else + sprintf(&pasivelvl[strlen(pasivelvl)], ",%i",pskilllvl[i]); + } + for(UINT i=0;i<32;i++) + { + if(i==0) + sprintf(&quick[i], "%i",quickbar[i]); + else + sprintf(&quick[strlen(quick)], ",%i",quickbar[i]); + } + long int hp = Stats->HP; + if(hp<=0) + hp=Stats->MaxHP * 10 / 100; + if(Stats->MP<0) + Stats->MP=0; + //Save data complete now?? (Pepu) + + GServer->DB->QExecute("UPDATE characters SET level=%i,face=%i,hairStyle=%i,sex=%i,classid=%i,zuly=%i,str=%i,dex=%i,_int=%i, con=%i,cha=%i,sen=%i,curHp=%i,curMp=%i,id=%i,statp=%i,skillp=%i,exp=%i,stamina=%i,quickbar='%s',basic_skills='%s',askill='%s',askilllvl='%s',pskill='%s',pskilllvl='%s',respawnid=%i,clanid=%i,clan_rank=%i,townid=%i WHERE char_name='%s'", + Stats->Level, CharInfo->Face, CharInfo->Hair, CharInfo->Sex, CharInfo->Job, CharInfo->Zulies, Attr->Str, Attr->Dex, Attr->Int, Attr->Con, Attr->Cha, Attr->Sen, hp, Stats->MP, CharInfo->charid, CharInfo->StatPoints, CharInfo->SkillPoints, + CharInfo->Exp, CharInfo->stamina, quick, basic, active, activelvl, pasive, pasivelvl, Position->respawn, Clan->clanid, Clan->clanrank, Position->saved, CharInfo->charname ); + + GServer->DB->QExecute("UPDATE characters SET unionid=%i,union1points=%i,union2points=%i,union3points=%i,union4points=%i,union5points=%i,union6points=%i,union7points=%i,union8points=%i,union9points=%i,union10points=%i WHERE char_name='%s'", + Union_s->unionvar[0], Union_s->unionvar[1],Union_s->unionvar[2], Union_s->unionvar[3], Union_s->unionvar[4], Union_s->unionvar[5], Union_s->unionvar[6], Union_s->unionvar[7], Union_s->unionvar[8], Union_s->unionvar[9], Union_s->unionvar[10], CharInfo->charname ); + + + if(!GServer->DB->QExecute("DELETE FROM items WHERE owner=%i", CharInfo->charid)) return; + for(UINT i=0;i 0) + { + GServer->DB->QExecute("INSERT INTO items (owner,itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem) VALUES(%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i)", + CharInfo->charid, items[i].itemnum, items[i].itemtype,items[i].refine, items[i].durability, + items[i].lifespan, i, items[i].count, items[i].stats, (items[i].socketed?1:0), + (items[i].appraised?1:0),items[i].gem ); + } + } + if(!GServer->DB->QExecute("DELETE FROM storage WHERE owner=%i", Session->userid)) return; + for(UINT i=0;i<160;i++) + { + if (storageitems[i].count > 0) + { + GServer->DB->QExecute("INSERT INTO storage (owner,itemnum,itemtype,refine,durability,lifespan,slotnum,count,stats,socketed,appraised,gem) VALUES(%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i,%i)", + Session->userid, storageitems[i].itemnum, storageitems[i].itemtype,storageitems[i].refine, storageitems[i].durability, + storageitems[i].lifespan, i, storageitems[i].count, storageitems[i].stats, (storageitems[i].socketed?1:0), + (storageitems[i].appraised?1:0), storageitems[i].gem ); + } + } + savequests( this ); + GServer->DB->QExecute("UPDATE accounts SET zulystorage = %i WHERE id = %i", CharInfo->Storage_Zulies, Session->userid); + Log(MSG_INFO, "Data Saved for char '%s' ", CharInfo->charname ); + } +}*/ + +void CPlayer::savequests( CPlayer* thisclient ) +{ + //char* questBuffer = new char[sizeof(SQuestData)*3 +1]; + //mysql_real_escape_string(GServer->DB->Mysql, questBuffer, (const char*)&quest, sizeof(SQuestData)); + //GServer->DB->QExecute( "DELETE FROM list_quest WHERE owner=%i",thisclient->CharInfo->charid ); + //GServer->DB->QExecute("INSERT INTO list_quest (owner, quests) VALUES(%i,'%s')", + // thisclient->CharInfo->charid, questBuffer); + //delete questBuffer; + + //PY retro method + //GServer->DB->QFree( ); + //Log(MSG_DEBUG,"Started SaveQuests"); + GServer->DB->QExecute( "DELETE FROM player_quests WHERE owner=%i",thisclient->CharInfo->charid ); + //Log(MSG_DEBUG,"Ran the player_quests deletion query"); + for(int i=0;i<10;i++) + { + if (thisclient->quest.quests[i].QuestID == 0) continue; + char vbl[100] = ""; //make the variables list + for(int j=0;j<10;j++) + { + sprintf(&vbl[strlen(vbl)], "%u",thisclient->quest.quests[i].Variables[j]); + if(j < 9) + sprintf(&vbl[strlen(vbl)],"|"); + } + char switches[10] = ""; //make the switches list + for(int j=0;j<4;j++) + { + sprintf(&switches[strlen(switches)], "%u",thisclient->quest.quests[i].Switches[j]); + if(j < 3) + sprintf(&switches[strlen(switches)],"|"); + } + char itemslist[100] = ""; //make the items list. Changed items to itemslist due to warning. + char itemcount[100] = ""; //item counts + for(int j=0;j<5;j++) + { + int ItemID = thisclient->quest.quests[i].Items[j].itemtype * 1000 + thisclient->quest.quests[i].Items[j].itemnum; + //sprintf(&items[strlen(items)], "%i",thisclient->quest.quests[i].Items[j].itemnum); + sprintf(&itemslist[strlen(itemslist)], "%i",ItemID); + sprintf(&itemcount[strlen(itemcount)], "%i",thisclient->quest.quests[i].Items[j].count); + if(j < 4) + { + sprintf(&itemslist[strlen(itemslist)],"|"); + sprintf(&itemcount[strlen(itemcount)],"|"); + } + } + char episode[50] = ""; //make the episode variable list + for(int j=0;j<5;j++) + { + sprintf(&episode[strlen(episode)], "%u",thisclient->quest.EpisodeVar[j]); + if(j < 4) + sprintf(&episode[strlen(episode)],"|"); + } + char jobvar[30] = ""; //make the job variable list + for(int j=0;j<3;j++) + { + sprintf(&jobvar[strlen(jobvar)], "%u",thisclient->quest.JobVar[j]); + if(j < 2) + sprintf(&jobvar[strlen(jobvar)],"|"); + } + char planetvar[70] = ""; //make the planet variable list + for(int j=0;j<7;j++) + { + sprintf(&planetvar[strlen(planetvar)], "%u",thisclient->quest.PlanetVar[j]); + if(j < 6) + sprintf(&planetvar[strlen(planetvar)],"|"); + } + char unionvar[100] = ""; //make the union variable list + for(int j=0;j<10;j++) + { + sprintf(&unionvar[strlen(unionvar)], "%u",thisclient->quest.UnionVar[j]); + if(j < 9) + sprintf(&unionvar[strlen(unionvar)],"|"); + } + GServer->DB->QExecute("INSERT INTO player_quests (owner, questid, starttime, vbl, switches, items, count, episodevar, jobvar, planetvar, unionvar) VALUES(%i,%i,%i,'%s','%s','%s','%s','%s','%s','%s','%s')", + thisclient->CharInfo->charid, thisclient->quest.quests[i].QuestID, thisclient->quest.quests[i].StartTime, + vbl, switches, itemslist, itemcount, episode, jobvar, planetvar, unionvar); + //Log(MSG_DEBUG,"Ran the player_quests insertion query"); + } + //GServer->DB->QFree( ); + //now the quest flags + GServer->DB->QExecute( "DELETE FROM questflags WHERE owner=%i",thisclient->CharInfo->charid ); + //Log(MSG_DEBUG,"Ran the questflag deletion query"); + char qflags[10000] = ""; + int flagCount = 0; + /* + for(int i=0;i<1000;i++) + { + if(thisclient->quest.Qflags[i] == 1) + { + if(sizeof(&qflags) > 995) + { + Log(MSG_WARNING,"Quest flag list too big for player %s",thisclient->CharInfo->charname); + i = 999; + continue; + } + if(flagCount == 0) + { + sprintf(&qflags[strlen(qflags)], "%i",i); + flagCount++; + } + else + { + sprintf(&qflags[strlen(qflags)],"|"); + sprintf(&qflags[strlen(qflags)], "%i",i); + flagCount++; + } + } + } + */ + + for(int i=0;i<0x40;i++) + { + for(int j=0;j<8;j++) + { + if(thisclient->quest.Qflags[i][j] != 0) + { + int flagid = (i*8)+j; + GServer->DB->QExecute("INSERT INTO questflags (owner, flags) VALUES(%i,%i)", + thisclient->CharInfo->charid, flagid); + //Log(MSG_DEBUG,"Ran the questflag deletion query"); + flagCount++; + } + } + } + //Log(MSG_DEBUG,"%i Quest flags detected during save process",flagCount); + +} diff --git a/Src/World Server/playerfunctions.cpp b/Src/World Server/playerfunctions.cpp new file mode 100644 index 0000000..6f49887 --- /dev/null +++ b/Src/World Server/playerfunctions.cpp @@ -0,0 +1,1055 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "player.h" +#include "worldserver.h" + +// Returns the amount of exp that is needed for the next level +UINT CPlayer::GetLevelEXP( ) +{ + if (Stats->Level <= 15) return (unsigned int)( ( Stats->Level + 10 ) * ( Stats->Level + 5 ) * ( Stats->Level + 3 ) * 0.7 ); + else if (Stats->Level <= 50) return (unsigned int)( ( Stats->Level - 5 ) * ( Stats->Level + 2 ) * ( Stats->Level + 2 ) * 2.2 ); + else if (Stats->Level <= 100) return (unsigned int)( ( Stats->Level - 38 ) * ( Stats->Level - 5 ) * ( Stats->Level + 2 ) * 9 ); + else if (Stats->Level <= 139) return (unsigned int)( ( Stats->Level + 220 ) * ( Stats->Level + 34 ) * ( Stats->Level + 27 ) ); + else return (unsigned int)( ( Stats->Level - 126 ) * ( Stats->Level - 15 ) * ( Stats->Level + 7 ) * 41 ); +} + +// check if player can level up +bool CPlayer::CheckPlayerLevelUP( ) +{ + //if(Session->Respawned == 2){Log( MSG_INFO, "Checking Levelup" );} + if (CharInfo->Exp >= GetLevelEXP()) + { + //if(Session->Respawned == 2){Log( MSG_INFO, "XP >= GetLevelXP" );} + CharInfo->Exp -= GetLevelEXP(); //exp is subtracted anyway whether we reached max or not. + //check if max level has been reached + if(Stats->Level >= GServer->Config.MaxLevel) + { + //posibly give extra bonuses here at some point rather than just subtracting exp and returning. + return true; // returning true should show the levelup animation still and also set HP and MP to max + } + Stats->Level++; + Stats->HP = GetMaxHP( ); + Stats->MP = GetMaxMP( ); + CharInfo->StatPoints += int((Stats->Level*0.8)+10); + // Proper skill point distribution for Pre-Evo + if(Stats->Level >= 100 && (Stats->Level % 2) == 0) + { + // 5 skill points every 2 levels from 100 + CharInfo->SkillPoints += 5; + } + else if(Stats->Level > 22 && ((Stats->Level - 22) % 4) == 0) + { + // 5 skill points every 4 levels from 22 + CharInfo->SkillPoints += 5; + } + else if(Stats->Level == 22) + { + CharInfo->SkillPoints += 4; + } + else if(Stats->Level == 18) + { + CharInfo->SkillPoints += 3; + } + else if(Stats->Level == 14 || Stats->Level == 10) + { + CharInfo->SkillPoints += 2; + } + + BEGINPACKET( pak, 0x79e ); + ADDWORD( pak, clientid ); + ADDWORD( pak, Stats->Level ); + ADDDWORD( pak, CharInfo->Exp ); + ADDWORD( pak, CharInfo->StatPoints ); + ADDWORD( pak, CharInfo->SkillPoints ); + client->SendPacket( &pak ); + + RESETPACKET( pak, 0x79e ); + ADDWORD( pak, clientid ); + GServer->SendToVisible( &pak, this ); + SetStats( ); + savedata( ); + //SendLevelUPtoChar(this); + return true; + } + //if(Session->Respawned == 2){Log( MSG_INFO, "XP < GetLevelXP" );} + return false; +} + +bool CPlayer::canUseSkill(CSkills* skill) +{ + + // Check for weapon requirements + int check = 5; + bool fail = true; + for (int i = 0; i < 5; i++) + { + if (skill->weapon[i] == 0) + { + check--; + continue; + } + for (int j = 1; j < 12; j++) { // I'm not sure if 1 - 12 is right for all equipment? + if (GServer->EquipList[items[j].itemtype].Index[items[j].itemnum]->type == skill->weapon[i]) + { + fail = false; + break; + } + } + if (!fail) break; + } + if (fail && check) + { + if(Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Failed skill check @ weapons. skill = %i", skill->id); + GServer->SendPM(this, buffer); + } + return false; + } + + // Check job requirement + fail = true; + check = 4; + for (char i = 0; i < 4; i++) + { + if (skill->c_class[i]>0) + { + switch(skill->c_class[i]) + { + case cSoldierJob: + if((CharInfo->Job)/100==1) fail = false; + break; + case cMuseJob: + if((CharInfo->Job)/100==2) fail = false; + break; + case cHawkerJob: + if((CharInfo->Job)/100==3) fail = false; + break; + case cDealerJob: + if((CharInfo->Job)/100==4) fail = false; + break; + case cKnightJob: + if(CharInfo->Job==121) fail = false; + break; + case cChampionJob: + if(CharInfo->Job==122) fail = false; + break; + case cMageJob: + if(CharInfo->Job==221) fail = false; + break; + case cClericJob: + if(CharInfo->Job==222) fail = false; + break; + case cRaiderJob: + if(CharInfo->Job==321) fail = false; + break; + case cScoutJob: + if(CharInfo->Job==322) fail = false; + break; + case cBourgeoisJob: + if(CharInfo->Job==421) fail = false; + break; + case cArtisanJob: + if(CharInfo->Job==422) fail = false; + break; + default: + break; + } + if (!fail) break; + } else + check--; + } + if (fail && check) { + if(Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Failed skill check @ job. job = %i skill = %i",CharInfo->Job, skill->id); + GServer->SendPM(this, buffer); + } + return false; + } + + // Check for stat requirements + fail = false; + for (char i = 0; i < 2; i++) + { + switch (skill->req[i]) + { + case sLevel: + if (skill->reqam[i] > Stats->Level) fail = true; + break; + case sStrength: + if (skill->reqam[i] > Attr->Str + Attr->Estr) fail = true; + break; + case sDexterity: + if (skill->reqam[i] > Attr->Dex + Attr->Edex) fail = true; + break; + case sIntelligence: + if (skill->reqam[i] > Attr->Int + Attr->Eint) fail = true; + break; + case sConcentration: + if (skill->reqam[i] > Attr->Con + Attr->Econ) fail = true; + break; + case sCharm: + if (skill->reqam[i] > Attr->Cha + Attr->Echa) fail = true; + break; + case sSensibility: + if (skill->reqam[i] > Attr->Sen + Attr->Esen) fail = true; + break; + default: + break; + } + } + if (fail) + { + if(Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Failed skill check @ stats. skill = %i", skill->id); + GServer->SendPM(this, buffer); + } + return false; + } + if(Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Skill check successful for skill = %i", skill->id); + GServer->SendPM(this, buffer); + } + // Passed all the tests, can cast skill + return true; +} + + +// Send a PM to client with user information +bool CPlayer::GetPlayerInfo( ) +{ + char text[50]; + sprintf(text,"Attack: %i | Critical: %i",Stats->Attack_Power, Stats->Critical ); + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"Defense: %i | Magic Defense: %i",Stats->Defense, Stats->Magic_Defense); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"Accury: %i | Dodge: %i",Stats->Accury,Stats->Dodge ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"aspeed: %i | mspeed: %i",Stats->Attack_Speed,Stats->Move_Speed ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"HP: %i/%i , MP: %i/%i",Stats->HP,Stats->MaxHP,Stats->MP,Stats->MaxMP); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"Position[%i]: (%.0f,%.0f)",Position->Map,Position->current.x,Position->current.y); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"ClientID: %u | CharID: %u", clientid, CharInfo->charid ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"inGame: %i | Logged: %i", Session->inGame, Session->isLoggedIn ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + sprintf(text,"ClanName[%u]: %s | ClanGrade: %i | ClanRank: %i", Clan->clanid, Clan->clanname, Clan->grade, Clan->clanrank ); + RESETPACKET( pak, 0x0784 ); + ADDSTRING( pak, "[GM]PlayerInfo" ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, text ); + ADDBYTE( pak, 0 ); + client->SendPacket(&pak); + return true; +} + +// clearn player lists +bool CPlayer::CleanPlayerVector( ) +{ + CMap* map = GServer->MapList.Index[Position->Map]; + VisiblePlayers.clear(); + VisibleMonsters.clear(); + VisibleDrops.clear(); + VisibleNPCs.clear(); + return true; +} + +// update visibility list +bool CPlayer::VisiblityList( ) +{ + if(!this->Session->inGame) return true; + std::vector newVisiblePlayers; + std::vector newVisibleDrops; + std::vector newVisibleMonsters; + std::vector newVisibleNPCs; + // Clients + CMap* map = GServer->MapList.Index[Position->Map]; + for(UINT i=0;iPlayerList.size();i++) + { + CPlayer* otherclient = map->PlayerList.at(i); + if(otherclient == NULL) + continue; + if ( this == otherclient || !otherclient->Session->inGame) + { + continue; + } + float distance = GServer->distance( this->Position->current, otherclient->Position->current ); + if ( GServer->IsVisible( this, otherclient ) ) + { + if ( distance < MAXVISUALRANGE && !otherclient->isInvisibleMode ) + { + newVisiblePlayers.push_back( otherclient ); + } + else + { + ClearObject( otherclient->clientid ); + } + } + else + { + if ( distance < MINVISUALRANGE && !otherclient->isInvisibleMode ) + { + newVisiblePlayers.push_back( otherclient ); + otherclient->SpawnToPlayer(this, otherclient); + } + } + } + // Monsters + for(UINT i=0;iMonsterList.size();i++) + { + CMonster* thismon = map->MonsterList.at( i ); + if(thismon == NULL) + continue; + float distance = GServer->distance ( this->Position->current, thismon->Position->current ); + if ( GServer->IsVisible( this, thismon ) ) + { + if (distance < MAXVISUALRANGE ) + { + newVisibleMonsters.push_back( thismon->clientid); + } + else + { + ClearObject( thismon->clientid ); + } + } + else + { + if ( distance< MINVISUALRANGE ) + { + newVisibleMonsters.push_back( thismon->clientid); + thismon->SpawnMonster(this, thismon ); + } + } + } + // Drops + for(unsigned i=0; iDropsList.size(); i++) + { + CDrop* thisdrop = map->DropsList.at(i); + if(thisdrop == NULL) + continue; + if( time(NULL) - thisdrop->droptime <= GServer->Config.DropDelay ) + continue; + float distance = GServer->distance( this->Position->current, thisdrop->pos ); + if ( GServer->IsVisible( this, thisdrop ) ) + { + if ( distance< MAXVISUALRANGE ) + { + newVisibleDrops.push_back( thisdrop ); + } + else + { + this->ClearObject( thisdrop->clientid ); + } + } + else + { + if ( distance < MINVISUALRANGE ) + { + thisdrop->spawned = true; + newVisibleDrops.push_back( thisdrop ); + GServer->pakSpawnDrop( this, thisdrop ); + } + } + } + // Npcs + for(unsigned i=0; iNPCList.size(); i++) + { + CNPC* thisnpc = map->NPCList.at(i); + float distance = GServer->distance( this->Position->current, thisnpc->pos ); + if ( GServer->IsVisible( this, thisnpc ) ) + { + if ( distance < MAXVISUALRANGE ) + { + newVisibleNPCs.push_back( thisnpc ); + } + else + { + this->ClearObject( thisnpc->clientid ); + } + } + else + { + if ( distance < MINVISUALRANGE ) + { + newVisibleNPCs.push_back( thisnpc ); + GServer->pakSpawnNPC( this, thisnpc ); + } + } + } + VisiblePlayers.clear(); + VisibleDrops.clear(); + VisibleMonsters.clear(); + VisibleNPCs.clear(); + VisiblePlayers = newVisiblePlayers; + VisibleDrops = newVisibleDrops; + VisibleMonsters = newVisibleMonsters; + VisibleNPCs = newVisibleNPCs; + return true; + +} + +bool CPlayer::ChangeDialog(unsigned int npc, unsigned int dialog, unsigned int eventid ) +{ + CMap* map = GServer->MapList.Index[Position->Map]; + if(map == NULL) + return true; + for(unsigned i=0; iNPCList.size(); i++) + { + CNPC* thisnpc = map->NPCList.at(i); + + if(thisnpc->npctype == npc) + { + //Log(MSG_INFO,"Found NPC %i with dialog %i",thisnpc->npctype, thisnpc->thisnpc->dialogid); + CNPC* newnpc = thisnpc; + this->ClearObject( thisnpc->clientid ); + newnpc->thisnpc->dialogid = dialog; + newnpc->thisnpc->eventid = eventid; + GServer->ObjVar[npc][0] = eventid; + VisibleNPCs.push_back( newnpc ); + GServer->pakSpawnNPC( this, newnpc ); + } + } + return true; +} + + +// Returns a free slot in the inventory (0xffff if is full) +UINT CPlayer::GetNewItemSlot( CItem thisitem ) +{ + UINT tabsize = 30; + UINT itemtab = 0; + switch(thisitem.itemtype) + { + case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9://equip + itemtab = 0; + break; + case 10://consumibles + itemtab = 1; + break; + case 11:case 12://etc + itemtab = 2; + break; + case 14://pat + itemtab = 3; + break; + default: + Log(MSG_WARNING,"unknown itemtype %i", thisitem.itemtype); + return 0xffff; + break; + } + //useitems and natural + //first we try to find a slot that already contains some of this item + //this way the items will always stack if possible. + if(itemtab == 1 || itemtab == 2) + { + for(int i=0;i<30;i++) + { + UINT slot=12; + slot += (tabsize*itemtab)+i; + if(items[slot].itemnum == thisitem.itemnum && items[slot].itemtype == thisitem.itemtype) + { + int totcount = thisitem.count + items[slot].count; + if(totcount < 1000) + return slot; + } + } + } + //now scan again to find a fully empty slot for mats, useitems and equipable stuff + //doesn't matter what tab type + for(int i=0;i<30;i++) + { + UINT slot=12; + slot += (tabsize*itemtab)+i; + if(items[slot].itemnum==0 &items[slot].count<1) + return slot; + } + return 0xffff; +} + +//count storage items +unsigned int CPlayer::Getnstorageitems( CPlayer* thisclient ) +{ + UINT count = 0; + for(UINT i=0;i<160;i++) + { + if(thisclient->storageitems[i].count > 0) + count++; + } + return count; +} + +// Returns a free slot in the storage (0xffff if is full) +UINT CPlayer::GetNewStorageItemSlot( CItem thisitem ) +{ + //first check for a slot that already contains this item if it is stackable + if(thisitem.itemtype == 10 || thisitem.itemtype == 11 || thisitem.itemtype == 12) + { + for(UINT i=0;i<144;i++) + { + if(storageitems[i].itemtype == thisitem.itemtype && storageitems[i].itemnum == thisitem.itemnum) + { + //now we make sure that the total of the new items plus the ones already there do not exceed 999 + int totalcount = storageitems[i].count + thisitem.count; + if(totalcount < 1000) + { + return i; + } + } + } + } + //so we couldn't find a matching entry to stack onto + //or the item is unstackable + //Let's look for an empty slot instead then + for(UINT i=0;i<144;i++) + { + if(storageitems[i].itemtype == 0) + { + + return i; + } + } + return 0xffff; +} + +// Erase a object from the user +bool CPlayer::ClearObject( unsigned int otherclientid ) +{ + BEGINPACKET( pak, 0x794 ); + ADDWORD( pak, otherclientid ); + client->SendPacket( &pak ); + return true; +} + +// Clean the player values +void CPlayer::RestartPlayerVal( ) +{ + ClearBattle( Battle ); + Shop->open = false; + Trade->trade_target = 0; + Trade->trade_status = 0; +} + +int CPlayer::GetQuestVar(short nVarType, short nVarNO){ + switch(nVarType){ + case 0: + { + SQuest* activeQuest = GetActiveQuest(); + if(activeQuest == NULL) return -1; + return activeQuest->Variables[nVarNO]; + } + case 0x100: + { + SQuest* activeQuest = GetActiveQuest(); + if(activeQuest == NULL) return -1; + return activeQuest->GetSwitchBit(nVarNO); + } + case 0x200://Remaining time + Log(MSG_DEBUG, "GetQuestVar got remaining quest time"); + return 1; + case 0x300: + if(nVarNO >= 5) return -1; + return quest.EpisodeVar[nVarNO]; + case 0x400: + if(nVarNO >= 3) return -1; + return quest.JobVar[nVarNO]; + case 0x500: + if(nVarNO >= 7) return -1; + return quest.PlanetVar[nVarNO]; + case 0x600: + if(nVarNO >= 10) return -1; + return quest.UnionVar[nVarNO]; + default: + break; + } + return -1; +} + +void CPlayer::SetQuestVar(short nVarType, short nVarNO, short nValue){ + switch(nVarType){ + case 0: + { + SQuest* activeQuest = GetActiveQuest(); + if(activeQuest == NULL) return; + activeQuest->Variables[nVarNO] = nValue; + } + return; + case 0x100: + { + SQuest* activeQuest = GetActiveQuest(); + if(activeQuest == NULL) return; + activeQuest->SetSwitchBit(nVarNO, nValue); + } + return; + case 0x300: + if(nVarNO >= 5) return; + quest.EpisodeVar[nVarNO] = nValue; + return; + case 0x400: + if(nVarNO >= 3) return; + quest.JobVar[nVarNO] = nValue; + return; + case 0x500: + if(nVarNO >= 7) return; + quest.PlanetVar[nVarNO] = nValue; + return; + case 0x600: + if(nVarNO >= 10) return; + quest.UnionVar[nVarNO] = nValue; + return; + default: + break; + } +} + + +int CPlayer::ExecuteQuestTrigger(dword hash) +{ + //Code that actually works. Taken from KTRose + CQuestTrigger* trigger = NULL; + CQuestTrigger* nexttrigger = NULL; + bool logme = false; + if (this->questdebug) logme = true; + CheckQuest = -1; + //if (GServer->questdebug) + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: searching for trigger %i in TriggerList with size %i", hash, GServer->TriggerList.size ()); + + for (unsigned j = 0; j < GServer->TriggerList.size (); j++) + { + if (GServer->TriggerList.at (j)->TriggerHash == hash) + { + trigger = GServer->TriggerList.at (j); + nexttrigger = GServer->TriggerList.at (j + 1); + break; + } + } + if (trigger == NULL) { + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: Could not find quest trigger. Returning Failure"); + return QUEST_FAILURE; + } + int success = QUEST_SUCCESS; + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: Trigger executed %s[%i]", trigger->TriggerName, trigger->CheckNext); + for (dword i = 0; i < trigger->ConditionCount; i++) + { + int command = trigger->Conditions[i]->opcode; + + if (command > 30 || command < 0) continue; + success = (*GServer->qstCondFunc[command])(GServer, this, trigger->Conditions[i]->data); + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: Condition %03u returned %d", command, success); + if (success == QUEST_FAILURE) + { + if (!trigger->CheckNext) + return success; + else return ExecuteQuestTrigger (nexttrigger->TriggerHash); + } + } + for (dword i = 0; i < trigger->ActionCount; i++) + { + int command = trigger->Actions[i]->opcode; + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: command %03u", command); + if (command > 35 || command < 0) continue; + /*if (this->questdebug) + Log(MSG_QUESTDEBUG, "ExecuteQuestTrigger:: Still good after command value check");*/ + success = (*GServer->qstRewdFunc[command])(GServer, this, trigger->Actions[i]->data); + if (this->questdebug) + Log (MSG_QUESTDEBUG, "ExecuteQuestTrigger:: reward %03u returned %d", command, success); + } + return success; + + + + + //CQuestTrigger* trigger = NULL; + // CQuestTrigger* nexttrigger = NULL; + // CheckQuest = -1; + // for(unsigned j=0; j < GServer->TriggerList.size(); j++) + // { + // if (GServer->TriggerList.at(j)->TriggerHash == hash) + // { + // trigger = GServer->TriggerList.at(j); + // nexttrigger = GServer->TriggerList.at(j + 1); + // break; + // } + // } + // if (trigger == NULL) return QUEST_FAILURE; + + // int success = QUEST_SUCCESS; + // Log(MSG_DEBUG, "Trigger Executed: %s [%i] hash %i", trigger->TriggerName, trigger->CheckNext, hash); + // if(Session->codedebug)GServer->SendPM(this, "Trigger executed %s[%i]", trigger->TriggerName, trigger->CheckNext); + // for (dword i = 0; i < trigger->ConditionCount; i++) + // { + // int command = trigger->Conditions[i]->opcode; + + // if (command > 30 || command < 0) continue; + // success = (*GServer->qstCondFunc[command])(GServer, this, trigger->Conditions[i]->data); + // //Log(MSG_DEBUG, "Condition %03u returned %d", command, success); + // if(Session->codedebug)GServer->SendPM(this, "Condition %03u returned %d", command, success); + // if (success == QUEST_FAILURE) + // { + // if (!trigger->CheckNext) + // return success; + // else return ExecuteQuestTrigger(nexttrigger->TriggerHash); + // } + // } + // for (dword i = 0; i < trigger->ActionCount; i++) + // { + // int command = trigger->Actions[i]->opcode; + // if(Session->codedebug)GServer->SendPM(this, "command %03u", command); + // if (command > 30 || command < 0) continue; + // if(Session->codedebug)GServer->SendPM(this, "Still good after command value check"); + // success = (*GServer->qstRewdFunc[command])(GServer, this, trigger->Actions[i]->data); + // if(Session->codedebug)GServer->SendPM(this, "reward %03u returned %d", command, success); + // //Log(MSG_DEBUG, "Reward %03u returned %d", command, success); + // } + // return success; +} + + +// HP/MP Regeneration Function +bool CPlayer::Regeneration() +{ + if (Stats->MaxHP == Stats->HP && Stats->MaxMP == Stats->MP) + { + lastRegenTime = 0; + return true; + } + + bool is_first_regen = false; + if (lastRegenTime == 0) + { + is_first_regen = true; + } + + clock_t etime = clock() - lastRegenTime; + if( etime >= 8 * CLOCKS_PER_SEC && Stats->HP > 0 ) + { + //Log(MSG_INFO,"Regeneration function. Character %i HP on entry %i", clientid, Stats->HP); + unsigned int hpamount = GetHPRegenAmount( ); + unsigned int mpamount = GetMPRegenAmount( ); + //if(stance = SITTING) + //{ + // hpamount = hpamount * 4; + // mpamount = mpamount * 4 + //} + Stats->HP += hpamount; + Stats->MP += mpamount; + if( Stats->HP > Stats->MaxHP) + Stats->HP = Stats->MaxHP; + if( Stats->MP > Stats->MaxMP ) + Stats->MP = Stats->MaxMP; + BEGINPACKET( pak, 0x7ec ); + ADDWORD( pak, Stats->HP); + ADDWORD( pak, Stats->MP ); + client->SendPacket( &pak ); + + if (Stats->MaxHP == Stats->HP && Stats->MaxMP == Stats->MP) + lastRegenTime = 0; + else + lastRegenTime = clock(); + //Log(MSG_INFO,"Regeneration function. Character %i HP on exit %i clock time %i", clientid, Stats->HP, lastRegenTime); + } + return true; +} + + +// Heal Player when use Food/Pots +bool CPlayer::PlayerHeal() +{ + clock_t transtime = clock() - UsedItem->lastRegTime; + if( UsedItem->usevalue != 0 && transtime >= 0.3 * CLOCKS_PER_SEC ) + { + if( UsedItem->used < UsedItem->usevalue && Stats->HP > 0 ) + { + int value = UsedItem->userate; + if((UsedItem->usevalue - UsedItem->used) < value) + { + value = UsedItem->usevalue - UsedItem->used; + } + switch( UsedItem->usetype ) + { + case 16: // HP + Stats->HP += value; + if(Stats->HP > Stats->MaxHP) + Stats->HP = Stats->MaxHP; + break; + case 17: // MP + Stats->MP += value; + if(Stats->MP > Stats->MaxMP) + Stats->MP = Stats->MaxMP; + break; + case 37: // Skill points + CharInfo->SkillPoints += value; + break; + case 76: //Stamina + CharInfo->stamina += value; + if(CharInfo->stamina > 5000) + CharInfo->stamina = 5000; + break; + default: + break; + } + UsedItem->used += value; + UsedItem->lastRegTime = clock(); + } + else + { + BEGINPACKET( pak,0x7b7 ); + ADDWORD ( pak, clientid ); + ADDDWORD ( pak, GServer->BuildBuffs( this ) ); + switch( UsedItem->usetype ) + { + case 16: // HP + ADDWORD( pak, Stats->HP ); + break; + case 17: // MP + ADDWORD( pak, Stats->MP ); + break; + case 37: //Skill Points + ADDWORD( pak, CharInfo->SkillPoints ); + break; + case 76: //Stamina + ADDWORD( pak, CharInfo->stamina ); + break; + default: + break; + } + GServer->SendToVisible( &pak, this ); + UsedItem->used = 0; + UsedItem->usevalue = 0; + UsedItem->userate = 0; + UsedItem->usetype = 0; + } + BEGINPACKET( pak, 0x7ec ); + ADDWORD( pak, Stats->HP); + ADDWORD( pak, Stats->MP ); + client->SendPacket( &pak ); + } + return true; +} + +void CPlayer::ReduceABC( ) +{ + unsigned int weapontype = 0; + weapontype = GServer->EquipList[WEAPON].Index[items[7].itemnum]->type; + switch(weapontype) + { + case 231: + items[132].count--; + if(items[132].count<=0) + { + ClearBattle( Battle ); + ClearItem( items[132] ); + } + break; + case 232: + items[133].count--; + if(items[133].count<=0) + { + ClearBattle( Battle ); + ClearItem( items[133] ); + } + break; + case 233: + items[134].count--; + if(items[134].count<=0) + { + ClearBattle( Battle ); + ClearItem( items[134] ); + } + break; + case 271: + items[132].count--; + if(items[132].count<=0) + { + ClearBattle( Battle ); + ClearItem( items[135] ); + } + break; + default: + break; + } +} + +SQuest* CPlayer::GetActiveQuest( ) +{ + for(dword i = 0; i < 10; i++){ + if(quest.quests[i].QuestID != ActiveQuest) continue; + return &quest.quests[i]; + } + return NULL; +} + +// return party pointer +CParty* CPlayer::GetParty( ) +{ + return Party->party; +} + +CLAN* CPlayer::GetClan( ) +{ + return Clan; +} +// return intelligence +unsigned int CPlayer::GetInt( ) +{ + return Attr->Int + Attr->Eint; + +} + +// add item [return item slot [0xffff if couldn't add it]] +unsigned int CPlayer::AddItem( CItem item ) +{ + unsigned int newslot = 0; + newslot = GetNewItemSlot( item ); + if(newslot != 0xffff) + { + if(items[newslot].count > 0) + { + unsigned int ntcount = item.count; + unsigned int utcount = items[newslot].count; + if(ntcount + utcount > 999) + { + item.count = ntcount + utcount - 999; + items[newslot].count = 999; + unsigned int otherslot = GetNewItemSlot( item ); + if(otherslot != 0xffff) + { + if(items[otherslot].count != 0) items[otherslot].count += item.count; + else items[otherslot] = item; + return newslot * 1000 + otherslot; + } + else + { + items[newslot].count = utcount; + return 0xffff; //not inventory space + } + } + else items[newslot].count = ntcount + utcount; + } + else items[newslot] = item; + } + return newslot; +} + +void CPlayer::UpdateInventory( unsigned int slot1, unsigned int slot2 ) +{ + + if(slot1 == 0xffff && slot2 == 0xffff) return; //neither slot is valid + BEGINPACKET( pak, 0x718 ); + if(slot1 != 0xffff && slot2 != 0xffff) {ADDBYTE( pak, 2 );} //both slots are valid + else {ADDBYTE( pak, 1 );} //one of the slots is valid + if(slot1 != 0xffff) + { + ADDBYTE ( pak, slot1); + ADDWORD ( pak, GServer->BuildItemHead( items[slot1] ) ); + ADDDWORD ( pak, GServer->BuildItemData( items[slot1] ) ); + } + if(slot2 != 0xffff) + { + ADDBYTE ( pak, slot2 ); + ADDWORD ( pak, GServer->BuildItemHead( items[slot2] ) ); + ADDDWORD ( pak, GServer->BuildItemData( items[slot2] ) ); + } + client->SendPacket( &pak ); + +} + +void CPlayer::takeSkillCost( CSkills* skill) { + for (int i = 0; i < 2; i++) + { + if (skill->costtype[i] == 0) continue; + Log(MSG_INFO, "Skill cost: %i", skill->costamount[i]); + switch (skill->costtype[i]) + { + case sHP: + Stats->HP -= skill->costamount[i]; + break; + case sMP: + Stats->MP -= (skill->costamount[i] - (skill->costamount[i] * Stats->MPReduction / 100)); + Log(MSG_INFO, "MP: %i", Stats->MP); + break; + case sMoney: + CharInfo->Zulies -= skill->costamount[i]; + break; + case sStamina: + CharInfo->stamina -= skill->costamount[i]; + break; + case sCartGauge: + Log(MSG_INFO, "Couldn't take CartGauge, no variable"); + break; + default: + break; + } + } +} diff --git a/Src/World Server/playerquest.cpp b/Src/World Server/playerquest.cpp new file mode 100644 index 0000000..c0d5e04 --- /dev/null +++ b/Src/World Server/playerquest.cpp @@ -0,0 +1,2 @@ +// This file is no longer needed, will remove from later versions when I get around to it - Drakia + diff --git a/Src/World Server/playerstats.cpp b/Src/World Server/playerstats.cpp new file mode 100644 index 0000000..b5e1811 --- /dev/null +++ b/Src/World Server/playerstats.cpp @@ -0,0 +1,1975 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "player.h" +#include "worldserver.h" + +// Return Dodge +unsigned int CPlayer::GetDodge( ) +{ + UINT Dodge = 0; + Dodge = (UINT)floor(( ((Stats->Level * 0.3) + ((Attr->Dex + Attr->Edex) * 1.9)) + 10 ) * 0.4); + UINT ClothDodge = 0; + + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if (i < 7) + ClothDodge += items[i].durability; + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_DODGE) + { + Dodge += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_DODGE) + { + Dodge += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_DODGE) + Dodge += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_DODGE) + Dodge += GServer->StatsList[items[i].stats].value[1]; + } + } + } + Dodge += (UINT)floor(ClothDodge*0.3); + + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sDodge ) + { + if( skill->value2[j] > 0 ) + Extra += Dodge * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + Dodge += Extra; + + if(Status->Dodge_up!=0xff) + Dodge += MagicStatus[Status->Dodge_up].Value; + if(Status->Dodge_down!=0xff) + Dodge -= MagicStatus[Status->Dodge_down].Value; + if(Dodge<30) + Dodge=30; + return Dodge; +} + +// Return Accury +unsigned int CPlayer::GetAccury( ) +{ + UINT Accury = 0; + if(items[7].count==0 ) + Accury = (UINT)floor( (((Attr->Con + Attr->Econ )+10)*0.5) + 15 ); + else + Accury = (UINT)floor( ((Attr->Con+Attr->Econ+10)*0.8) + ((GServer->EquipList[WEAPON].Index[items[7].itemnum]->quality*0.6) + (items[7].durability*0.8)) ); + UINT extra_refine[10] = { 0, 4, 7, 10, 14, 20, 26, 33, 40, 50}; + if( items[7].refine>0 ) + { + UINT refine = (UINT)floor((double)(items[7].refine/16)); + if(refine<10) + { + Accury += extra_refine[refine]; + } + } + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_ACCUR) + { + Accury += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_ACCUR) + { + Accury += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_ACCUR) + Accury += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_ACCUR) + Accury += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sAccuracy ) + { + if( skill->value2[j] > 0 ) + Extra += Accury * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + Accury += Extra; + + if(Status->Accury_up!=0xff) + Accury += MagicStatus[Status->Dodge_up].Value; + if(Status->Accury_down!=0xff) + Accury -= MagicStatus[Status->Accury_down].Value; + if(Accury<30) + Accury=30; + return Accury; +} + +// Return Critical +unsigned int CPlayer::GetCritical( ) +{ + UINT Critical = 0; + UINT weapontype = 1; + if(items[7].itemnum!=0 && items[7].count > 0) // weapon equipped + weapontype = GServer->EquipList[WEAPON].Index[items[7].itemnum]->type; + Critical = (UINT)floor(((Attr->Con + Attr->Econ + 20)*0.2) + Attr->Sen + Attr->Esen ); + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_CRITICAL) + { + Critical += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_CRITICAL) + { + Critical += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_CRITICAL) + Critical += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_CRITICAL) + Critical += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + //item specific crit boosts + for(int n=0;n<5;n++) + { + if( skill->weapon[n] == weapontype) // item defined in the skill + { + if( skill->value2[j] > 0 ) + Extra += Critical * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + } + Critical += Extra; + + if(Fairy)Critical = (unsigned int)floor(Critical*1.2); + if(Status->Critical_up!=0xff) + Critical += MagicStatus[Status->Critical_up].Value; + if(Status->Critical_down!=0xff) + Critical -= MagicStatus[Status->Critical_down].Value; + if(Critical<10) + Critical=10; + return Critical; +} + +// Return Magic Attack based on the weapon currently equipped +unsigned int CPlayer::GetMagicAttack( ) +{ + if(GServer->EquipList[WEAPON].Index[items[7].itemnum]->magicattack == 1) + return 1; + else + return 0; +} + + +// Return Magic Defense +unsigned int CPlayer::GetMagicDefense( ) +{ + UINT MagicDefense = 0; + UINT Extra = 0; + switch( CharInfo->Job ) + { + case 121:case 122: + case 221:case 222: + case 321:case 322: + case 421:case 422: + Extra += 20; + break; + default: + break; + } + MagicDefense += (UINT)floor(((Attr->Int + Attr->Eint + 5)*0.6) + ((Stats->Level + 15)*0.8)); + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if (i < 9 && items[i].itemtype != WEAPON) + MagicDefense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->magicresistence; + + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_MRESIST) + { + MagicDefense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_MRESIST) + { + MagicDefense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_MRESIST) + MagicDefense += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_MRESIST) + MagicDefense += GServer->StatsList[items[i].stats].value[1]; + } + } + } + + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMagicResistance ) + { + if( skill->value2[j] > 0 ) + Extra += MagicDefense * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + MagicDefense += Extra; + + if(Status->Magic_Defense_up!=0xff) + MagicDefense += MagicStatus[Status->Magic_Defense_up].Value; + if(Status->Magic_Defense_down!=0xff) + MagicDefense -= MagicStatus[Status->Magic_Defense_down].Value; + return MagicDefense; +} + + +// Return Attack +unsigned int CPlayer::GetAttackPower( ) +{ + UINT attack = 0; + UINT weapontype = 0; + UINT weaponatk = 0; + UINT Extra = 0; + UINT skilltype = 0; + switch(CharInfo->Job) + { + case 121:case 122: + case 221:case 222: + case 321:case 322: + case 421:case 422: + Extra += 30; + break; + default: + break; + } + if( items[7].itemnum!=0 && items[7].count > 0 && items[7].durability > 0 ) + { + UINT extra_refine[10] = {0, 7, 14, 21, 31, 41, 51, 65, 80 , 100}; + weaponatk = GServer->EquipList[WEAPON].Index[items[7].itemnum]->attackpower; + weapontype = GServer->EquipList[WEAPON].Index[items[7].itemnum]->type; + if( items[7].refine > 0 ) + { + UINT refine = (UINT)floor((double)(items[7].refine/16)); + if(refine>0 && refine<10) + { + weaponatk += (UINT)floor(extra_refine[refine] * 0.01 * weaponatk); + } + + } + if(Session->codedebug) + { + Log( MSG_INFO, "Base AP with weapon equipped (includes refines) %i",attack ); + } + } + else + { + attack += (UINT)floor( ((Attr->Dex + Attr->Edex) * 0.3) + ((Attr->Str + Attr->Estr) * 0.5) + (Stats->Level * 0.2)); + if(Session->codedebug) + { + Log( MSG_INFO, "AP unarmed %i",attack ); + } + } + switch( weapontype ) + { + case ONE_HAND_SWORD://one hand sword + { + attack += (UINT)floor( (Attr->Str + Attr->Estr) * 0.75 + Stats->Level * 0.2 + ((Attr->Str + Attr->Estr) * 0.05 + 29) * weaponatk * 0.03333334 ); + if(Session->codedebug) + { + Log( MSG_INFO, "AP from sword only is %i",attack ); + } + skilltype = 42; + } + break; + case ONE_HAND_BUNT_SWORD://one hand blunt sword + { + attack += (UINT)floor( (Attr->Str + Attr->Estr)*0.75 + Stats->Level*0.2 + ((Attr->Str + Attr->Estr)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 42; + } + break; + case TWO_HAND_SWORD://two hand sword + { + attack += (UINT)floor( (Attr->Str + Attr->Estr)*0.75 + Stats->Level*0.2 + ((Attr->Str + Attr->Estr)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 43; + } + break; + case SPEAR://spear + { + attack += (UINT)floor( (Attr->Str + Attr->Estr)*0.75 + Stats->Level*0.2 + ((Attr->Str + Attr->Estr)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 43; + } + break; + case TWO_HAND_AXE://two hand axe + { + attack += (UINT)floor( (Attr->Str + Attr->Estr)*0.75 + Stats->Level*0.2 + ((Attr->Str + Attr->Estr)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 43; + } + break; + case BOW://bow + { + float arrows = 0; + if(items[132].count>0) + arrows = GServer->NaturalList.Index[items[132].itemnum]->quality * 0.5; + attack += (UINT)floor((((Attr->Str + Attr->Estr) + Stats->Level) * 0.1) + ((Attr->Dex + Attr->Edex) * 0.04 + (Attr->Sen + Attr->Esen) * 0.03 + 29) * weaponatk * 0.03333334 + (Attr->Dex+Attr->Edex) * 0.52 + arrows); + if(Session->codedebug) + { + Log( MSG_INFO, "AP from Bow + Arrows only is %i",attack ); + } + skilltype = 44; //Bow attack power + } + break; + case GUN://gun + case DUAL_GUN://dual guns + { + float bullets = 0; + if(items[133].count>1) + bullets = GServer->NaturalList.Index[items[133].itemnum]->quality * 0.8; + attack += (UINT)floor( (Attr->Con + Attr->Econ)*0.47 + bullets + Stats->Level*0.1 + (Attr->Dex + Attr->Edex)*0.3 + ((Attr->Con + Attr->Econ)*0.04 + (Attr->Sen + Attr->Esen)*0.05 + 29) * weaponatk * 0.03333334); + skilltype = 45; + } + break; + case LAUNCHER://launcher + { + float cannons = 0; + if(items[134].count>0) + { + cannons = GServer->NaturalList.Index[items[134].itemnum]->quality; + } + attack += (UINT)floor( cannons*0.8 + Stats->Level*0.1 + (Attr->Str + Attr->Estr)*0.32 + (Attr->Con + Attr->Econ)*0.45 + ((Attr->Con + Attr->Econ)*0.04 + (Attr->Sen + Attr->Esen)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 45; + } + break; + case STAFF://staff + { + attack += (UINT)floor( Stats->Level*0.2 + ((Attr->Int + Attr->Eint)+(Attr->Str + Attr->Estr))*0.4 + ((Attr->Int + Attr-> Eint)*0.05 + 29) * weaponatk * 0.03333334 ); + skilltype = 46; + } + break; + case WAND://wand + { + attack += (UINT)floor( Stats->Level*0.2 + (Attr->Int + Attr->Eint)*0.6 + ( ((Attr->Sen + Attr->Esen)*0.1 +26) * 0.03703704 * weaponatk ) ); + skilltype = 46; + } + break; + case KATAR://katar + { + attack += (UINT)floor( Stats->Level*0.2 + (Attr->Str + Attr->Estr)*0.42 + (Attr->Dex + Attr->Edex)*0.55 + ((Attr->Dex + Attr->Edex)*0.05 + 20) * weaponatk * 0.0476190); + skilltype = 48; + } + break; + case DOUBLE_SWORD://double sword + { + attack += (UINT)floor( Stats->Level*0.2 + (Attr->Str + Attr->Estr)*0.63 + (Attr->Dex + Attr->Edex)*0.45 + ((Attr->Dex + Attr->Edex)*0.05 + 25) * weaponatk * 0.03846154); + skilltype = 48; + } + break; + case CROSSBOW://crossbow + { + float arrows = 0; + if(items[132].count>0) + arrows = GServer->NaturalList.Index[items[132].itemnum]->quality*0.5; + attack += (UINT)floor((((Attr->Str + Attr->Estr)+Stats->Level)*0.1) + ((Attr->Dex + Attr->Edex)*0.04 + (Attr->Sen +Attr->Esen)*0.03 + 29) * weaponatk * 0.03333334 + (Attr->Dex + Attr->Edex)*0.52 + arrows); + skilltype = 47; //Crossbow Attack Power + } + break; + default: + break; + } + + for(UINT i=1;i<12;i++)//cloth stats + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == 18) + { + attack += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == 18) + { + attack += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == 18) + attack += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == 18) + attack += GServer->StatsList[items[i].stats].value[1]; + } + } + } + if(Session->codedebug) + { + Log( MSG_INFO, "AP after cloth stats %i",attack ); + } + + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + //weapon specific AP boosts + for(int n=0;n<5;n++) + { + if(weapontype == 0) //no weapon equipped + { + if( skill->buff[j] == 41) //unarmed AP + { + if( skill->value2[j] > 0 ) + Extra += attack * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + else // some kind of weapon equipped + { + if( skill->weapon[n] == weapontype || skill->buff[j] == 18) + { + int xt1 = 0; + int xt2 = 0; + if(skill->buff[j] == skilltype) + { + xt1 = skill->value1[j]; + xt2 = attack * skill->value2[j] / 100; + if(xt1 > xt2) + Extra = xt1; + else + Extra = xt2; + //Extra += attack * skill->value2[j] / 100; + if(Session->codedebug) + { + Log( MSG_INFO, "Extra AP from passive %i",Extra ); + } + } + } + } + } + } + } + attack += Extra; + + if(Session->codedebug) + { + Log( MSG_INFO, "AP final value before fairy and buff check is %i",attack ); + } + if(Fairy)attack = (unsigned int)floor(attack*1.2); + if(Status->Attack_up!=0xff) + attack += MagicStatus[Status->Attack_up].Value; + if(Status->Attack_down!=0xff) + attack -= MagicStatus[Status->Attack_down].Value; + //if(attack<30) + // attack = 30; + return attack; +} + +// Return Defense +unsigned int CPlayer::GetDefense( ) +{ + UINT defense = 0; + UINT Extra = 0; + switch( CharInfo->Job ) + { + case 121:case 122: + case 221:case 222: + case 321:case 322: + case 421:case 422: + Extra += 25; + break; + default: + break; + } + defense += (UINT)floor( (( Attr->Estr + Attr->Str + 5) * 0.35) + ((Stats->Level + 15) * 0.7) ); + UINT extra_refine[10] = {0, 6, 12, 18, 27, 36, 45, 57, 70 , 85}; + for(UINT i=1;i<9;i++) + { + if(i==7) + continue; + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + defense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->defense; + if(items[i].refine>0) + { + UINT refine = (UINT)floor((double)(items[i].refine/16)); + if(refine<10) + { + defense += (UINT)floor(extra_refine[refine] * 0.01 * GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->defense); + } + } + } + } + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_DEFENSE) + { + defense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_DEFENSE) + { + defense += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_DEFENSE) + defense += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_DEFENSE) + defense += GServer->StatsList[items[i].stats].value[1]; + } + } + } + // Additional def due to pskills + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sDefense || skill->buff[j] == sDefense2 ) + { + if( skill->value2[j] > 0 ) + Extra += defense * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + //Shield specific def boosts. previously not included. + for(int n=0;n<5;n++) + { + if( skill->weapon[n] == items[8].type && items[8].count > 0) // item defined in the skill is currently equipped in the shield slot + { + if( skill->value2[j] > 0 ) + Extra += defense * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + // this is BS sShield = 35 and that is "Shield Damage" not defense + //if (items[8].count > 0 && skill->buff[j] == sShield) + //{ + // if( skill->value2[j] > 0 ) + // Extra += defense * skill->value2[j] / 100; + // if( skill->value1[j] > 0 ) + // Extra += skill->value1[j]; + //} + } + } + defense += Extra; + if(Fairy) defense = (unsigned int)floor(defense*1.2); + if(Status->Defense_up!=0xff) + defense += MagicStatus[Status->Defense_up].Value; + if(Status->Defense_down!=0xff) + defense -= MagicStatus[Status->Defense_down].Value; + return defense; +} + +// Return Attack Speed +unsigned int CPlayer::GetAttackSpeed( ) +{ + UINT aspeed = 0; + if( items[7].itemnum!=0 && items[7].count > 0 ) + { + UINT wpnspd = 0; + wpnspd = GServer->EquipList[WEAPON].Index[items[7].itemnum]->attackspeed; + switch(wpnspd) + { + case 12: //normal + aspeed = 88; + break; + case 11: //+1 + aspeed = 93; + break; + case 10: //+2 + aspeed = 100; + break; + case 9: //+3 + aspeed = 107; + break; + case 8: //+4 + aspeed = 115; + break; + case 7: //+5 + aspeed = 125; + break; + case 6: //+6 + aspeed = 136; + break; + default: + break; + } + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Weapon speed %i. Attack speed so far %i",wpnspd,aspeed); + } + } + else + { + aspeed = 115; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Attack speed with no weapon %i",aspeed); + } + } + + UINT weapontype = 0; + UINT skilltype = 0; + weapontype = GServer->EquipList[WEAPON].Index[items[7].itemnum]->type; + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + bool GotWeapon = false; //do I have one of the listed weapons? + bool GotRightWeapon = false; //Do i have the right one though? + if( skill->weapon[0] == 0) + { + GotWeapon = true; + } + else + { + for(int n=0;n<5;n++) + { + if( skill->weapon[n] != 0 && skill->weapon[n] == weapontype) // item defined in the skill + { + GotWeapon = true; + } + } + } + if(GotWeapon) //No point checking if we have the RIGHT weapon when we don't have any of them + { + for(UINT j = 0; j < 2; j++ ) + { + GotRightWeapon = false; + switch(skill->buff[j]) + { + case 24: //attackspeed + GotRightWeapon = true; //true for all weapons listed + break; + case 49: //bow speed + if(weapontype == 231 || weapontype == 271) + GotRightWeapon = true; //true if we have a bow or crossbow + break; + case 50: //gun speed + if(weapontype == 232 || weapontype == 253) + GotRightWeapon = true; //true if we have a gun or dual guns + break; + case 51: //combat weapon speed + if(weapontype == 251 || weapontype == 252) + GotRightWeapon = true; //true if we have a katar or dual swords + break; + case 118: //one hand sword attack speed + if(weapontype == 211) + GotRightWeapon = true; //true if we have a one hand sword + break; + case 119: //one hand melee weapon attack speed + if(weapontype == 212) + GotRightWeapon = true; //true if we have a one hand melee weapon + break; + case 120: //two hand sword attack speed + if(weapontype == 221) + GotRightWeapon = true; //true if we have a two hand sword + break; + case 121: //spear attack speed + if(weapontype == 222) + GotRightWeapon = true; //true if we have a spear + break; + case 122: //two hand axe attack speed + if(weapontype == 223) + GotRightWeapon = true; //true if we have an axe + break; + default: + break; + } + if(GotRightWeapon) + { + if( skill->value2[j] > 0 ) + Extra += aspeed * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Attack speed %i added for weapon type %i. Skill ID %i",Extra,weapontype,skill->id); + } + } + } + } + } + aspeed += Extra; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Attack speed after passives %i",aspeed); + } + + //active buffs + if(Status->Haste_up!=0xff) + aspeed += MagicStatus[Status->Haste_up].Value; + if(Status->Haste_down!=0xff) + aspeed -= MagicStatus[Status->Haste_down].Value; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Attack speed after buffs %i",aspeed); + } + + //cloth stats + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_HASTE) + { + aspeed += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_HASTE) + { + aspeed += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + //don't need gems. Covered by the stats section + if(items[i].stats>0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_HASTE) + aspeed += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_HASTE) + aspeed += GServer->StatsList[items[i].stats].value[1]; + } + } + } + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Attack speed after cloth stats and gems %i",aspeed); + } + return aspeed; +} + +// Return Movement Speed +unsigned int CPlayer::GetMoveSpeed( ) +{ + UINT mspeed = 425; + if(!Status->CanRun) + Status->Stance = WALKING; + //mspeed += UINT((Attr->Dex)*0.85); + switch(Status->Stance) + { + case WALKING: //walking + mspeed = 200; + break; + case 1: + case RUNNING: // running + { + if( items[6].count != 0 )//Foot + { + UINT tspeed = GServer->EquipList[SHOE].Index[items[6].itemnum]->movespeed; + mspeed += ( tspeed - 65 ) * 5; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed with shoes on %i",mspeed); + } + } + for(UINT i=0;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype > 9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == MOVE_SPEED) + { + mspeed += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == MOVE_SPEED) + { + mspeed += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %i after equipment stats for item in slot %i",mspeed,i); + } + } + if(items[i].stats > 0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_DASH) + { + mspeed += GServer->StatsList[items[i].stats].value[0]; + } + if(GServer->StatsList[items[i].stats].stat[1] == A_DASH) + { + mspeed += GServer->StatsList[items[i].stats].value[1]; + } + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %i after adding extra/gem stats for item in slot %i",mspeed,i); + } + } + } + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %i after adding all equipped items",mspeed); + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMoveSpeed || skill->buff[j] == sMoveSpeed2 ) + { + if( skill->value2[j] > 0 ) + Extra += mspeed * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + mspeed += Extra; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %i after adding passive skills",mspeed); + } + Stats->Mspd_base = mspeed; + if(Fairy) + { + mspeed += 110; + if(Session->codedebug) //Debug code + { + Log( MSG_INFO, "Fairy detected. move speed + 110 %i", mspeed); + } + } + if(Status->Dash_up!=0xff) + { + mspeed += MagicStatus[Status->Dash_up].Value; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Speed buff detected. amount: %i new move speed: %i",MagicStatus[Status->Dash_up].Value, mspeed); + } + } + if(Status->Dash_down!=0xff) + { + mspeed -= MagicStatus[Status->Dash_down].Value; + + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Speed DE-buff detected. amount: %i new move speed: %i",MagicStatus[Status->Dash_down].Value, mspeed); + } + } + + } + break; + case DRIVING: //cart or gear + { + //Log(MSG_INFO, "player is driving"); + float m_speed = 0; + int patType = GServer->PatList.Index[items[135].itemnum]->type; + //if(this->items[135].itemnum > 30 && this->items[135].itemnum < 40 ) //Castle gear body + if(patType == 512) //Castle gear body + { + m_speed = 1000; + float mod1 = (float)GServer->PatList.Index[items[136].itemnum]->speed; + mod1 /= 100; //engine + float mod2 = (float)GServer->PatList.Index[items[137].itemnum]->speed; + mod2 /= 100; // legs + m_speed *= mod1; + m_speed *= mod2; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %i riding a Castle Gear",m_speed); + } + } + else if(patType == 511) //Cart body + { + m_speed = 1000; + float mod1 = (float)GServer->PatList.Index[items[136].itemnum]->speed; + mod1 /= 100; //engine + float mod2 = (float)GServer->PatList.Index[items[137].itemnum]->speed; + mod2 /= 100; //wheels + m_speed *= mod1; + m_speed *= mod2; + // Many accessory items give a speed boost too. Added in after the modifiers + if(GServer->PatList.Index[items[138].itemnum]->condition[0] == 23) // accessory + m_speed += (float)GServer->PatList.Index[items[138].itemnum]->modifier[0]; + if(GServer->PatList.Index[items[138].itemnum]->condition[1] == 23) // accessory + m_speed += (float)GServer->PatList.Index[items[138].itemnum]->modifier[1]; + // some frames give a speed boost too. Added in after the modifiers + if(GServer->PatList.Index[items[135].itemnum]->condition[0] == 23) // frame + m_speed += (float)GServer->PatList.Index[items[135].itemnum]->modifier[0]; + if(GServer->PatList.Index[items[135].itemnum]->condition[1] == 23) // frame + m_speed += (float)GServer->PatList.Index[items[135].itemnum]->modifier[1]; + // some engines give a speed boost too. Added in after the modifiers + if(GServer->PatList.Index[items[136].itemnum]->condition[0] == 23) // engine + m_speed += (float)GServer->PatList.Index[items[135].itemnum]->modifier[0]; + if(GServer->PatList.Index[items[136].itemnum]->condition[1] == 23) // engine + m_speed += (float)GServer->PatList.Index[items[135].itemnum]->modifier[1]; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Move speed %f riding a Cart",m_speed); + } + } + else + { + Log(MSG_ERROR,"What the heck is this character driving? it is type %i", patType); + } + mspeed = (int)floor(m_speed); + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Final Speed before fairy buffs added: %i", mspeed); + } + Stats->Mspd_base = mspeed; + if(Fairy) + { + //mspeed = (unsigned int)floor(mspeed * 1.2); + mspeed += 110; + if(Session->codedebug) //Debug code + { + GServer->SendPM(this, "Fairy detected. move speed * 1.2 %i", mspeed); + } + } + } + break; + default: + break; + } + return mspeed; +} + +// Return Max HP +unsigned int CPlayer::GetMaxHP( ) +{ + if (Stats->Level<1) Stats->Level=1; + //unsigned int hpmax = (unsigned int)floor(((sqrt(Stats->Level + 20) * (Stats->Level + 5 )) * 3.5) + ((Attr->Str + Attr->Estr) << 1)); + //unsigned int hpmax1 = (unsigned int)floor((0.1555 * Stats->Level * Stats->Level) + (13.812 * Stats->Level) + 45 + ((Attr->Str + Attr->Estr) * 2)); + + // Base stats for Visitor + float multi = 2.36; + int add1 = 26; + int add2 = 4; + // Now change them based on class + switch (CharInfo->Job) { + case jSoldier: + multi = 3.5; + add1 = 20; + add2 = 5; + break; + case jKnight: + multi = 3.5; + add1 = 28; + add2 = 5; + break; + case jChampion: + multi = 3.5; + add1 = 22; + add2 = 5; + break; + + case jMuse: + multi = 2.36; + add1 = 26; + add2 = 4; + break; + case jMage: + multi = 2.37; + add1 = 26; + add2 = 5; + break; + case jCleric: + multi = 2.4; + add1 = 26; + add2 = 7; + break; + + + case jHawker: + multi = 2.7; + add1 = 20; + add2 = 5; + break; + case jRaider: + multi = 3.0; + add1 = 23; + add2 = 5; + break; + case jScout: + multi = 2.7; + add1 = 21; + add2 = 5; + break; + + case jDealer: + case jBourgeois: + case jArtisan: + multi = 2.7; + add1 = 20; + add2 = 5; + break; + default: + break; + } + // Max HP calc from client + UINT Extra = 0; + float ModLevel = add1 + Stats->Level; + UINT hpmax = (UINT)floor((( sqrt(ModLevel) * (add2 + Stats->Level) ) * multi) + ((Attr->Str + Attr->Estr) << 1) ); + // Stat boost for second jobs + switch( CharInfo->Job ) + { + case 121:case 122: + case 221:case 222: + case 321:case 322: + case 421:case 422: + Extra += 300; + break; + default: + break; + } + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_MAX_HP || GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_HP ) + { + hpmax += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_MAX_HP || GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_HP ) + { + hpmax += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_MAX_HP || GServer->StatsList[items[i].stats].stat[0] == A_HP) + hpmax += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_MAX_HP || GServer->StatsList[items[i].stats].stat[0] == A_HP) + hpmax += GServer->StatsList[items[i].stats].value[1]; + } + } + } + + // Additional hp due to pskills + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMaxHP || skill->buff[j] == sMaxHP2 ) + { + if( skill->value2[j] > 0 ) + Extra += hpmax * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + hpmax += Extra; + if(Fairy) hpmax = (unsigned int)floor(hpmax*1.2); + if(Status->HP_up!=0xff) + hpmax += MagicStatus[Status->HP_up].Value; + if(Status->HP_down!=0xff) + hpmax -= MagicStatus[Status->HP_down].Value; + return hpmax; +} +// Return Max MP [fixed by Tomiz] TODO: add cloth stat +unsigned int CPlayer::GetMaxMP( ) +{ + + if( Stats->Level < 0 ) + Stats->Level = 1; + UINT maxmp = 0; + float Mult = 0; + int JobValue = (int)CharInfo->Job; + switch(JobValue) + { + case 111: + case 411: + case 421: + Mult = 4; + JobValue = 3; + break; + case 122: + Mult = 5; + JobValue = -6; + break; + case 211: + Mult = 6; + JobValue = 0; + break; + case 221: + Mult = 7; + JobValue = -7; + break; + case 222: + Mult = 6.5; + JobValue = -4; + break; + case 311: + case 321: + Mult = 4; + JobValue = 4; //LMA + break; + case 121: + case 322: + case 422: + Mult = 4.5; + JobValue = 0; + break; + default: + Mult = 3; + JobValue = 4; + break; + } + maxmp = (UINT)floor((Stats->Level + JobValue) * Mult + ((Attr->Int + Attr->Eint) << 2)); + + + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_MAX_MP || GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_MP ) + { + maxmp += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_MAX_MP || GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_MP ) + { + maxmp += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_MAX_MP || GServer->StatsList[items[i].stats].stat[0] == A_MP) + maxmp += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_MAX_MP || GServer->StatsList[items[i].stats].stat[0] == A_MP) + maxmp += GServer->StatsList[items[i].stats].value[1]; + } + } + } + + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMaxMP || skill->buff[j] == sMaxMP2 ) + { + if( skill->value2[j] > 0 ) + Extra += maxmp * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + maxmp += Extra; + + if(Fairy) maxmp = (unsigned int)floor(maxmp*1.2); + if(Status->MP_up!=0xff) + maxmp += MagicStatus[Status->MP_up].Value; + if(Status->MP_down!=0xff) + maxmp -= MagicStatus[Status->MP_down].Value; + + return maxmp; +} + +// Get HP Regeneration Amount +unsigned int CPlayer::GetHPRegenAmount( ) +{ + UINT amount = (UINT)ceil ( Stats->MaxHP * 0.02 ); + if( Status->Stance == 1 ) + amount *= 4; + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_HP_REC_RATE) + { + amount += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_HP_REC_RATE) + { + amount += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_HP_REC_RATE ) + amount += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_HP_REC_RATE ) + amount += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sHPRate || skill->buff[j] == sHPRecovery ) // Not sure on sHPRate + { + if( skill->value2[j] > 0 ) + Extra += amount * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + amount += Extra; + + return amount; +} + +// Get MP Regeneration Amount +unsigned int CPlayer::GetMPRegenAmount( ) +{ + UINT amount = (UINT)ceil ( Stats->MaxMP * 0.02 ); + if( Status->Stance == 1 ) + amount *= 4; + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_MP_REC_RATE) + { + amount += amount * GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1] / 100; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_MP_REC_RATE) + { + amount += amount * GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1] / 100; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_MP_REC_RATE ) + amount += amount * GServer->StatsList[items[i].stats].value[0] / 100; + if(GServer->StatsList[items[i].stats].stat[1] == A_MP_REC_RATE ) + amount += amount * GServer->StatsList[items[i].stats].value[1] / 100; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMPRate || skill->buff[j] == sMPRecovery ) + { + if( skill->value2[j] > 0 ) + Extra += amount * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + amount += Extra; + return amount; +} + +// max weight supported +unsigned int CPlayer::GetMaxWeight( ) +{ + UINT weight = 0; + //weight = ((Attr->Str + Attr->Estr) + (Attr->Str + Attr->Estr)*2) * 2 + Stats->Level+220 + (Stats->Level+220)*4; + weight = ((Attr->Str + Attr->Estr)*6)+((Stats->Level+220)*5); + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == A_INVENTORY_CAPACITY) + { + weight += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == A_INVENTORY_CAPACITY) + { + weight += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == A_INVENTORY_CAPACITY ) + weight += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == A_INVENTORY_CAPACITY ) + weight += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMaxWeight || skill->buff[j] == sMaxWeight2 ) + { + if( skill->value2[j] > 0 ) + Extra += weight * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + + weight += Extra; + return weight; +} + +// Current weight +unsigned int CPlayer::GetCurrentWeight( ) +{ + UINT weight = 0; + for(UINT i=0;i 0) + { + weight += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->weight; + } + else + { + switch(items[i].itemtype) + { + case CONSUMIBLE: + weight += GServer->UseList.Index[items[i].itemnum]->weight*items[i].count; + break; + case JEM: + weight += GServer->JemList.Index[items[i].itemnum]->weight*items[i].count; + break; + case NATURAL: + weight += GServer->NaturalList.Index[items[i].itemnum]->weight*items[i].count; + break; + case PAT: + weight += GServer->PatList.Index[items[i].itemnum]->weight; + break; + default: + break; + } + } + } + return weight; +} + +// MP Reduction +unsigned int CPlayer::GetMPReduction( ) +{ + UINT mpreduction = 0; + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if(items[i].count>0) + { + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == MP_COST_RED) + { + mpreduction += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == MP_COST_RED) + { + mpreduction += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == MP_COST_RED ) + mpreduction += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == MP_COST_RED ) + mpreduction += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sMPSpendDecrease ) + { + if( skill->value2[j] > 0 ) + Extra += mpreduction * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + mpreduction += Extra; + return mpreduction; +} + +// Max summon capacity +unsigned int CPlayer::GetMaxSummonGauge( ) +{ + UINT gauge = 50; + for(UINT i=1;i<12;i++)//cloth stats [from tomiz] + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if(items[i].count>0) + { + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == SUMMON_GAUGE) + { + gauge += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == SUMMON_GAUGE) + { + gauge += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + } + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == MP_COST_RED ) + gauge += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == MP_COST_RED ) + gauge += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sExpandSummonGauge ) + { + if( skill->value2[j] > 0 ) + Extra += gauge * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + gauge += Extra; + + return gauge; +} + +// Extra attributes [str/dex/con/cha/sen/int] +void CPlayer::GetExtraStats( ) +{ + Attr->Estr = 0; + Attr->Edex = 0; + Attr->Eint = 0; + Attr->Econ = 0; + Attr->Esen = 0; + Attr->Echa = 0; + for(UINT i=1;i<12;i++)//cloth stats + { + if( items[i].count > 0 ) + { + UINT Stat1 = GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0]; + UINT Stat2 = GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0]; + switch(Stat1) + { + case A_STR: Attr->Estr += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + case A_DEX: Attr->Edex += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + case A_INT: Attr->Eint += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + case A_CON: Attr->Econ += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + case A_CHA: Attr->Echa += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + case A_SEN: Attr->Esen += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; break; + default: + break; + } + switch(Stat2) + { + case A_STR: Attr->Estr += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + case A_DEX: Attr->Edex += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + case A_INT: Attr->Eint += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + case A_CON: Attr->Econ += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + case A_CHA: Attr->Echa += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + case A_SEN: Attr->Esen += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; break; + default: + break; + } + // Why are we doing jems and stats seperately? they are the same thing O.o + /*if(items[i].gem != 0) // jem stats + { + //somehow a player had a negative value in his gem on an item. Put this code in to patch it. + if(items[i].gem < 0 || items[i].stats < 0) + { + items[i].gem = 0; + items[i].stats = 0; + } + + UINT Stat1 = GServer->JemList.Index[items[i].gem]!=NULL?GServer->JemList.Index[items[i].gem]->stat1[0]:0; + UINT Stat2 = GServer->JemList.Index[items[i].gem]!=NULL?GServer->JemList.Index[items[i].gem]->stat2[0]:0; + switch(Stat1) + { + case STRENGTH: Attr->Estr += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + case DEXTERY: Attr->Edex += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + case INTELIGENCE: Attr->Eint += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + case CONCENTRATION: Attr->Econ += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + case CHARM: Attr->Echa += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + case SENSE: Attr->Esen += GServer->JemList.Index[items[i].gem]->stat1[1]; break; + } + switch(Stat2) + { + case STRENGTH: Attr->Estr += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + case DEXTERY: Attr->Edex += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + case INTELIGENCE: Attr->Eint += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + case CONCENTRATION: Attr->Econ += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + case CHARM: Attr->Echa += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + case SENSE: Attr->Esen += GServer->JemList.Index[items[i].gem]->stat2[1]; break; + } + } */ + if(items[i].stats != 0 && items[i].stats < 500) // this should cover all the gems too + { + Stat1 = GServer->StatsList[items[i].stats].stat[0]; + Stat2 = GServer->StatsList[items[i].stats].stat[1]; + switch(Stat1) + { + case STRENGTH: Attr->Estr += GServer->StatsList[items[i].stats].value[0]; break; + case DEXTERY: Attr->Edex += GServer->StatsList[items[i].stats].value[0]; break; + case INTELIGENCE: Attr->Eint += GServer->StatsList[items[i].stats].value[0]; break; + case CONCENTRATION: Attr->Econ += GServer->StatsList[items[i].stats].value[0]; break; + case CHARM: Attr->Echa += GServer->StatsList[items[i].stats].value[0]; break; + case SENSE: Attr->Esen += GServer->StatsList[items[i].stats].value[0]; break; + default: + break; + } + switch(Stat2) + { + case STRENGTH: Attr->Estr += GServer->StatsList[items[i].stats].value[1]; break; + case DEXTERY: Attr->Edex += GServer->StatsList[items[i].stats].value[1]; break; + case INTELIGENCE: Attr->Eint += GServer->StatsList[items[i].stats].value[1]; break; + case CONCENTRATION: Attr->Econ += GServer->StatsList[items[i].stats].value[1]; break; + case CHARM: Attr->Echa += GServer->StatsList[items[i].stats].value[1]; break; + case SENSE: Attr->Esen += GServer->StatsList[items[i].stats].value[1]; break; + default: + break; + } + } + } + } +} + +float CPlayer::GetAttackDistance( ) +{ + if(items[7].count==0) return (float)1; + float AttackDistance = 0; + AttackDistance = GServer->EquipList[WEAPON].Index[items[7].itemnum]->attackdistance/100; + return AttackDistance; +} + +// Return XPRate +unsigned int CPlayer::GetXPRate( ) +{ + UINT XPRate = 0; + if(Stats->ItemXPRate == -1) + return 0; // sends back a rate of 0. Used for exp nulification + + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype>9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == XP_RATE) + XPRate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == XP_RATE) + XPRate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + if(items[i].stats>0 && items[i].stats<500) + { + if(GServer->StatsList[items[i].stats].stat[0] == XP_RATE) + XPRate += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == XP_RATE) + XPRate += GServer->StatsList[items[i].stats].value[1]; + } + } + } + if(Stats->ItemXPRate != 0 && Stats->ItemXPRate != 1 && Stats->ItemXPRate != 2) + Stats->ItemXPRate = 0; + XPRate += Stats->ItemXPRate; // add timed XPRate buffs + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == sEXPRate ) + { + if( skill->value2[j] > 0 ) + Extra += XPRate * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + CMap* map = GServer->MapList.Index[Position->Map]; + + XPRate += Extra; + + if(XPRate < 1) + XPRate = 1; + XPRate = XPRate * map->mapXPRate; + //Log( MSG_INFO, "XP rate calculated as = %i", XPRate); + Log( MSG_INFO, "XPRate: %i Map XP rate: %i for map %i",XPRate,map->mapXPRate, map->id); + return XPRate; +} + +// Return Item Drop Rate +unsigned int CPlayer::GetItemDropRate( ) +{ + int itemdroprate = 0; + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype > 9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == 79 ) //ITEM_DROP_RATE + itemdroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == 79 ) //ITEM_DROP_RATE + itemdroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + if(items[i].stats > 0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == 79 ) //ITEM_DROP_RATE + itemdroprate += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == 79 ) //ITEM_DROP_RATE + itemdroprate += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == 79 ) //sItemDropIncrease + { + if( skill->value2[j] > 0 ) + Extra += itemdroprate * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + itemdroprate += Extra; + //Log( MSG_INFO, "item drop rate calculated as = %i", itemdroprate); + if(itemdroprate < 0) + itemdroprate = 0; + //itemdroprate += GServer->Config.DROP_RATE; + //Log( MSG_INFO, "item drop rate calculated as = %i", itemdroprate); + //Log( MSG_INFO, "Server item drop rate = %i", GServer->Config.DROP_RATE); + return itemdroprate; +} + +// Return Item Drop count increase +unsigned int CPlayer::GetItemDropCountRate( ) +{ + int itemdroprate = 0; + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype > 9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == 63 ) //ITEM_DROP_Count RATE + itemdroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == 63 ) //ITEM_DROP_Coiunt RATE + itemdroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + if(items[i].stats > 0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == 63 ) //ITEM_DROP_Count RATE + itemdroprate += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == 63 ) //ITEM_DROP_Count RATE + itemdroprate += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == 63 ) //Item drop Count rate + { + if( skill->value2[j] > 0 ) + Extra += itemdroprate * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + itemdroprate += Extra; + //Log( MSG_INFO, "item drop rate calculated as = %i", itemdroprate); + if(itemdroprate < 0) + itemdroprate = 0; + //itemdroprate += GServer->Config.DROP_RATE; + //Log( MSG_INFO, "item drop rate calculated as = %i", itemdroprate); + //Log( MSG_INFO, "Server item drop rate = %i", GServer->Config.DROP_RATE); + return itemdroprate; +} + +// Return Zuly Drop Rate +unsigned int CPlayer::GetZulyDropRate( ) +{ + int zulydroprate = 1; + for(UINT i=1;i<12;i++) + { + if( items[i].count != 0 ) + { + if(items[i].itemtype > 9) + { + Log(MSG_WARNING, "Char %s have equip invalid item: %i,%i", CharInfo->charname, items[i].itemtype, items[i].itemnum ); + continue; + } + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[0] == 64 ) //ZULY_DROP_RATE + zulydroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat1[1]; + if( GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[0] == 64 ) //ZULY_DROP_RATE + zulydroprate += GServer->EquipList[items[i].itemtype].Index[items[i].itemnum]->stat2[1]; + if(items[i].stats > 0 && items[i].stats < 500) + { + if(GServer->StatsList[items[i].stats].stat[0] == 64 ) //ZULY_DROP_RATE + zulydroprate += GServer->StatsList[items[i].stats].value[0]; + if(GServer->StatsList[items[i].stats].stat[1] == 64 ) //ZULY_DROP_RATE + zulydroprate += GServer->StatsList[items[i].stats].value[1]; + } + } + } + UINT Extra = 0; + for(UINT i=0;i<30;i++) + { + if (pskill[i] == 0) + continue; + CSkills* skill = GServer->GetSkillByID( (pskill[i])+(pskilllvl[i])); + if( skill == NULL ) + continue; + for(UINT j = 0; j < 2; j++ ) + { + if( skill->buff[j] == 64 ) //sItemZulyIncrease + { + if( skill->value2[j] > 0 ) + Extra += zulydroprate * skill->value2[j] / 100; + if( skill->value1[j] > 0 ) + Extra += skill->value1[j]; + } + } + } + zulydroprate += Extra; + if(zulydroprate < 1) + zulydroprate = 1; + //Log( MSG_INFO, "zuly drop rate calculated as = %i", zulydroprate); + return zulydroprate; +} + + + + +// calculate player stats +void CPlayer::SetStats( ) +{ + GetExtraStats( ); + Stats->MaxHP = GetMaxHP( ); + Stats->MaxMP = GetMaxMP( ); + Stats->Attack_Power = GetAttackPower( ); + Stats->Defense = GetDefense( ); + Stats->Critical = GetCritical( ); + Stats->Move_Speed = GetMoveSpeed( ); + //Stats->Mspd_base = GetMoveSpeed(1); + Stats->Magic_Defense = GetMagicDefense( ); + Stats->magicattack = GetMagicAttack( ); + Stats->Accury = GetAccury( ); + Stats->Dodge = GetDodge( ); + Stats->Attack_Speed = GetAttackSpeed( ); + Stats->Attack_Distance = GetAttackDistance( ); + Stats->MaxWeight = GetMaxWeight( ); + Stats->MaxSummonGauge = GetMaxSummonGauge( ); + Stats->MPReduction = GetMPReduction( ); + Stats->xprate = GetXPRate( ); + Stats->itemdroprate = GetItemDropRate( ); + Stats->itemdropcountrate = GetItemDropCountRate( ); + Stats->zulydroprate = GetZulyDropRate( ); +} + diff --git a/Src/World Server/quest.cpp b/Src/World Server/quest.cpp new file mode 100644 index 0000000..a68f313 --- /dev/null +++ b/Src/World Server/quest.cpp @@ -0,0 +1,125 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +/*bool CWorldServer::DoQuestScript( CPlayer* thisclient, CQuest* thisquest ) +{ + BEGINPACKET( pak, 0 ); + switch( thisquest->script ) + { + case 1: //Teleport + { + if(thisquest->value3>0) + { + thisclient->CharInfo->Zulies -= thisquest->value3; + RESETPACKET( pak, 0x71e ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + } + fPoint thispoint; + thispoint.x = floor( thisquest->value2/10000 ); + thispoint.y = floor( thisquest->value2%10000 ); + TeleportTo ( thisclient, thisquest->value1, thispoint ); + thisclient->EndQuest( thisquest->questid ); + } + break; + case 2: // Change Job + { + if( thisclient->CharInfo->Job == 0 ) + { + thisclient->CharInfo->Job = thisquest->value1; + RESETPACKET(pak, 0x721 ); + ADDWORD (pak, 0x0004 ); + ADDWORD (pak, thisclient->CharInfo->Job ); + ADDWORD (pak, 0x0000 ); + thisclient->client->SendPacket(&pak); + } + } + break; + case 20: // 2nd Change Job + { //Credits to tomiz + if( thisclient->CharInfo->Job > 0 ) + { + thisclient->CharInfo->Job = thisquest->value1; + RESETPACKET(pak, 0x721 ); + ADDWORD (pak, 0x0004 ); + ADDWORD (pak, thisclient->CharInfo->Job ); + ADDWORD (pak, 0x0000 ); + thisclient->client->SendPacket(&pak); + } + if(thisquest->value3>0) // zuly need for second job + { + thisclient->CharInfo->Zulies-= thisquest->value3; + RESETPACKET( pak, 0x71e ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + } + } + break; + case 3: //Give Stats/Skill points + { + switch( thisquest->value1 ) + { + case 1: // Give Stats points + thisclient->CharInfo->StatPoints += thisquest->value2; + RESETPACKET( pak, 0x720 ); + ADDWORD ( pak, 0x0020 ); + ADDDWORD ( pak, thisquest->value2 ); + thisclient->client->SendPacket( &pak ); + break; + case 2: // Give Skill points + thisclient->CharInfo->SkillPoints += thisquest->value2; + break; + } + } + break; + case 4: //Restart Stats/Skills points + switch( thisquest->value1 ) + { + case 1: // Stats points [fixed by Geobot] + thisclient->CharInfo->StatPoints = 0; + for(UINT i=2;i<=thisclient->Stats->Level;i++) + thisclient->CharInfo->StatPoints += 10 + (i/2); + thisclient->Attr->Str = 15; + thisclient->Attr->Dex = 15; + thisclient->Attr->Int = 15; + thisclient->Attr->Con = 15; + thisclient->Attr->Cha = 10; + thisclient->Attr->Sen = 10; + break; + case 2: // Skill points + + break; + } + break; + case 10: //Delete a quest + if( thisquest->value1==0 ) + return true; + CQuest* athisquest = GetQuestByID( thisquest->value1 ); + QUESTS* myquest = thisclient->GetQuestByQuestID( athisquest->questid ); + if( myquest!=0 ) + myquest->active = false; + break; + } + return true; +}*/ diff --git a/Src/World Server/quest/CIntStb.hpp b/Src/World Server/quest/CIntStb.hpp new file mode 100644 index 0000000..a83d877 --- /dev/null +++ b/Src/World Server/quest/CIntStb.hpp @@ -0,0 +1,55 @@ +// Props to ExJam for this code :D + +class CIntStb +{ +public: + CIntStb() : rows(0) {} + CIntStb(char* filename) : rows(0){ LoadSTB(filename); } + ~CIntStb(){ CleanUp(); } + + int LoadSTB( char* filename ){ + FILE* fh; + char tmpfield[512]; + unsigned dataoffset = 0; + unsigned short fieldlen = 0; + + if ( (fh = fopen( filename, "rb" )) != 0 ) { + Log( MSG_ERROR, "Could not load STB '%s'", filename ); + return 1; + }else{ + fseek( fh, 4, SEEK_SET ); + fread( &dataoffset, 4, 1, fh ); + fread( &rowcount, 4, 1, fh ); + fread( &fieldcount, 4, 1, fh ); + + rowcount--; + fieldcount--; + + fseek( fh, dataoffset, SEEK_SET ); + int* tmp = new int[rowcount*fieldcount]; + rows = new int*[rowcount]; + for(dword i = 0; i < rowcount; i++ ) rows[i] = &tmp[i*fieldcount]; + for(dword j = 0; j < rowcount*fieldcount; j++) { + fread( &fieldlen, 2, 1, fh ); + fread( tmpfield, 1, fieldlen, fh ); + tmpfield[fieldlen]=0; + tmp[j] = atoi( tmpfield ); + } + fclose( fh ); + } + + return 0; + } + + dword rowcount; + dword fieldcount; + int** rows; + +private: + void CleanUp(){ + if(rows != NULL){ + delete [] rows[0]; + delete [] rows; + } + } +}; diff --git a/Src/World Server/quest/CRoseArray.hpp b/Src/World Server/quest/CRoseArray.hpp new file mode 100644 index 0000000..c6a5064 --- /dev/null +++ b/Src/World Server/quest/CRoseArray.hpp @@ -0,0 +1,120 @@ +//Made by Caali + +#define __CARRAY_HPP__ + +/* Includes */ +#include + +/* Variable type definitions */ +typedef size_t CARRAY_SIZE; + +/* Defines */ +#ifndef NULL +# ifdef __cplusplus +# define NULL 0 +# else +# define NULL (void*)0 +# endif +#endif + +/* A basic CArray class. It is being used because C++ has no function to get the actual size in memory of a pointer. */ +template +class CRoseArray +{ + private: + CARRAY_SIZE arraysize; + public: + T* arr; + CRoseArray( CARRAY_SIZE arrsize ) + { + this->arr = NULL; + this->arraysize = 0; + this->arr = new T[arrsize]; + if( this->arr != NULL ) + { + this->arraysize = arrsize; + Clean( ); + } + } + + ~CRoseArray( ) + { + if( this->arr != NULL ) + { + delete[] this->arr; + this->arraysize = 0; + } + } + + void resize( CARRAY_SIZE newsize ) + { + if( this->arr == NULL ) return; + T* tmp = new T[(size_t)(this->arraysize)]; + CARRAY_SIZE tmpsize = this->arraysize; + memcpy( tmp, this->arr, (size_t)(this->arraysize*sizeof(T)) ); + delete[] this->arr; + this->arr = new T[(size_t)(newsize)]; + if( this->arr != NULL ) + { + memcpy( this->arr, tmp, (size_t)(tmpsize * sizeof(T)) ); + delete[] tmp; + this->arraysize = newsize; + } + else + { + delete[] tmp; + this->arraysize = 0; + } + } + + void Clean( ) + { + if( this->arr != NULL ) + { + for( CARRAY_SIZE i = 0; i < this->arraysize; i++ ) + { + this->arr[i] = 0; + } + } + } + CARRAY_SIZE size() + { + return this->arraysize; + } + CARRAY_SIZE absSize() + { + return (this->arraysize*sizeof(T)); + } + inline CRoseArray& operator = ( const CRoseArray& otherarr ) + { + if( arr != NULL ) delete[] arr; + arraysize = otherarr.arraysize; + for( CARRAY_SIZE i = 0; i +#include + +#ifndef __CARRAY_HPP__ +#include "CRoseArray.hpp" +#endif + +/* MS Visual C++ Deprecation control defines */ +#ifdef _MSC_VER +# ifdef _CRT_SECURE_NO_DEPRECATE +# define CFILE_NODEPRECATE +# endif +#else +# define CFILE_NODEPRECATE +#endif + +//Debug: +//#define CFILE_NOUNICODE + +/* Various defines */ +#define CFILE_MAXLINELENGHT 255 +#define CFILE_UNICODE_HEADER 0xFEFF + +/* Variable type declarations */ +/* ETC */ +typedef bool CFILE_BOOLEAN; +typedef size_t CFILE_SIZE; + +/* Unsigned */ +typedef unsigned long long int CFILE_ULONGLONG; +typedef unsigned long int CFILE_ULONG; +typedef unsigned int CFILE_UINT; +typedef unsigned short int CFILE_USHORT; +typedef unsigned char CFILE_UCHAR; + +/* Signed */ +typedef signed long long int CFILE_SLONGLONG; +typedef signed long int CFILE_SLONG; +typedef signed int CFILE_SINT; +typedef signed short int CFILE_SSHORT; +typedef signed char CFILE_SCHAR; +typedef char CFILE_CHAR; + +/* File types */ +typedef enum +{ + FT_NONE, + FT_BINARY, + FT_ASCII, + FT_UNICODE +} FILETYPE; +/* File open modes */ +typedef enum +{ + FM_NONE = 0, + FM_READ = (1<<0), + FM_WRITE = (1<<1), + FM_CREATEIFDOESNTEXIST = (1<<2), + FM_BINARY = (1<<3), + FM_UNICODE = (1<<4), + FM_READWRITE = (FM_READ | FM_WRITE), + FM_RW = (FM_READWRITE | FM_CREATEIFDOESNTEXIST) +} FILEMODE; + +/* File errors */ +typedef enum +{ + FE_SUCCESS = 0, //No error + FE_EOF, //CFile::EndOfFile( ) == true + FE_NOFILEOPENED, //fh == NULL + FE_NORIGHTSFORACTION, //Like when trying to read when only opening in write mode + FE_INTERNALERROR, //CFile-internal errors, supposed to destruct the CFile now! + FE_UNICODENOTALLOWED, //#ifdef CFILE_NOUNICODE and file is unicode + FE_UNABLETOOPENFILE //fopen( ) == NULL +} FILEERROR; + +/* The CFile class */ +class CRoseFile +{ + private: + /** The current filetype + */ + FILETYPE ftype; + /** The last error occured + */ + FILEERROR flasterr; + /** The current file open mode bitfield + */ + CFILE_CHAR fmode; + /** Function to throw an error + @param + err: A FILEERROR + */ + void DoError( FILEERROR err ) + { + flasterr = err; + } + /** Converts the bitfield file open mode to c standart ASCII open modes + @param + mode: A byte with the bitfield + @result + string with the ASCII string + */ + CFILE_CHAR* ConvertBitFieldFileModeToASCIIFileMode( CFILE_SCHAR mode ) + { + CFILE_CHAR* result = new CFILE_CHAR[30]; + CFILE_UCHAR len = 0; + for( CFILE_CHAR i = 0; i<30; i++ ) result[i] = 0x00; + if( (mode & FM_READWRITE) == FM_READWRITE ) + { + if( (mode & FM_CREATEIFDOESNTEXIST) == FM_CREATEIFDOESNTEXIST ) + { + result[0] = 'w'; + result[1] = '+'; + len = 2; + } + else + { + result[0] = 'r'; + result[1] = '+'; + len = 2; + } + } + else if( (mode & FM_READ) == FM_READ ) + { + result[0] = 'r'; + len = 1; + } + else if( (mode & FM_WRITE) == FM_WRITE ) + { + result[0] = 'w'; + len = 1; + } + else + { + DoError( FE_INTERNALERROR ); + delete[] result; + return NULL; + } + if( (mode & FM_BINARY) == FM_BINARY ) + { + result[len] = 'b'; + len++; + } + if( (mode & FM_UNICODE) == FM_UNICODE ) + { + printf( (CFILE_CHAR*)result, 30, "%s, ccs=UNICODE", (CFILE_CHAR*)result ); + len += 13; + } + result[len] = 0x00; + return result; + } + + public: + + /** The current filehandle + */ + FILE* fh; + /** Contructor + */ + CRoseFile( ) + { + fh = NULL; + ftype = FT_NONE; + fmode = FM_NONE; + flasterr = FE_SUCCESS; + } + /** Contructor opening a file after contructing + @param + fname: A string with the filename + @param + mode: A bitfield using the FILETYPE enumerator to specify the fopen mode + */ + CRoseFile( CFILE_CHAR* fname, CFILE_CHAR mode ) + { + fh = NULL; + ftype = FT_NONE; + fmode = FM_NONE; + flasterr = FE_SUCCESS; + Open( fname, mode ); + } + + //Destructors + ~CRoseFile( ) + { + if( fh != NULL ) Close( ); + } + + /** Opens a file + @param + fname: A string with the filename + @param + mode: A bitfield using the FILETYPE enumerator to specify the fopen mode + @result + Boolean indicating if Open successed or failed + @note + If there is already an opened file, CFile will close it and open the new file + */ + CFILE_BOOLEAN Open( CFILE_CHAR* fname, CFILE_CHAR mode ) + { + if( fh != NULL ) Close( ); + fmode = mode; + CFILE_CHAR* fasciimode = ConvertBitFieldFileModeToASCIIFileMode( fmode ); + if( fasciimode == NULL ) + { + DoError( FE_INTERNALERROR ); + return false; + } + if( (fmode & FM_BINARY) == FM_BINARY ) + { + ftype = FT_BINARY; + } + #ifndef CFILE_NODEPRECATE + if( fopen_s( &fh, fname, fasciimode ) == 0 ) + { + #else + if( ( fh = fopen( fname, fasciimode ) ) != NULL ) + { + #endif + delete[] fasciimode; + //Check if unicode or ascii + if( ftype != FT_BINARY ) + { + CFILE_USHORT buf; + Read( &buf, sizeof( buf ), 1 ); + if( buf == CFILE_UNICODE_HEADER ) + { + #ifndef CFILE_NOUNICODE + fmode |= FM_UNICODE; + Close( ); + CFILE_CHAR* fasciimode = ConvertBitFieldFileModeToASCIIFileMode( fmode ); + //fasciimode = ConvertBitFieldFileModeToASCIIFileMode( fmode ); + if( fasciimode == NULL ) + { + DoError( FE_INTERNALERROR ); + return false; + } + #ifndef CFILE_NODEPRECATE + fopen_s( &fh, fname, fasciimode ); + #else + fh = fopen( fname, fasciimode ); + #endif + SetAbsPos( 2 ); + ftype = FT_UNICODE; + #else + DoError( FE_UNICODENOTALLOWED ); + Close( ); + #endif + } + else + { + SetAbsPos( 0 ); + ftype = FT_ASCII; + } + } + else + { + SetAbsPos( 0 ); + } + return true; + } + else + { + DoError( FE_UNABLETOOPENFILE ); + fh = NULL; + delete[] fasciimode; + return false; + } + } + + /** Closes the current open file + */ + void Close( ) + { + if( fh != 0 ) + { + DoError( FE_SUCCESS ); + fclose( fh ); + fh = NULL; + } + } + + CFILE_BOOLEAN IsOpen( ) + { + return (fh != NULL)?(true):(false); + } + + /** Returns if the file has been opened in read mode + @result + Boolean indicating if the current file has been opened in read mode + */ + CFILE_BOOLEAN IsRead( ) + { + return ((fmode & FM_READ) == FM_READ)?(true):(false); + } + /** Returns if the file has been opened in write mode + @result + Boolean indicating if the current file has been opened in write mode + */ + CFILE_BOOLEAN IsWrite( ) + { + return ((fmode & FM_WRITE) == FM_WRITE)?(true):(false); + } + /** Returns filehandle + @result + Returns the current filehandle + */ + FILE* FileHandle( ) + { + return fh; + } + /** Returns filetype + @result + returns the current filetype + */ + FILETYPE FileType( ) + { + return ftype; + } + /** Returns last error + @result + Returns the last error occured + */ + FILEERROR GetLastError( ) + { + return flasterr; + } + + /** Moves the file position to a custom point + @param + offset: Indicates how many bytes to move from origin + @param + origin: SEEK_SET -> File start | SEEK_CUR -> Current position | SEEK_END -> Last byte in file + @result + Boolean indicating if the seek process has successed + @note + Offset can be negative, too. + */ + CFILE_BOOLEAN Seek( CFILE_SLONG offset, signed int origin ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return false; + } + if( fseek( fh, offset, origin ) == 0 ) + { + return true; + } + else + { + return false; + } + } + /** Moves the file position to an absolute position from the file start + @param + offset: Indicates how many bytes to move from SEEK_SET + @result + Boolean indicating if the seek process has successed + @note + Offset MUST NOT be negative, else it will not do anything + */ + CFILE_BOOLEAN SetAbsPos( CFILE_SLONG offset ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return false; + } + if( offset < 0 ) + { + DoError( FE_INTERNALERROR ); + return false; + } + return Seek(offset,SEEK_SET); + } + /** Moves the file position to the start of the file + @result + Boolean indicating if the rewinding process has successed + @note + This function checks if the file has been opened in unicode mode, and if so, it sets the pointer to 2, + the start of the file AFTER the unicode file header + */ + CFILE_BOOLEAN GoToStart( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return false; + } + if( ftype == FT_ASCII || ftype == FT_BINARY ) + { + return SetAbsPos( 0 ); + } + else if( ftype == FT_UNICODE ) + { + return SetAbsPos( 2 ); + } + DoError( FE_INTERNALERROR ); + return false; + } + /** Tells the current position in the file + @result + Signed long indicating the absolute offset from SEEK_SET + */ + CFILE_SLONG Tell( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return EOF; + } + return ftell( fh ); + } + /** Checks if the file position is set after the end of the file + @result + Boolean indicating if the file pointer is at the end of the file + */ + CFILE_BOOLEAN EndOfFile( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return true; + } + return (feof( fh )==0)?(false):(true); + } + /** Moves the file position to a custom point + @result + Unsigned long indicating the size of the file in bytes + */ + CFILE_ULONG FileSize( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + CFILE_SLONG tmp_offset = Tell( ); + if( tmp_offset < 0 ) return 0; + Seek( 0, SEEK_END ); + CFILE_SLONG result = Tell( ); + SetAbsPos( tmp_offset ); + return result; + } + /** Reads from the file starting at the current position + @param + buf: A pointer to a buffer where the data will be stored + @param + element_size: The size of each block being read + @param + element_count: The number of blocks being read + @result + Unsigned int indicating the number of bytes read + */ + CFILE_UINT Read( void* buf, CFILE_SIZE element_size, CFILE_SIZE element_count = 1 ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + return (CFILE_UINT)( fread( buf, element_size, element_count, fh ) ); + } + /** Reads from the file starting at [offset] relative to [origin] + @param + buf: A pointer to a buffer where the data will be stored + @param + element_size: The size of each block being read + @param + element_count: The number of blocks being read + @param + offset: The offset to start reading from, relative to [origin] + @param + origin: Where offset is relative to, SEEK_SET -> Beginning of file | SEEK_CUR -> Current position | SEEK_END -> End of file + @result + Unsigned int indicating the number of bytes read + */ + CFILE_UINT ReadFrom( void* buf, CFILE_UINT element_size, CFILE_UINT element_count, CFILE_SLONG offset, signed int origin = SEEK_SET ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + if( !Seek( offset, origin ) ) return 0; + return (CFILE_UINT)( Read( buf, element_size, element_count ) ); + } + /** Reads from the file starting at from and ending at to + @param + buf: A pointer to a buffer where the data will be stored + @param + from: The offset to start reading from, absolute, aka relative to SEEK_SET + @param + to: The offset to end reading at, absolute, aka relative to SEEK_SET + @result + Unsigned int indicating the number of bytes read + @note + If (to-from) > the size of buf, it throws an exception (-> access violation), so check it before calling this function! + */ + CFILE_UINT ReadFromTo( void* buf, CFILE_SLONG from, CFILE_SLONG to ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + if( to < from ) return 0; + if( !SetAbsPos( from ) ) return 0; + return (CFILE_UINT)( Read( buf, (to-from), 1 ) ); + } + /** Reads one byte and returns it as signed char + @result + char with the byte read + */ + CFILE_CHAR GetC( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return EOF; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return EOF; + } + if( ftype == FT_ASCII || ftype == FT_BINARY ) + { + return (CFILE_CHAR)( fgetc( fh ) ); + } + else if( ftype == FT_UNICODE ) + { + #ifndef CFILE_NOUNICODE + CFILE_CHAR buf; + wchar_t buffer; + buffer = fgetwc( fh ); + #ifndef CFILE_NODEPRECATE + size_t tmp; + wcstombs_s( &tmp, &buf, (size_t)(sizeof(buf)), &buffer, 1 ); + #else + wcstombs( &buf, &buffer, 1 ); + #endif + return (CFILE_CHAR)buf; + #else + DoError( FE_UNICODENOTALLOWED ); + return EOF; + #endif + } + return EOF; + } + + /** Reads from the file starting at from and ending at to + @param + buf: A pointer to a buffer where the data will be stored + @param + max_count: The maximum number of bytes to read + @result + string with the string read + @note + GetS either reads up to [max_count] bytes or until the end of one line has been reached. (0x0D 0x0A) + @note + If the file is opened in binary mode, GetS throws an error and does nothing + */ + CFILE_CHAR* GetS( CRoseArray* buf, CFILE_UINT max_count ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return NULL; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return NULL; + } + if( ftype == FT_ASCII ) + { + fgets( buf->arr, max_count, fh ); + return NULL; + } + else if( ftype == FT_UNICODE ) + { + #ifndef CFILE_NOUNICODE + wchar_t* buffer = new wchar_t[max_count]; + fgetws( buffer, max_count, fh ); + #ifndef CFILE_NODEPRECATE + size_t tmp; + wcstombs_s( &tmp, buf->arr, (size_t)(buf->absSize()), buffer, max_count ); + #else + wcstombs( buf->arr, buffer, max_count ); + #endif + return NULL; + #else + DoError( FE_UNICODENOTALLOWED ); + return NULL; + #endif + } + DoError( FE_INTERNALERROR ); + return NULL; + } + /** Reads one line + @result + string with the line read + @note + If the file is opened in binary mode, the call to GetS throws an error and does nothing + */ + CFILE_CHAR* ReadLn( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return NULL; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return NULL; + } + if( EndOfFile( ) ) + { + DoError( FE_INTERNALERROR ); + return NULL; + } + CRoseArray* result = new CRoseArray( CFILE_MAXLINELENGHT ); + result->Clean( ); + if( GetS( result, CFILE_MAXLINELENGHT ) == NULL ) + { + CFILE_CHAR* res = new CFILE_CHAR[ (size_t)( result->size() ) ]; + for( CFILE_UINT i = 0;isize(); i++ ) res[i] = 0x00; + memcpy( res, result->arr, (size_t)(result->size()*sizeof(CFILE_CHAR)) ); + for( CFILE_UINT i = 0;i= FileSize( ) - 2 ) + { + delete result; + delete[] res; + return NULL; + } + res[i] = 0x00; + break; + } + } + delete result; + return res; + } + else + { + DoError( FE_INTERNALERROR ); + delete result; + return NULL; + } + } + /** Template to read different types + @ + typename + @result + typename: the value being read + */ + template T Get( ) + { + T result; + Read( &result, sizeof( T ), 1 ); + return result; + } + /*template CVector2F Get( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return CVector2F(0,0); + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return CVector2F(0,0); + } + CVector2F result; + Read( &result, sizeof( CVector2F ), 1 ); + return result; + } */ + /** Reads the whole file into the memory + @result + string: pointer to the memory section, NULL on error + @note + The current file position pointer DOES NOT get affected by this function! + */ + /*string ReadFileToMemory( ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsRead( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + CFILE_SLONG curpos = Tell( ); + SetAbsPos( 0 ); + string mem = new char[(CFILE_SIZE)(FileSize( ))]; + if( mem == NULL ) //not enough RAM + { + SetAbsPos( curpos ); + return NULL; + } + else + { + for( CFILE_UINT i = 0; i < FileSize( ); i++ ) mem[i] = 0x00; + Read( mem, (CFILE_SIZE)(FileSize( )) ); + SetAbsPos( curpos ); + return mem; + /* + if( Read( mem, (CFILE_SIZE)(FileSize( )) ) != FileSize( ) ) + { + delete[] mem; + return NULL; + } + else + { + SetAbsPos( curpos ); + return mem; + } + * + } + }*/ + + /** Writes to the file + @param + ptr: A pointer to a buffer where the data will be read to write from + @param + element_size: The size of one block being written + @param + element_count: The number of blocks being written + @result + unsigned int indicating the number of bytes being written + */ + CFILE_UINT Write( void* ptr, CFILE_SIZE element_size, CFILE_SIZE element_count=1 ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + return (CFILE_UINT)( fwrite( ptr, (size_t)( element_size ), (size_t)( element_count ), fh ) ); + } + /** Writes to the file starting at [offset] bytes relative to [origin] + @param + ptr: A pointer to a buffer where the data will be read to write from + @param + element_size: The size of one block being written + @param + element_count: The number of blocks being written + @param + offset: The offset to start writing from, relative to [origin] + @param + origin: The position offset is relative to, SEEK_SET -> Start of file | SEEK_CUR -> Current position | SEEK_END -> End of file + @result + unsigned int indicating the number of bytes being written + */ + CFILE_UINT WriteFrom( void* ptr, CFILE_SIZE element_size, CFILE_SIZE element_count, CFILE_SLONG offset, signed int origin ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + if( !Seek( offset, origin ) ) return 0; + return (CFILE_UINT)( Write( ptr, element_size, element_count ) ); + } + /** Writes to the file, from [from] to [to] + @param + ptr: A pointer to a buffer where the data will be read to write from + @param + from: The absolute (aka relative to SEEK_SET) offset to start writing from + @param + to: The absolute (aka relative to SEEK_SET) offset to stop writing at + @result + unsigned int indicating the number of bytes being written + @note + If (to-from) > the size of ptr, it throws an exception, so check it before calling this function! + */ + CFILE_UINT WriteFromTo( void* ptr, CFILE_SLONG from, CFILE_SLONG to ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + if( to < from ) return 0; + if( !SetAbsPos( from ) ) return 0; + return (CFILE_UINT)( Write( ptr, (to-from), 1 ) ); + } + /** Writes one byte to the file, as char + @param + chr: The byte to be written + @result + signed char: If success, it returns the char being written, else it returns EOF + */ + CFILE_SCHAR PutC( CFILE_SCHAR chr ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return EOF; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return EOF; + } + if( ftype == FT_ASCII || ftype == FT_BINARY ) + { + return fputc( chr, fh ); + } + else if( ftype == FT_UNICODE ) + { + #ifndef CFILE_NOUNICODE + wchar_t buffer; + #ifndef CFILE_NODEPRECATE + size_t tmp; + mbstowcs_s( &tmp, &buffer, 1, (CFILE_CHAR*)(&chr), 1 ); + #else + mbstowcs( &buffer, (CFILE_CHAR*)(&chr), 1 ); + #endif + return (CFILE_SCHAR)(fputwc( buffer, fh )); + #else + DoError( FE_UNICODENOTALLOWED ); + return EOF; + #endif + } + return EOF; + } + + /** Writes a string + @param + str: A string to the string to be written + @result + boolean indicating if writing the string has worked + @note + If the file is opened in binary mode, it throws an error and does nothing + */ + CFILE_BOOLEAN PutS( CRoseArray* str ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return false; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return false; + } + if( ftype == FT_ASCII ) + { + return (fputs( str->arr, fh )!=EOF)?(true):(false); + } + else + { + #ifndef CFILE_NOUNICODE + wchar_t* buffer = new wchar_t[ (size_t)(str->size()) ]; + #ifndef CFILE_NODEPRECATE + size_t tmp; + mbstowcs_s( &tmp, buffer, (size_t)(str->size()), str->arr, (size_t)(str->size()) ); + #else + mbstowcs( buffer, str->arr, str->size() ); + #endif + return (fputws( buffer, fh )!=EOF)?(true):(false); + #else + DoError( FE_UNICODENOTALLOWED ); + return false; + #endif + } + DoError( FE_INTERNALERROR ); + return false; + } + + /** Template to write different types + @ + typename + @param + typename val: the value to be written + @param + cnt: times val will be written + @result + typename: If success, it returns the value being written, else it returns 0 + */ + template T Put( T val, CFILE_UINT cnt=1 ) + { + if( fh == NULL ) + { + DoError( FE_NOFILEOPENED ); + return 0; + } + if( !IsWrite( ) ) + { + DoError( FE_NORIGHTSFORACTION ); + return 0; + } + return (Write( &val, sizeof( val ), cnt ) != 0)?(val):(0); + } +}; diff --git a/Src/World Server/quest/CStrStb.hpp b/Src/World Server/quest/CStrStb.hpp new file mode 100644 index 0000000..f9c372b --- /dev/null +++ b/Src/World Server/quest/CStrStb.hpp @@ -0,0 +1,82 @@ +// Props to ExJam for this code :D + +class CStrStb +{ +public: + CStrStb():_Data(0){} + CStrStb(const char* filename) : _Data(0){ LoadSTB(filename); } + ~CStrStb(){CleanUp();} + + bool LoadSTB(const char* path) + { + CleanUp(); + + CRoseFile* fh = new CRoseFile((CFILE_CHAR*) path, FM_READ | FM_BINARY); + if(!fh->IsOpen()){ + delete fh; + return false; + } // if is open + + fh->Seek(4, SEEK_CUR); + dword offset = fh->Get(); + + _Rows = fh->Get(); + _Columns = fh->Get(); + + _Rows--; + _Columns--; + + fh->SetAbsPos(offset); + _Data = new char**[_Rows]; + for(dword i = 0; i < _Rows; i++) + _Data[i] = new char*[_Columns]; + + for(dword i = 0; i < _Rows; i++){ + for(dword j = 0; j < _Columns; j++){ + dword len = fh->Get(); + if(len == 0){ + _Data[i][j] = NULL; + continue; + } // if len + _Data[i][j] = new char[len+1]; + fh->Read(_Data[i][j], len, 1); + _Data[i][j][len] = 0; + } // for j + } // for i + + fh->Close(); + delete fh; + return true; + } // bool + + char* Data(dword Row, dword Column){ + if(Row > _Rows) return NULL; + if(Column > _Columns) return NULL; + + return _Data[Row][Column]; + } + + dword Rows(){ return _Rows; } + dword Columns(){ return _Columns; } + +private: + void CleanUp(){ + if(_Data != NULL){ + for(dword i = 0; i < _Rows; i++){ + for(dword j = 0; j < _Columns; j++){ + delete [] _Data[i][j]; + } + delete [] _Data[i]; + } + delete [] _Data; + } + _Rows = 0; + _Columns = 0; + } + +private: + dword _Rows; + dword _Columns; + + char*** _Data; +}; diff --git a/Src/World Server/quest/QuestActions.cpp b/Src/World Server/quest/QuestActions.cpp new file mode 100644 index 0000000..5c297df --- /dev/null +++ b/Src/World Server/quest/QuestActions.cpp @@ -0,0 +1,1052 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#include "../WorldServer.h" + +dword GetRewardValue(dword function, dword amount, CPlayer* client, word nDupCNT) +{ + switch(function) + { + case 0: + { + dword tempVal = amount + 0x1E; + tempVal *= client->Attr->Cha + 0x0a; // We need to add a way to get all stats totals (Including buff, E, etc) - Drakia + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->Stats->Level + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 1: + { + dword tempVal = (client->Stats->Level + 3) * amount; + tempVal *= (client->Attr->Cha >> 1) + client->Stats->Level + 0x28; + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x2710; + } + break; + case 2: + return (amount * nDupCNT); + break; + case 3: + case 5: + { + dword tempVal = amount + 0x14; + tempVal *= client->Attr->Cha + 0x0a; + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->Stats->Level + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 4: + { + dword tempVal = amount + 0x2; + tempVal *= (client->Attr->Cha + client->Stats->Level + 0x28); + tempVal *= 0x28;//Fame + 0x28 + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x222E0; + } + break; + case 6: + { + dword tempVal = amount + 0x14; + tempVal *= client->Attr->Cha + client->Stats->Level; + tempVal *= 0x14;//Fame + 0x14 + tempVal *= (100 & 0xFFFF);//World Rate + return (tempVal / 0x2DC6C0) + amount; + } + break; + default: + break; + } + return amount; +} + +//Update Quest +QUESTREWD(000) +{ + //dword iQuestSN pos 0x00 + //byte btOp pos 0x04 + GETREWDDATA(000); + switch(data->btOp)//0 remove, 1 start, 2 replace quest keep items, 3 replace quest delete items, 4 select + { + case 0: + { + if( client->questdebug ) + server->SendPM(client, "Remove Quest: %u", data->iQuestSN); + if (client->ActiveQuest == data->iQuestSN) client->ActiveQuest = 0; + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != data->iQuestSN) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + break; + } + } + break; + case 1: + { + if( client->questdebug ) + server->SendPM(client, "Start Quest: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID == data->iQuestSN) return QUEST_SUCCESS; + if(client->quest.quests[i].QuestID != 0) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 2: + { + if( client->questdebug ) + server->SendPM(client, "Replace Quest, keep items: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != client->ActiveQuest) continue; + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 3: + { + if( client->questdebug ) + server->SendPM(client, "Replace Quest, delete items: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != client->ActiveQuest) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 4: + { + if( client->questdebug ) + server->SendPM(client, "Select Quest: %u", data->iQuestSN); + client->ActiveQuest = data->iQuestSN; + } + break; + default: + break; + } + return QUEST_SUCCESS; +} + +//Update Quest Items +QUESTREWD(001) +{ + //dword uiItemSN; pos 0x00 + //byte btOp; pos 0x04 + //word nDupCNT; pos 0x06 + //byte btPartyOpt; pos 0x0a + GETREWDDATA(001); + if( client->questdebug )server->SendPM(client, "Update Quest Item (uiItemSN: %u btOp: %u nDupCNT: %u)", data->uiItemSN, data->btOp, data->nDupCNT); + CItem tmpItem; + tmpItem.itemtype = data->uiItemSN / 1000; + tmpItem.itemnum = data->uiItemSN % 1000; + tmpItem.count = data->nDupCNT; + + SQuest* curQuest = client->GetActiveQuest(); + if(curQuest == NULL) + { + //Log(MSG_DEBUG, "Couldn't find active quest, wtf? Id %u", client->ActiveQuest); + return QUEST_FAILURE; + } + curQuest->AddItem(&tmpItem, data->btOp); + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(002) +{ + GETREWDDATA(002); + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 union. This or + //short m_mVarNo pos 0x04 this + + //word m_wVarTYPE; pos 0x06 Type of quest variable + // 0 GetVarType = "ActiveQuest" + // 16 GetVarType = "SwitchBit" + // 32 GetVarType = "Remaining Time" + // 48 GetVarType = "EpisodeVar" + // 64 GetVarType = "JobVar" + // 80 GetVarType = "PlanetVar" + // 96 GetVarType = "UnionVar" + //word nValue; pos 0x08 + //byte btOp; pos 0x0a + + for(dword i = 0; i < data->iDataCnt; i++) + { + dword address = i * sizeof(STR_QUEST_DATA); + address += (dword)data; + address += 4; + STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Set quest var[%#04x][%i] - %i (Op: %i)", curQst->m_wVarTYPE, curQst->m_wVarNO, curQst->nValue, curQst->btOp ); + word nValue = curQst->nValue; + word tempValue = client->GetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO); + OperateValues(curQst->btOp, &tempValue, curQst->nValue); + client->SetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO, tempValue); + } + return QUEST_SUCCESS; +} + +//Update Stats +QUESTREWD(003) +{ + GETREWDDATA(003); + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 + //dword iValue; pos 0x08 + //byte btOp; pos 0x0c + for(dword i = 0; i < data->iDataCnt; i++) + { + //dword address = i * 0x0C; + //address += (dword)data; + //address += 4; + //STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Update stat[%i] %i (Op: %i)", data->iType, data->iValue, data->btOp); + switch( data->iType ) + { + case 2: //Gender + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Sex, data->iValue)) + return QUEST_FAILURE; + //client->CharInfo->Sex = data->iValue; + //BEGINPACKET( pak, 0x720 ); + //ADDWORD( pak, 2 ); + //ADDWORD( pak, client->CharInfo->Sex ); + //ADDWORD( pak, 0 ); + //GServer->SendToVisible( &pak, client ); + //client->client->SendPacket( &pak ); + //RESETPACKET( pak, 0x0730 ); + //ADDWORD( pak, 5 ); + //ADDWORD( pak, 0xa24d ); + //ADDWORD( pak, 0x40b3 ); + //GServer->SendToVisible( &pak, client ); + //client->client->SendPacket( &pak ); + client->quicksave(); + } + break; + case 4: //job + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Job, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + client->quicksave(); + } + break; + case 5: //union + { + if (!OperateValues(data->btOp, (int*)&client->Union_s->unionvar[0], data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + client->quicksave(); + //Log(MSG_DEBUG,"Client Union changed"); + } + break; + case 8: // face + { + if (!OperateValues(data->btOp, (int*)&client->CharInfo->Face, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 8); + ADDWORD(pak, client->CharInfo->Face); + ADDWORD(pak, 0); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + client->quicksave(); + } + break; + case 9: // Hair + { + if (!OperateValues(data->btOp, (int*)&client->CharInfo->Hair, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 9); + ADDWORD(pak, client->CharInfo->Hair); + ADDWORD(pak, 0); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + client->quicksave(); + } + case 81: // Union points for junon order + case 82: + case 83: // Union points for righteous crusaders + case 84: + case 85: + case 86: + case 87: // Union 7 points used for tracking previous memberships + case 88: + case 89: //kuro-Tejina Union points + case 90: + { + if (!OperateValues(data->btOp, (int*)&client->Union_s->unionvar[data->iType - 80], data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x720 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + + if(data->iType == 89) //update the database if the quest just gave KTPoints + { + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + client->Session->KTPoints,client->Session->userid, client->Session->password); + } + } + break; + case 10: //strength + if(!OperateValues(data->btOp, (int*)&client->Attr->Str, data->iValue)) + return QUEST_FAILURE; + break; + case 11: //dexterity + if(!OperateValues(data->btOp, (int*)&client->Attr->Dex, data->iValue)) + return QUEST_FAILURE; + break; + case 12: //inteligence + if(!OperateValues(data->btOp, (int*)&client->Attr->Int, data->iValue)) + return QUEST_FAILURE; + break; + case 13: //concentration + if(!OperateValues(data->btOp, (int*)&client->Attr->Con, data->iValue)) + return QUEST_FAILURE; + break; + case 14: //charm + if(!OperateValues(data->btOp, (int*)&client->Attr->Cha, data->iValue)) + return QUEST_FAILURE; + break; + case 15: //sense + if(!OperateValues(data->btOp, (int*)&client->Attr->Sen, data->iValue)) + return QUEST_FAILURE; + break; + case 30: // set global XP rate + { + GServer->Config.EXP_RATE = data->iValue; + return QUEST_SUCCESS; + } + break; + case 31: //level + if(!OperateValues(data->btOp, (int*)&client->Stats->Level, data->iValue)) + return QUEST_FAILURE; + break; + case 32: // Stat points + if(!OperateValues(data->btOp, (int*)&client->CharInfo->StatPoints, data->iValue)) + return QUEST_FAILURE; + break; + case 37: // Skill points + if(!OperateValues(data->btOp, (int*)&client->CharInfo->SkillPoints, data->iValue)) + return QUEST_FAILURE; + break; + //case sMoney: + //if(!OperateValues(data->btOp, (int*)&client->CharInfo->Zulies, data->iValue)) + //return QUEST_FAILURE; + //break; + case 40: //money + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Zulies, data->iValue)) + return QUEST_FAILURE; + // Send zuly update packet + BEGINPACKET(pak, 0x71D); + ADDQWORD( pak, client->CharInfo->Zulies ); + client->client->SendPacket( &pak ); + } + break; + default: + Log(MSG_WARNING, "Type Unknown: '%i'", data->iType); + break; + } + } + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(004) +{ + return QUEST_REWD_002(server, client, raw); +} + + //Give Reward +QUESTREWD(005) +{ + GETREWDDATA(005); + if( client->questdebug ) + server->SendPM(client, "Quest reward 005 using equation %i", data->btEquation); + switch(data->btTarget) + { + case 0://EXP + { + if( client->questdebug ) + server->SendPM(client, "Give EXP: %i", data->iValue); + client->CharInfo->Exp += GetRewardValue(data->btEquation, data->iValue, client, 0); + BEGINPACKET(pak, 0x79b); + ADDDWORD(pak, client->CharInfo->Exp); + ADDDWORD(pak, client->CharInfo->stamina); + ADDWORD (pak, 0); + client->client->SendPacket(&pak); + } + break; + case 1://Zuly + { + if( client->questdebug ) + server->SendPM(client, "Give Zuly: %i", data->iValue); + unsigned int j = client->CheckQuest; + int itemcount = client->quest.quests[j].Items[0].count; + if( client->questdebug ) + server->SendPM(client, "Items returned: %i", itemcount); + //int Zulyreward = GetRewardValue(data->btEquation, data->iValue, client, itemcount); + // temp fix + int Zulyreward = data->iValue; + if(itemcount > 0 ) //this is for cases where multiple items are handed in for fetch quest. + Zulyreward = data->iValue * itemcount; + client->CharInfo->Zulies += Zulyreward; + //client->CharInfo->Zulies += GetRewardValue(data->btEquation, data->iValue, client, 1);//dunno nDupCount for this one! + BEGINPACKET(pak, 0x71D); + ADDQWORD(pak, client->CharInfo->Zulies); + client->client->SendPacket(&pak); + } + break; + case 2://Item + { + CItem nItem; + nItem.itemtype = data->iItemSN / 1000; + nItem.itemnum = data->iItemSN % 1000; + if(nItem.IsStackable()) + { + nItem.count = GetRewardValue(data->btEquation, data->iValue, client, 0); + } + else + { + nItem.count = 1; + } + if( client->questdebug ) + server->SendPM(client, "Give item [%i][%i]x%i", nItem.itemtype, nItem.itemnum, nItem.count); + nItem.durability = GServer->STB_ITEM[nItem.itemtype-1].rows[nItem.itemnum][29]; + nItem.gem = 0; + nItem.stats = 0; + nItem.refine = 0; + nItem.socketed = 0; + nItem.lifespan = 100; + nItem.appraised = 1; + dword slot = client->AddItem(nItem); + if (slot == 0xffff) // Fail + { + BEGINPACKET( pak, 0x7a7); + ADDWORD(pak, 0x00); + ADDBYTE(pak, 0x03); + ADDBYTE(pak, 0x00); + client->client->SendPacket(&pak); + } + else // Success + { + BEGINPACKET( pak, 0x71f); + ADDBYTE(pak, 0x01); + ADDBYTE(pak, slot); + ADDWORD(pak, client->items[slot].GetPakHeader( ) ); + ADDDWORD(pak, client->items[slot].GetPakData( ) ); + client->client->SendPacket(&pak); + } + } + break; + default: + break; + } + return QUEST_SUCCESS; +} + +//Restore HP/MP +QUESTREWD(006) +{ + //dword iPercentOfHP pos 0x00 + //dword iPercentOfMP pos 0x04 + //byte btPartyOpt pos 0x08 + GETREWDDATA(006); + if( client->questdebug ) + server->SendPM(client, "Restore %i%% HP, %i%% MP", data->iPercentOfHP, data->iPercentOfMP); + client->Stats->HP = (long int)((float)client->Stats->MaxHP / 100.0f) * data->iPercentOfHP; + client->Stats->MP = (long int)((float)client->Stats->MaxHP / 100.0f) * data->iPercentOfMP; + return QUEST_SUCCESS; +} + +//Teleport +QUESTREWD(007) +{ + //dword iZoneSN; pos 0x00 + //dword iX; pos 0x04 + //dword iY; pos 0x08 + //byte btPartyOpt; pos 0x0d + GETREWDDATA(007); + fPoint thispoint; + thispoint.x = floor(((float)data->iX)/100); + thispoint.y = floor(((float)data->iY)/100); + if( client->questdebug ) + server->SendPM(client, "Teleport [%i][%f][%f]", data->iZoneSN, thispoint.x, thispoint.y); + GServer->TeleportTo(client, data->iZoneSN, thispoint); + return QUEST_SUCCESS; +} + +//Spawn Monster +QUESTREWD(008) +{ + //dword iMonsterSN pos 0x00 + //dword iHowMany pos 0x04 + // byte btWho pos 0x08 or + // int iWho pos 0x08 + //dword iZoneSN pos 0x0a + //dword iX pos 0x0e + //dword iY pos ox11 + //dword iRange pos 0x15 + //dword iTeamNo pos 0x19 + GETREWDDATA(008); + fPoint position; + dword mapId; + if(data->iX == 0 || data->iY == 0 || data->iZoneSN == 0) + { + position.x = client->Position->current.x; + position.y = client->Position->current.y; + mapId = client->Position->Map; + } + else + { + position.x = data->iX / 100; + position.y = data->iY / 100; + mapId = data->iZoneSN; + } + for(dword i = 0; i < data->iHowMany; i++) + { + fPoint pos = GServer->RandInCircle( position, data->iRange ); + if( client->questdebug ) + server->SendPM(client, "Spawn mob[%i] @ %f, %f", data->iMonsterSN, pos.x, pos.y); + CMap* map = GServer->MapList.Index[mapId]; + CMonster* mon = map->AddMonster( data->iMonsterSN, pos, 0, 0 , 1 ); + if(data->iMonsterSN > 750 && data->iMonsterSN < 755) // ghost rackies = non aggresive + { + mon->thisnpc->aggresive = 0; + } + else + { + mon->thisnpc->aggresive = 999; // Force the mob to be agressive. + } + mon->lastSighCheck = 0; // Force sight check instantly. + } + return QUEST_SUCCESS; +} + +//Execute Quest Trigger +QUESTREWD(009) +{ + //word shNameLen pos 0x00 + //string szNextTriggerSN pos 0x02 + GETREWDDATA(009); + char* tempName = reinterpret_cast(&data->szNextTriggerSN) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, "Execute Quest Trigger %s[%d] [%08x]", tempName, data->shNameLen, hash); + return client->ExecuteQuestTrigger(hash); + //return QUEST_SUCCESS; +} + +//Reset Stats +QUESTREWD(010) +{ + if( client->questdebug ) + server->SendPM(client, "Reset Stats"); + client->CharInfo->StatPoints = 0; + client->Attr->Str = 15; + client->Attr->Dex = 15; + client->Attr->Int = 15; + client->Attr->Con = 15; + client->Attr->Cha = 10; + client->Attr->Sen = 10; + for(int i = 2; i <= client->Stats->Level; i++) + { + client->CharInfo->StatPoints += 10; + client->CharInfo->StatPoints += i / 2; + } + return QUEST_SUCCESS; +} + +//Update Object Var +QUESTREWD(011) +{ + GETREWDDATA(011); + /*if(data->btWho == 0) //npc + { + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return QUEST_FAILURE; + if(data->nVarNo > 19) return AI_FAILURE; + short tempval = GServer->ObjVar[monster->thisnpc->refNPC][data->nVarNo]; + switch(data->btOp) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + case 9: + tempval++; + break; + default: + return QUEST_FAILURE; + break; + } + if(tempval < 0)tempval = 0; + GServer->ObjVar[monster->thisnpc->refNPC][data->nVarNo] = tempval; + return QUEST_SUCCESS; + } + else if(data->btWho == 1) // event + { + //need to write the event code still it seems. Seems easy enough + return QUEST_FAILURE; + }*/ + + + /*if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + + if(data->btWho == 0){//Npc + CNpc* thisNpc = reinterpret_cast(entity); + thisNpc = thisNpc->SelectedNpc; + if(thisNpc == NULL) return QUEST_FAILURE; + + short VarValue = thisNpc->ObjVar.GetVar(data->nVarNo); + OperateValues(data->btOp, &VarValue, (short)data->iValue); + thisNpc->ObjVar.SetVar(data->nVarNo, VarValue); + }else if(data->btWho == 1){//Event + short VarValue = server->EventVar.GetVar(data->nVarNo); + OperateValues(data->btOp, &VarValue, (short)data->iValue); + server->EventVar.SetVar(data->nVarNo, VarValue); + } + + + return QUEST_SUCCESS;*/ + //byte btWho; pos 0x00 + //word nVarNo; pos 0x02 + //dword iValue; pos 0x04 this was an int but ints only hold 2 bytes so btop would be out of position + //byte btOp; pos 0x08 btop definitely at position 0x08 so had to adjust iValue + int tempval; + if(data->btWho == 0) + { + if(data->nVarNo > 19) return AI_FAILURE; + tempval = GServer->ObjVar[client->NPCvar][data->nVarNo]; + //Log(MSG_INFO,"Just checking. NPCvar = %i", client->NPCvar); + switch( data->btOp ) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + break; + case 9: + tempval++; + break; + default: + return QUEST_FAILURE; + break; + } + } + else if(data->btWho == 1) //event + { + // not sure what to do here yet + return QUEST_FAILURE; + } + GServer->ObjVar[client->NPCvar][data->nVarNo] = tempval; + return QUEST_SUCCESS; +} + +//NPC Speak +QUESTREWD(012) +{ + //if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + GETREWDDATA(012); +/* + CNpc* thisNpc = reinterpret_cast(entity); + if(thisNpc->SelectedNpc == NULL) return QUEST_FAILURE; + std::map::iterator triggerITR = server->NpcNames.find(thisNpc->SelectedNpc->NpcID); + if(triggerITR == server->NpcNames.end()) return QUEST_FAILURE; + + char* npcName = triggerITR->second; + char* sayStr = server->lngQst->GetLTBStr(data->iStrID); + if(sayStr == NULL) return QUEST_FAILURE; + if(data->btMsgType == 1){ + //Shout + CPacket pakout(0x785); + pakout.Add(npcName); + pakout.Add(sayStr); + server->SendPacketToZone(thisNpc, &pakout); + }else if(data->btMsgType == 2){ + //Announce + CPacket pakout(0x702); + pakout.AddBytes((byte*)npcName, strlen(npcName)); + pakout.Add('>'); + pakout.Add(sayStr); + server->SendPacketToZone(thisNpc, &pakout); + } + delete [] sayStr;*/ + + dword npcId = 0; + //if(entity->_EntityType == ENTITY_NPC) + //CMonster* thisMonster = reinterpret_cast(entity); + switch(data->btMsgType) + { + case 0: //whisper to client + // don't think this is possible. No way to get a character to send it to?? + break; + case 1: //shout to map + Log(MSG_INFO,"Quest -- NPC Shouts LTB string %i to map %i",data->iStrID,client->Position->Map); + GServer->NPCShout(client->Position->Map,GServer->Ltbstring[data->iStrID].QuestLTB,GServer->Ltbstring[data->iStrID].QuestNpcName); + break; + case 2: //announce to server. + Log(MSG_INFO,"Quest -- NPC announces LTB string %i to map %i",data->iStrID,client->Position->Map); + GServer->NPCAnnounce(GServer->Ltbstring[data->iStrID].QuestLTB,GServer->Ltbstring[data->iStrID].QuestNpcName); + break; + default: + break; + } + + return QUEST_SUCCESS; + // This is cool and all, but we lack the stuff to do it - Drakia + // Not any more - PY +} + +//Unknown +QUESTREWD(013) +{ + // byte btWho pos 0x00 + // int iWho pos 0x00 + //dword iSec pos 0x02 + //word nNameLength pos 0x06 + //string szTriggerName pos 0x08 + //dword m_HashNextTrigger pos 0x06 + nNameLength? + //looks like a quest trigger to me + GETREWDDATA(013); + char* tempName = reinterpret_cast(&data->szTriggerName) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, " RWD 013 Execute Quest Trigger %s[%d] [%08x]", tempName, data->nNameLength, hash); + //suppress the actual execution for now just to be sure + return client->ExecuteQuestTrigger(hash); + return QUEST_SUCCESS; +} + +//Learn Skill +QUESTREWD(014) +{ + // byte btOp pos 0x00 + // int iOp pos 0x00 + //dword iSkillNo pos 0x02 + GETREWDDATA(014); + if( client->questdebug ) + server->SendPM(client, "Learn skill: %i", data->iSkillNo); + GServer->LearnSkill(client, data->iSkillNo, false); + return QUEST_SUCCESS; +} + +//Set Quest Flag +QUESTREWD(015) +{ + GETREWDDATA(015); + //word nSN; 0x00 + //byte btOp; 0x02 + if( client->questdebug ) + server->SendPM(client, "Set QFlag[%i]=%i", data->nSN, data->btOp); + client->quest.SetFlag(data->nSN, (data->btOp == 1)?true:false); + // PY method + //client->quest.Qflags[data->nSN] == data->btOp; + // PY end + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(016) +{ + //word nGroupSN pos 0x00 + return QUEST_SUCCESS; +} + +//Reset All Quest Flags +QUESTREWD(017) +{ + if( client->questdebug ) + server->SendPM(client, "Reset all quest flags"); + //memset(&client->quest.flags, 0, 64); + // PY method + for(UINT i=0;i<1000;i++) + { + for(int j=0;j<8;j++) + { + client->quest.Qflags[i][j] = 0; + } + } + // PY end + return QUEST_SUCCESS; +} + +//Send Announcement +QUESTREWD(018) +{ + //dword iStrID pos 0x00 + //word nCnt pos 0x04 + //string Data pos 0x06 + //why isn't this coded? looks simple enough + GETREWDDATA(018); + char* tempName = reinterpret_cast(&data->Data) - 2; + if( client->questdebug ) + server->SendPM(client, " RWD 018 Send announcement %s", tempName); + Log(MSG_INFO,"Announcement to all %s",tempName); + GServer->NPCAnnounce(tempName,"Quest NPC"); + return QUEST_SUCCESS; +} + +//Execute Quest Trigger in Other Map +QUESTREWD(019) +{ + //this is going to be a pain + //it runs a quest trigger that would appear to be sent to every player in the designated zone + + //word nZoneNo; //pos 0x00 + //word nTeamNo; //pos 0x02 + //word nTriggerLength; //pos 0x04 This is definitely NOT the length of the following name. PY + //string TriggerName; //pos 0x06 + nTriggerlength more bytes. Name always appears to be 17 bytes long + //dword m_HashTrigger; + //word m_HashTrigger; //pos 0x17 only has 2 bytes not 4. PY + + // let's check the values first + GETREWDDATA(019); + //Log(MSG_DEBUG,"Execute Quest trigger: %s for zone %i. Hash: %i",data->TriggerName, data->nZoneNo, data->m_HashTrigger); + + /*CMap* map = GServer->MapList.Index[data->nZoneNo]; + if(map == NULL) return QUEST_SUCCESS; + if( map->PlayerList.size()<1 ) return QUEST_SUCCESS; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + player->ExecuteQuestTrigger(data->m_HashTrigger); + } + return QUEST_SUCCESS;*/ + + char* tempName = reinterpret_cast(&data->TriggerName) - 2; + dword hash = MakeStrHash(tempName); + CMap* map = GServer->MapList.Index[data->nZoneNo]; + if(map == NULL) return QUEST_SUCCESS; + if( map->PlayerList.size()<1 ) return QUEST_SUCCESS; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + player->ExecuteQuestTrigger(hash); + } + + return QUEST_SUCCESS; +} + +//PvP Status +QUESTREWD(020) +{ + GETREWDDATA(020); + //byte btNoType; pos 0x00 + + return QUEST_SUCCESS; +} + +//Set Respawn Position +QUESTREWD(021) +{ + //dword iX pos 0x00 + //dword iY pos 0x04 + GETREWDDATA(020); + client->Position->saved = client->Position->Map; + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(022) +{ + //word nZonNo pos 0x00 + //byte btOp pos 0x02 + return QUEST_SUCCESS; +} + +//Raise Clan Grade - This is not the least bit efficient - Drakia +QUESTREWD(023) +{ + GETREWDDATA(023); + if( client->questdebug ) + server->SendPM(client, "Raise clan grade"); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + continue; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if( player->Clan->clanid != client->Clan->clanid ) continue; + player->Clan->grade++; + + //load clan info in char server + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0xfb ); //action to update clan informacion (charserver) + ADDWORD ( pak, player->Clan->clanid ); + ADDWORD ( pak, player->Clan->grade ); + GServer->SendISCPacket( &pak ); + + //Send to other players + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, player->clientid ); + ADDWORD ( pak, player->Clan->clanid); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, player->Clan->back ); + ADDWORD ( pak, player->Clan->logo ); + ADDBYTE ( pak, player->Clan->grade );//clan grade + ADDBYTE ( pak, 0x06 );//clan rank + ADDSTRING ( pak, player->Clan->clanname ); + ADDBYTE ( pak, 0x00 ); + GServer->SendToVisible( &pak, player ); + } + } + GServer->DB->QExecute("UPDATE list_clan SET grade=%i WHERE id=%i", client->Clan->grade, client->Clan->clanid); + return QUEST_SUCCESS; +} + +//Clan Money +QUESTREWD(024) +{ + //int iMONEY pos 0x00 + //byte btOP pos 0x02 + GETREWDDATA(024); + return QUEST_SUCCESS; +} + +//Clan Points +QUESTREWD(025) +{ + //short nPOINT pos 0x00 + //byte btOP pos 0x02 + GETREWDDATA(025); + return QUEST_SUCCESS; +} + +//Clan Skill +QUESTREWD(026) +{ + GETREWDDATA(026); + //word nSkillNo; pos 0x00 + //byte btOP; pos 0x02 + //do something (based on btOp) with a clan skill id = data->nSkillNo + if( client->questdebug )server->SendPM(client, "Clan skill rwd[026] skill id: %i btOP: %i",data->nSkillNo,data->btOP); + return QUEST_SUCCESS; +} + +//Clan Contribution +QUESTREWD(027) +{ + GETREWDDATA(027); + //short nCONT; pos 0x00 + //byte btOP; pos 0x02 + if( client->questdebug )server->SendPM(client, "Clan Contribution rwd[026] nCONT: %i btOP: %i",data->nCONT,data->btOP); + + return QUEST_SUCCESS; +} + +//Clan Teleportation - Again, not efficient. Maybe keep a list of pointers to clan members? - Drakia +QUESTREWD(028) +{ + //dword iRange; pos 0x00 + //word nZoneNo; pos 0x04 + //dword iX; pos 0x06 + //dword iY; pos 0x0a + GETREWDDATA(028); + fPoint telepos; + fPoint newPos; + telepos.x = data->iX/100; + telepos.y = data->iY/100; + if( client->questdebug ) + server->SendPM(client, "Clan teleport to [%i][%f][%f]", data->nZoneNo, telepos.x, telepos.y); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + continue; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if( player->Clan->clanid != client->Clan->clanid ) continue; + newPos = GServer->RandInCircle(telepos, data->iRange); + GServer->TeleportTo(player, data->nZoneNo, newPos ); + } + } + return QUEST_SUCCESS; +} + +QUESTREWD(029) //execute client quest trigger (LUA) +{ + GETREWDDATA(029); + char* tempName = reinterpret_cast(&data->LuaName) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, "Execute Client LUA %s[%d] [%08x]", tempName, data->strLen, hash); + //return client->ExecuteQuestTrigger(hash); + //need to code a packet here. on evo it's 0x7f8. Don't even know if we use one here + Log(MSG_DEBUG,"ran quest action 29"); + return QUEST_SUCCESS; +} + +QUESTREWD(030) //Reset skills +{ + //Log(MSG_DEBUG,"ran quest action 30"); + for(int i=0;i<30;i++) + { + //give one sp for every active skill present + if(client->askill[i] != 0) + { + client->CharInfo->SkillPoints += 1; + } + client->askill[i] = 0; + //give SP equal to the level of the active skill. Actual level is always 1 more than this + client->CharInfo->SkillPoints += client->askilllvl[i]; + client->askilllvl[i] = 0; + //give one sp for every passive skill present + if(client->pskill[i] != 0) + { + client->pskill[i] = 0; + client->CharInfo->SkillPoints += 1; + } + //give SP equal to the level of the passive skill. Actual level is always 1 more than this + client->CharInfo->SkillPoints += client->pskilllvl[i]; + client->pskilllvl[i] = 0; + } + client->savedata(); + return QUEST_SUCCESS; +} diff --git a/Src/World Server/quest/QuestActions.cpp.wrong b/Src/World Server/quest/QuestActions.cpp.wrong new file mode 100644 index 0000000..65612b9 --- /dev/null +++ b/Src/World Server/quest/QuestActions.cpp.wrong @@ -0,0 +1,1138 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#include "../WorldServer.h" + +dword GetRewardValue(dword function, dword amount, CPlayer* client, word nDupCNT) +{ + switch(function) + { + case 0: + { + dword tempVal = amount + 0x1E; + tempVal *= client->Attr->Cha + 0x0a; // We need to add a way to get all stats totals (Including buff, E, etc) - Drakia + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->Stats->Level + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 1: + { + dword tempVal = (client->Stats->Level + 3) * amount; + tempVal *= (client->Attr->Cha >> 1) + client->Stats->Level + 0x28; + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x2710; + } + break; + case 2: + return (amount * nDupCNT); + break; + case 3: + case 5: + { + dword tempVal = amount + 0x14; + tempVal *= client->Attr->Cha + 0x0a; + tempVal *= (100 & 0xFFFF);//World Rate + tempVal *= 0x14;//Fame + 0x14 + tempVal = ((tempVal / (client->Stats->Level + 0x46)) / 0x7530) + amount; + return tempVal; + } + break; + case 4: + { + dword tempVal = amount + 0x2; + tempVal *= (client->Attr->Cha + client->Stats->Level + 0x28); + tempVal *= 0x28;//Fame + 0x28 + tempVal *= (100 & 0xFFFF);//World Rate + return tempVal / 0x222E0; + } + break; + case 6: + { + dword tempVal = amount + 0x14; + tempVal *= client->Attr->Cha + client->Stats->Level; + tempVal *= 0x14;//Fame + 0x14 + tempVal *= (100 & 0xFFFF);//World Rate + return (tempVal / 0x2DC6C0) + amount; + } + break; + default: + break; + } + return amount; +} + +//Update Quest +QUESTREWD(000) +{ + //dword iQuestSN pos 0x00 + //byte btOp pos 0x04 + GETREWDDATA(000); + switch(data->btOp) //0 remove, 1 start, 2 replace quest keep items, 3 replace quest delete items, 4 select + { + case 0: + { + if( client->questdebug ) + server->SendPM(client, "Remove Quest: %u", data->iQuestSN); + if (client->ActiveQuest == data->iQuestSN) client->ActiveQuest = 0; + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != data->iQuestSN) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + break; + } + } + break; + case 1: + { + if( client->questdebug ) + server->SendPM(client, "Start Quest: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID == data->iQuestSN) return QUEST_SUCCESS; + if(client->quest.quests[i].QuestID != 0) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 2: + { + if( client->questdebug ) + server->SendPM(client, "Replace Quest, keep items: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != client->ActiveQuest) continue; + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 3: + { + if( client->questdebug ) + server->SendPM(client, "Replace Quest, delete items: %u", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID != client->ActiveQuest) continue; + memset(&client->quest.quests[i], 0, sizeof(SQuest)); + client->quest.quests[i].QuestID = data->iQuestSN; + client->quest.quests[i].StartTime = time(NULL); + break; + } + client->ActiveQuest = data->iQuestSN; + } + break; + case 4: + { + if( client->questdebug ) + server->SendPM(client, "Select Quest: %u", data->iQuestSN); + client->ActiveQuest = data->iQuestSN; + } + break; + default: + break; + } + return QUEST_SUCCESS; +} + +//Update Quest Items +QUESTREWD(001) +{ + //dword uiItemSN; pos 0x00 + //byte btOp; pos 0x04 + //word nDupCNT; pos 0x06 + //byte btPartyOpt; pos 0x0a + GETREWDDATA(001); + if( client->questdebug )server->SendPM(client, "Update Quest Item (uiItemSN: %u btOp: %u nDupCNT: %u)", data->uiItemSN, data->btOp, data->nDupCNT); + CItem tmpItem; + tmpItem.itemtype = data->uiItemSN / 1000; + tmpItem.itemnum = data->uiItemSN % 1000; + tmpItem.count = data->nDupCNT; + + SQuest* curQuest = client->GetActiveQuest(); + if(curQuest == NULL) + { + //Log(MSG_DEBUG, "Couldn't find active quest, wtf? Id %u", client->ActiveQuest); + return QUEST_FAILURE; + } + curQuest->AddItem(&tmpItem, data->btOp); + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(002) +{ + GETREWDDATA(002); + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 union. This or + //short m_mVarNo pos 0x04 this + + //word m_wVarTYPE; pos 0x06 Type of quest variable + // 0 GetVarType = "ActiveQuest" + // 16 GetVarType = "SwitchBit" + // 32 GetVarType = "Remaining Time" + // 48 GetVarType = "EpisodeVar" + // 64 GetVarType = "JobVar" + // 80 GetVarType = "PlanetVar" + // 96 GetVarType = "UnionVar" + //word nValue; pos 0x08 + //byte btOp; pos 0x0a + + for(dword i = 0; i < data->iDataCnt; i++) + { + dword address = i * sizeof(STR_QUEST_DATA); + address += (dword)data; + address += 4; + STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Set quest var[%#04x][%i] - %i (Op: %i)", curQst->m_wVarTYPE, curQst->m_wVarNO, curQst->nValue, curQst->btOp ); + word nValue = curQst->nValue; + word tempValue = client->GetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO); + OperateValues(curQst->btOp, &tempValue, curQst->nValue); + client->SetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO, tempValue); + } + return QUEST_SUCCESS; +} + +//Update Stats +QUESTREWD(003) +{ + GETREWDDATA(003); + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 + //dword iValue; pos 0x08 + //byte btOp; pos 0x0c + for(dword i = 0; i < data->iDataCnt; i++) + { + //dword address = i * 0x0C; + //address += (dword)data; + //address += 4; + //STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Update stat[%i] %i (Op: %i)", data->iType, data->iValue, data->btOp); + switch( data->iType ) + { + case 2: //Gender + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Sex, data->iValue)) + return QUEST_FAILURE; + //client->CharInfo->Sex = data->iValue; + //BEGINPACKET( pak, 0x720 ); + //ADDWORD( pak, 2 ); + //ADDWORD( pak, client->CharInfo->Sex ); + //ADDWORD( pak, 0 ); + //GServer->SendToVisible( &pak, client ); + //client->client->SendPacket( &pak ); + //RESETPACKET( pak, 0x0730 ); + //ADDWORD( pak, 5 ); + //ADDWORD( pak, 0xa24d ); + //ADDWORD( pak, 0x40b3 ); + //GServer->SendToVisible( &pak, client ); + //client->client->SendPacket( &pak ); + client->quicksave(); + } + break; + case 4: //job + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Job, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + client->quicksave(); + } + break; + case 5: //union + { + if (!OperateValues(data->btOp, (int*)&client->Union_s->unionvar[0], data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + client->quicksave(); + //Log(MSG_DEBUG,"Client Union changed"); + } + break; + case 8: // face + { + if (!OperateValues(data->btOp, (int*)&client->CharInfo->Face, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 8); + ADDWORD(pak, client->CharInfo->Face); + ADDWORD(pak, 0); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + client->quicksave(); + } + break; + case 9: // Hair + { + if (!OperateValues(data->btOp, (int*)&client->CharInfo->Hair, data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x721); + ADDWORD(pak, 9); + ADDWORD(pak, client->CharInfo->Hair); + ADDWORD(pak, 0); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + RESETPACKET(pak, 0x0730); + ADDWORD(pak, 5); + ADDWORD(pak, 0xa24d); + ADDWORD(pak, 0x40b3); + GServer->SendToVisible( &pak, client ); + //client->client->SendPacket(&pak); + client->quicksave(); + } + case 81: // Union points for junon order + case 82: + case 83: // Union points for righteous crusaders + case 84: + case 85: + case 86: + case 87: // Union 7 points used for tracking previous memberships + case 88: + case 89: //kuro-Tejina Union points + case 90: + { + if (!OperateValues(data->btOp, (int*)&client->Union_s->unionvar[data->iType - 80], data->iValue)) + return QUEST_FAILURE; + BEGINPACKET(pak, 0x720 ); + ADDWORD(pak, data->iType ); + ADDDWORD(pak, data->iValue ); + client->client->SendPacket( &pak ); + + if(data->iType == 89) //update the database if the quest just gave KTPoints + { + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + client->Session->KTPoints,client->Session->userid, client->Session->password); + } + } + break; + case 10: //strength + if(!OperateValues(data->btOp, (int*)&client->Attr->Str, data->iValue)) + return QUEST_FAILURE; + break; + case 11: //dexterity + if(!OperateValues(data->btOp, (int*)&client->Attr->Dex, data->iValue)) + return QUEST_FAILURE; + break; + case 12: //inteligence + if(!OperateValues(data->btOp, (int*)&client->Attr->Int, data->iValue)) + return QUEST_FAILURE; + break; + case 13: //concentration + if(!OperateValues(data->btOp, (int*)&client->Attr->Con, data->iValue)) + return QUEST_FAILURE; + break; + case 14: //charm + if(!OperateValues(data->btOp, (int*)&client->Attr->Cha, data->iValue)) + return QUEST_FAILURE; + break; + case 15: //sense + if(!OperateValues(data->btOp, (int*)&client->Attr->Sen, data->iValue)) + return QUEST_FAILURE; + break; + case 30: // set global XP rate + { + GServer->Config.EXP_RATE = data->iValue; + return QUEST_SUCCESS; + } + break; + case 31: //level + if(!OperateValues(data->btOp, (int*)&client->Stats->Level, data->iValue)) + return QUEST_FAILURE; + break; + case 32: // Stat points + if(!OperateValues(data->btOp, (int*)&client->CharInfo->StatPoints, data->iValue)) + return QUEST_FAILURE; + break; + case 37: // Skill points + if(!OperateValues(data->btOp, (int*)&client->CharInfo->SkillPoints, data->iValue)) + return QUEST_FAILURE; + break; + //case sMoney: + //if(!OperateValues(data->btOp, (int*)&client->CharInfo->Zulies, data->iValue)) + //return QUEST_FAILURE; + //break; + case 40: //money + { + if(!OperateValues(data->btOp, (int*)&client->CharInfo->Zulies, data->iValue)) + return QUEST_FAILURE; + // Send zuly update packet + BEGINPACKET(pak, 0x71D); + ADDQWORD( pak, client->CharInfo->Zulies ); + client->client->SendPacket( &pak ); + } + break; + default: + Log(MSG_WARNING, "Type Unknown: '%i'", data->iType); + break; + } + } + return QUEST_SUCCESS; +} + +//Set Quest Variable +QUESTREWD(004) +{ + return QUEST_REWD_002(server, client, raw); +} + + //Give Reward +QUESTREWD(005) +{ + GETREWDDATA(005); + //byte btTarget; // pos 0x00 + //union + //{ + // byte btEquation; // pos 0x02 apparently should be 0x01 + // word nEquation; + //}; + //byte btEquation; // pos 0x01 + //word nEquation; // pos 0x02 + //int iValue; // pos 0x04 + //dword iItemSN; // pos 0x06 + //union + //{ + // byte btPartyOpt; // pos 0x0a + // word nPartyOpt; + //}; + //word nItemOpt; // pos 0x0c + //word nSkill. Co-opting nItemOpt as a skill id + word nSkill = data->nItemOpt; + if( client->questdebug ) + server->SendPM(client, "Quest reward 005 using equation %i", data->btEquation); + switch(data->btTarget) + { + case 0://EXP + { + if( client->questdebug ) + server->SendPM(client, "Give EXP: %i", data->iValue); + client->CharInfo->Exp += GetRewardValue(data->btEquation, data->iValue, client, 0); + BEGINPACKET(pak, 0x79b); + ADDDWORD(pak, client->CharInfo->Exp); + ADDDWORD(pak, client->CharInfo->stamina); + ADDWORD (pak, 0); + client->client->SendPacket(&pak); + } + break; + case 1://Zuly + { + if( client->questdebug ) + server->SendPM(client, "Give Zuly: %i", data->iValue); + unsigned int j = client->CheckQuest; + int itemcount = client->quest.quests[j].Items[0].count; + if( client->questdebug ) + server->SendPM(client, "Items returned: %i", itemcount); + //int Zulyreward = GetRewardValue(data->btEquation, data->iValue, client, itemcount); + // temp fix + int Zulyreward = data->iValue; + if(itemcount > 0 ) //this is for cases where multiple items are handed in for fetch quest. + Zulyreward = data->iValue * itemcount; + client->CharInfo->Zulies += Zulyreward; + //client->CharInfo->Zulies += GetRewardValue(data->btEquation, data->iValue, client, 1);//dunno nDupCount for this one! + BEGINPACKET(pak, 0x71D); + ADDQWORD(pak, client->CharInfo->Zulies); + client->client->SendPacket(&pak); + } + break; + case 2://Item + { + CItem nItem; + nItem.itemtype = data->iItemSN / 1000; + nItem.itemnum = data->iItemSN % 1000; + if(nItem.IsStackable()) + { + nItem.count = GetRewardValue(data->btEquation, data->iValue, client, 0); + } + else + { + nItem.count = 1; + } + if( client->questdebug ) + server->SendPM(client, "Give item [%i][%i]x%i", nItem.itemtype, nItem.itemnum, nItem.count); + nItem.durability = GServer->STB_ITEM[nItem.itemtype-1].rows[nItem.itemnum][29]; + nItem.gem = 0; + nItem.stats = 0; + nItem.refine = 0; + nItem.socketed = 0; + nItem.lifespan = 100; + nItem.appraised = 1; + dword slot = client->AddItem(nItem); + if (slot == 0xffff) // Fail + { + BEGINPACKET( pak, 0x7a7); + ADDWORD(pak, 0x00); + ADDBYTE(pak, 0x03); + ADDBYTE(pak, 0x00); + client->client->SendPacket(&pak); + } + else // Success + { + BEGINPACKET( pak, 0x71f); + ADDBYTE(pak, 0x01); + ADDBYTE(pak, slot); + ADDWORD(pak, client->items[slot].GetPakHeader( ) ); + ADDDWORD(pak, client->items[slot].GetPakData( ) ); + client->client->SendPacket(&pak); + } + } + break; + //PY: This seems a little overcomplicated. we can just send the request directly to AddBuffs() or UseBuffSkill() + /*case 3://Recieve Buff. start battle so that AIP can actually do the buffing based on RefVar values + { + CCharacter* monster = GServer->GetMonsterByID(client->NPCvar,client->Position->Map); + monster->StartAction( client, SKILL_ATTACK, nSkill ); + + } + break; + */ + case 3: + { + //need a CCharacter object first + CSkills* thisskill = GServer->GetSkillByID( nSkill ); + client->UseBuffSkillQSD(client, thisskill); + } + break; + default: + break; + } + return QUEST_SUCCESS; +} + +//Restore HP/MP +QUESTREWD(006) +{ + //dword iPercentOfHP pos 0x00 + //dword iPercentOfMP pos 0x04 + //byte btPartyOpt pos 0x08 + GETREWDDATA(006); + if( client->questdebug ) + server->SendPM(client, "Restore %i%% HP, %i%% MP", data->iPercentOfHP, data->iPercentOfMP); + client->Stats->HP = (long int)((float)client->Stats->MaxHP / 100.0f) * data->iPercentOfHP; + client->Stats->MP = (long int)((float)client->Stats->MaxHP / 100.0f) * data->iPercentOfMP; + return QUEST_SUCCESS; +} + +//Teleport +QUESTREWD(007) +{ + //dword iZoneSN; pos 0x00 + //dword iX; pos 0x04 + //dword iY; pos 0x08 + //byte btPartyOpt; pos 0x0d + GETREWDDATA(007); + fPoint thispoint; + thispoint.x = floor(((float)data->iX)/100); + thispoint.y = floor(((float)data->iY)/100); + if( client->questdebug ) + server->SendPM(client, "Teleport [%i][%f][%f]", data->iZoneSN, thispoint.x, thispoint.y); + GServer->TeleportTo(client, data->iZoneSN, thispoint); + return QUEST_SUCCESS; +} + +//Spawn Monster +QUESTREWD(008) +{ + //dword iMonsterSN pos 0x00 + //dword iHowMany pos 0x04 + // byte btWho pos 0x08 or + // int iWho pos 0x08 + //dword iZoneSN pos 0x0a + //dword iX pos 0x0e + //dword iY pos ox11 + //dword iRange pos 0x15 + //dword iTeamNo pos 0x19 + GETREWDDATA(008); + fPoint position; + dword mapId; + if(data->iX == 0 || data->iY == 0 || data->iZoneSN == 0) + { + position.x = client->Position->current.x; + position.y = client->Position->current.y; + mapId = client->Position->Map; + } + else + { + position.x = data->iX / 100; + position.y = data->iY / 100; + mapId = data->iZoneSN; + } + for(dword i = 0; i < data->iHowMany; i++) + { + fPoint pos = GServer->RandInCircle( position, data->iRange ); + if( client->questdebug ) + server->SendPM(client, "Spawn mob[%i] @ %f, %f", data->iMonsterSN, pos.x, pos.y); + CMap* map = GServer->MapList.Index[mapId]; + CMonster* mon = map->AddMonster( data->iMonsterSN, pos, 0, 0 , 1 ); + if(data->iMonsterSN > 750 && data->iMonsterSN < 755) // ghost rackies = non aggresive + { + mon->thisnpc->aggresive = 0; + } + else + { + mon->thisnpc->aggresive = 999; // Force the mob to be agressive. + } + mon->lastSighCheck = 0; // Force sight check instantly. + } + return QUEST_SUCCESS; +} + +//Execute Quest Trigger +QUESTREWD(009) +{ + //word shNameLen pos 0x00 + //string szNextTriggerSN pos 0x02 + GETREWDDATA(009); + char* tempName = reinterpret_cast(&data->szNextTriggerSN) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, "Execute Quest Trigger %s[%d] [%08x]", tempName, data->shNameLen, hash); + return client->ExecuteQuestTrigger(hash); + //return QUEST_SUCCESS; +} + +//Reset Stats +QUESTREWD(010) +{ + if( client->questdebug ) + server->SendPM(client, "Reset Stats"); + client->CharInfo->StatPoints = 0; + client->Attr->Str = 15; + client->Attr->Dex = 15; + client->Attr->Int = 15; + client->Attr->Con = 15; + client->Attr->Cha = 10; + client->Attr->Sen = 10; + for(int i = 2; i <= client->Stats->Level; i++) + { + client->CharInfo->StatPoints += 10; + client->CharInfo->StatPoints += i / 2; + } + return QUEST_SUCCESS; +} + +//Update Object Var +QUESTREWD(011) +{ + GETREWDDATA(011); + /*if(data->btWho == 0) //npc + { + CMonster* monster = reinterpret_cast(entity); + if(monster == NULL) return QUEST_FAILURE; + if(data->nVarNo > 19) return AI_FAILURE; + short tempval = GServer->ObjVar[monster->thisnpc->refNPC][data->nVarNo]; + switch(data->btOp) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + case 9: + tempval++; + break; + default: + return QUEST_FAILURE; + break; + } + if(tempval < 0)tempval = 0; + GServer->ObjVar[monster->thisnpc->refNPC][data->nVarNo] = tempval; + return QUEST_SUCCESS; + } + else if(data->btWho == 1) // event + { + //need to write the event code still it seems. Seems easy enough + return QUEST_FAILURE; + }*/ + + + /*if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + + if(data->btWho == 0){//Npc + CNpc* thisNpc = reinterpret_cast(entity); + thisNpc = thisNpc->SelectedNpc; + if(thisNpc == NULL) return QUEST_FAILURE; + + short VarValue = thisNpc->ObjVar.GetVar(data->nVarNo); + OperateValues(data->btOp, &VarValue, (short)data->iValue); + thisNpc->ObjVar.SetVar(data->nVarNo, VarValue); + }else if(data->btWho == 1){//Event + short VarValue = server->EventVar.GetVar(data->nVarNo); + OperateValues(data->btOp, &VarValue, (short)data->iValue); + server->EventVar.SetVar(data->nVarNo, VarValue); + } + + + return QUEST_SUCCESS;*/ + //byte btWho; pos 0x00 + //word nVarNo; pos 0x02 + //dword iValue; pos 0x04 this was an int but ints only hold 2 bytes so btop would be out of position + //byte btOp; pos 0x08 btop definitely at position 0x08 so had to adjust iValue + int tempval; + if(data->btWho == 0) + { + if(data->nVarNo > 19) return AI_FAILURE; + tempval = GServer->ObjVar[client->NPCvar][data->nVarNo]; + //Log(MSG_INFO,"Just checking. NPCvar = %i", client->NPCvar); + switch( data->btOp ) + { + case 5: + tempval = data->iValue; + break; + case 6: + tempval += data->iValue; + break; + case 7: + tempval -= data->iValue; + break; + case 9: + tempval++; + break; + default: + return QUEST_FAILURE; + break; + } + } + else if(data->btWho == 1) //event + { + // not sure what to do here yet + return QUEST_FAILURE; + } + GServer->ObjVar[client->NPCvar][data->nVarNo] = tempval; + return QUEST_SUCCESS; +} + +//NPC Speak +QUESTREWD(012) +{ + //if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + GETREWDDATA(012); +/* + CNpc* thisNpc = reinterpret_cast(entity); + if(thisNpc->SelectedNpc == NULL) return QUEST_FAILURE; + std::map::iterator triggerITR = server->NpcNames.find(thisNpc->SelectedNpc->NpcID); + if(triggerITR == server->NpcNames.end()) return QUEST_FAILURE; + + char* npcName = triggerITR->second; + char* sayStr = server->lngQst->GetLTBStr(data->iStrID); + if(sayStr == NULL) return QUEST_FAILURE; + if(data->btMsgType == 1){ + //Shout + CPacket pakout(0x785); + pakout.Add(npcName); + pakout.Add(sayStr); + server->SendPacketToZone(thisNpc, &pakout); + }else if(data->btMsgType == 2){ + //Announce + CPacket pakout(0x702); + pakout.AddBytes((byte*)npcName, strlen(npcName)); + pakout.Add('>'); + pakout.Add(sayStr); + server->SendPacketToZone(thisNpc, &pakout); + } + delete [] sayStr;*/ + + dword npcId = 0; + //if(entity->_EntityType == ENTITY_NPC) + //CMonster* thisMonster = reinterpret_cast(entity); + switch(data->btMsgType) + { + case 0: //whisper to client + // don't think this is possible. No way to get a character to send it to?? + break; + case 1: //shout to map + Log(MSG_INFO,"Quest -- NPC Shouts LTB string %i to map %i",data->iStrID,client->Position->Map); + GServer->NPCShout(client->Position->Map,GServer->Ltbstring[data->iStrID].QuestLTB,GServer->Ltbstring[data->iStrID].QuestNpcName); + break; + case 2: //announce to server. + Log(MSG_INFO,"Quest -- NPC announces LTB string %i to map %i",data->iStrID,client->Position->Map); + GServer->NPCAnnounce(GServer->Ltbstring[data->iStrID].QuestLTB,GServer->Ltbstring[data->iStrID].QuestNpcName); + break; + default: + break; + } + + return QUEST_SUCCESS; + // This is cool and all, but we lack the stuff to do it - Drakia + // Not any more - PY +} + +//Execute Quest Trigger with time delay. STILL NEEDS TO BE CODED +QUESTREWD(013) +{ + // byte btWho pos 0x00 + // int iWho pos 0x00 + //dword iSec pos 0x02 + //word nNameLength pos 0x06 + //string szTriggerName pos 0x08 + //dword m_HashNextTrigger pos 0x06 + nNameLength? + //looks like a quest trigger with a timer delay to me + GETREWDDATA(013); + char* tempName = reinterpret_cast(&data->szTriggerName) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, " RWD 013 Execute Quest Trigger %s[%d] [%08x]", tempName, data->nNameLength, hash); + //suppress the actual execution for now just to be sure + return client->ExecuteQuestTrigger(hash); + return QUEST_SUCCESS; +} + +//Learn Skill +QUESTREWD(014) +{ + // byte btOp pos 0x00 + // int iOp pos 0x00 + //dword iSkillNo pos 0x02 + GETREWDDATA(014); + if( client->questdebug ) + server->SendPM(client, "Learn skill: %i", data->iSkillNo); + GServer->LearnSkill(client, data->iSkillNo, false); + return QUEST_SUCCESS; +} + +//Set Quest Flag +QUESTREWD(015) +{ + GETREWDDATA(015); + //word nSN; 0x00 + //byte btOp; 0x02 + if( client->questdebug ) + server->SendPM(client, "Set QFlag[%i]=%i", data->nSN, data->btOp); + client->quest.SetFlag(data->nSN, (data->btOp == 1)?true:false); + // PY method + //client->quest.Qflags[data->nSN] == data->btOp; + // PY end + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(016) +{ + //word nGroupSN pos 0x00 + return QUEST_SUCCESS; +} + +//Reset All Quest Flags +QUESTREWD(017) +{ + if( client->questdebug ) + server->SendPM(client, "Reset all quest flags"); + //memset(&client->quest.flags, 0, 64); + // PY method + for(UINT i=0;i<1000;i++) + { + for(int j=0;j<8;j++) + { + client->quest.Qflags[i][j] = 0; + } + } + // PY end + return QUEST_SUCCESS; +} + +//Send Announcement +QUESTREWD(018) +{ + //dword iStrID pos 0x00 + //word nCnt pos 0x04 + //string Data pos 0x06 + //why isn't this coded? looks simple enough + GETREWDDATA(018); + char* tempName = reinterpret_cast(&data->Data) - 2; + if( client->questdebug ) + server->SendPM(client, " RWD 018 Send announcement %s", tempName); + Log(MSG_INFO,"Announcement to all %s",tempName); + GServer->NPCAnnounce(tempName,"Quest NPC"); + return QUEST_SUCCESS; +} + +//Execute Quest Trigger in Other Map +QUESTREWD(019) +{ + //this is going to be a pain + //it runs a quest trigger that would appear to be sent to every player in the designated zone + + //word nZoneNo; //pos 0x00 + //word nTeamNo; //pos 0x02 + //word nTriggerLength; //pos 0x04 This is definitely NOT the length of the following name. PY + //string TriggerName; //pos 0x06 + nTriggerlength more bytes. name is zero terminated so m_hashtrigger position varies + //dword m_HashTrigger; + //word m_HashTrigger; //pos 0x17 only has 2 bytes not 4. PY + + // let's check the values first + GETREWDDATA(019); + //Log(MSG_DEBUG,"Execute Quest trigger: %s for zone %i. Hash: %i",data->TriggerName, data->nZoneNo, data->m_HashTrigger); + + /*CMap* map = GServer->MapList.Index[data->nZoneNo]; + if(map == NULL) return QUEST_SUCCESS; + if( map->PlayerList.size()<1 ) return QUEST_SUCCESS; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + player->ExecuteQuestTrigger(data->m_HashTrigger); + } + return QUEST_SUCCESS;*/ + + char* tempName = reinterpret_cast(&data->TriggerName) - 2; + dword hash = MakeStrHash(tempName); + CMap* map = GServer->MapList.Index[data->nZoneNo]; + if(map == NULL) return QUEST_SUCCESS; + if( map->PlayerList.size()<1 ) return QUEST_SUCCESS; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + player->ExecuteQuestTrigger(hash); + } + + return QUEST_SUCCESS; +} + +//PvP Status +QUESTREWD(020) +{ + GETREWDDATA(020); + //byte btNoType; pos 0x00 + + return QUEST_SUCCESS; +} + +//Set Respawn Position +QUESTREWD(021) +{ + //dword iX pos 0x00 + //dword iY pos 0x04 + GETREWDDATA(021); + client->Position->saved = client->Position->Map; + return QUEST_SUCCESS; +} + +//Unknown +QUESTREWD(022) +{ + //word nZonNo pos 0x00 + //byte btOp pos 0x02 + return QUEST_SUCCESS; +} + +//Raise Clan Grade - This is not the least bit efficient - Drakia +QUESTREWD(023) +{ + GETREWDDATA(023); + if( client->questdebug ) + server->SendPM(client, "Raise clan grade"); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + continue; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if( player->Clan->clanid != client->Clan->clanid ) continue; + player->Clan->grade++; + + //load clan info in char server + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0xfb ); //action to update clan informacion (charserver) + ADDWORD ( pak, player->Clan->clanid ); + ADDWORD ( pak, player->Clan->grade ); + GServer->SendISCPacket( &pak ); + + //Send to other players + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, player->clientid ); + ADDWORD ( pak, player->Clan->clanid); + ADDWORD ( pak, 0x0000 );//??? + ADDWORD ( pak, player->Clan->back ); + ADDWORD ( pak, player->Clan->logo ); + ADDBYTE ( pak, player->Clan->grade );//clan grade + ADDBYTE ( pak, 0x06 );//clan rank + ADDSTRING ( pak, player->Clan->clanname ); + ADDBYTE ( pak, 0x00 ); + GServer->SendToVisible( &pak, player ); + } + } + GServer->DB->QExecute("UPDATE list_clan SET grade=%i WHERE id=%i", client->Clan->grade, client->Clan->clanid); + return QUEST_SUCCESS; +} + +//Clan Money +QUESTREWD(024) +{ + //int iMONEY pos 0x00 + //byte btOP pos 0x02 + GETREWDDATA(024); + return QUEST_SUCCESS; +} + +//Clan Points +QUESTREWD(025) +{ + //short nPOINT pos 0x00 + //byte btOP pos 0x02 + GETREWDDATA(025); + int points = data->nPOINT; + //this whole function appears to be screwed up. Fix later + /* + + + //Loading "real" CP points. + //getClanPoints function is mysteriously missing so skip it for now + //client->Clan->CP = GServer->getClanPoints(client->Clan->clanid); + + //we "delete" points. + if(data->btOP == 7) + { + points =- 1 * points; + } + + if (client->Clan->CP + points < 0) + { + client->Clan->CP = 0; + } + else + { + client->Clan->CP += points; + } + + //sending packet. + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0xfe ); + ADDDWORD ( pak, client->CharInfo->charid); //charid + ADDDWORD ( pak, points); //Clan points (to be added) + cryptPacket( (char*)&pak, GServer->cct ); + send( GServer->csock, (char*)&pak, pak.Size, 0 ); + + Log(MSG_WARNING,"We added %i CP",points); + + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x35 ); + ADDWORD ( pak, client->clientid ); + ADDWORD ( pak, client->Clan->clanid ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, client->Clan->back ); + ADDWORD ( pak, client->Clan->logo ); + ADDBYTE ( pak, client->Clan->grade ); + ADDBYTE ( pak, client->Clan->clanrank); + ADDSTRING ( pak, client->Clan->clanname ); + ADDBYTE ( pak, 0x00 ); + client->client->SendPacket(&pak); + + */ + return QUEST_SUCCESS; +} + +//Clan Skill +QUESTREWD(026) +{ + GETREWDDATA(026); + //word nSkillNo; pos 0x00 + //byte btOP; pos 0x02 + //do something (based on btOp) with a clan skill id = data->nSkillNo + if( client->questdebug )server->SendPM(client, "Clan skill rwd[026] skill id: %i btOP: %i",data->nSkillNo,data->btOP); + return QUEST_SUCCESS; +} + +//Clan Contribution +QUESTREWD(027) +{ + GETREWDDATA(027); + //short nCONT; pos 0x00 + //byte btOP; pos 0x02 + if( client->questdebug )server->SendPM(client, "Clan Contribution rwd[026] nCONT: %i btOP: %i",data->nCONT,data->btOP); + + return QUEST_SUCCESS; +} + +//Clan Teleportation - Again, not efficient. Maybe keep a list of pointers to clan members? - Drakia +QUESTREWD(028) +{ + //dword iRange; pos 0x00 + //word nZoneNo; pos 0x04 + //dword iX; pos 0x06 + //dword iY; pos 0x0a + GETREWDDATA(028); + fPoint telepos; + fPoint newPos; + telepos.x = data->iX/100; + telepos.y = data->iY/100; + if( client->questdebug ) + server->SendPM(client, "Clan teleport to [%i][%f][%f]", data->nZoneNo, telepos.x, telepos.y); + for(UINT i=0;iMapList.Map.size();i++) + { + CMap* map = GServer->MapList.Map.at(i); + if( map->PlayerList.size()<1 ) + continue; + for(UINT j=0;jPlayerList.size();j++) + { + CPlayer* player = map->PlayerList.at(j); + if( player->Clan->clanid != client->Clan->clanid ) continue; + newPos = GServer->RandInCircle(telepos, data->iRange); + GServer->TeleportTo(player, data->nZoneNo, newPos ); + } + } + return QUEST_SUCCESS; +} + +QUESTREWD(029) //execute client quest trigger (LUA) +{ + GETREWDDATA(029); + char* tempName = reinterpret_cast(&data->LuaName) - 2; + dword hash = MakeStrHash(tempName); + if( client->questdebug ) + server->SendPM(client, "Execute Client LUA %s[%d] [%08x]", tempName, data->strLen, hash); + //return client->ExecuteQuestTrigger(hash); + //need to code a packet here. on evo it's 0x7f8. Don't even know if we use one here + Log(MSG_DEBUG,"ran quest action 29"); + return QUEST_SUCCESS; +} + +QUESTREWD(030) //Reset skills +{ + //Log(MSG_DEBUG,"ran quest action 30"); + for(int i=0;i<30;i++) + { + //give one sp for every active skill present + if(client->askill[i] != 0) + { + client->CharInfo->SkillPoints += 1; + } + client->askill[i] = 0; + //give SP equal to the level of the active skill. Actual level is always 1 more than this + client->CharInfo->SkillPoints += client->askilllvl[i]; + client->askilllvl[i] = 0; + //give one sp for every passive skill present + if(client->pskill[i] != 0) + { + client->pskill[i] = 0; + client->CharInfo->SkillPoints += 1; + } + //give SP equal to the level of the passive skill. Actual level is always 1 more than this + client->CharInfo->SkillPoints += client->pskilllvl[i]; + client->pskilllvl[i] = 0; + } + client->savedata(); + return QUEST_SUCCESS; +} +QUESTREWD(050) //Respawn NPC after time delay. Used only on the missing children in Zant +{ + return QUEST_SUCCESS; +} diff --git a/Src/World Server/quest/QuestActions.h b/Src/World Server/quest/QuestActions.h new file mode 100644 index 0000000..cd0fe28 --- /dev/null +++ b/Src/World Server/quest/QuestActions.h @@ -0,0 +1,242 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#pragma once +#define QUESTREWD(reward) int QUEST_REWD_ ## reward (class CWorldServer* server, class CPlayer* client, byte* raw) +typedef int (*fpQuestRewd)(class CWorldServer*, class CPlayer*, byte*); +#define GETREWDDATA(rewd) STR_REWD_ ## rewd * data = (STR_REWD_ ## rewd *)raw + +QUESTREWD(000);QUESTREWD(001);QUESTREWD(002); +QUESTREWD(003);QUESTREWD(004);QUESTREWD(005); +QUESTREWD(006);QUESTREWD(007);QUESTREWD(008); +QUESTREWD(009);QUESTREWD(010);QUESTREWD(011); +QUESTREWD(012);QUESTREWD(013);QUESTREWD(014); +QUESTREWD(015);QUESTREWD(016);QUESTREWD(017); +QUESTREWD(018);QUESTREWD(019);QUESTREWD(020); +QUESTREWD(021);QUESTREWD(022);QUESTREWD(023); +QUESTREWD(024);QUESTREWD(025);QUESTREWD(026); +QUESTREWD(027);QUESTREWD(028);QUESTREWD(029); +QUESTREWD(030);QUESTREWD(050); + +struct STR_REWD_000 { + dword iQuestSN; + byte btOp; +}; + +struct STR_REWD_001 { + dword uiItemSN; + union { + byte btOp; + word nOp; + }; + word nDupCNT; + byte btPartyOpt; +}; + +struct STR_REWD_002 +{ + dword iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_REWD_003 +{ + dword iDataCnt; //pos 0x00 + dword iType; //pos 0x04 + dword iValue; //pos 0x08 + byte btOp; //pos 0x0c + //this (below original code) is just plain wrong + //dword iDataCnt; + //STR_ABIL_DATA* CheckData; + //byte btPartyOpt; +}; + +struct STR_REWD_004 { + dword iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_REWD_005 +{ + byte btTarget; // pos 0x00 + //union + //{ + // byte btEquation; // pos 0x02 apparently should be 0x01 + // word nEquation; + //}; + byte btEquation; // pos 0x01 + word nEquation; // pos 0x02 + int iValue; // pos 0x04 + dword iItemSN; // pos 0x06 + union + { + byte btPartyOpt; // pos 0x0a + word nPartyOpt; + }; + word nItemOpt; // pos 0x0c +}; + +struct STR_REWD_006 { + dword iPercentOfHP; + dword iPercentOfMP; + byte btPartyOpt; +}; + +struct STR_REWD_007 { + dword iZoneSN; + dword iX; + dword iY; + byte btPartyOpt; +}; + +struct STR_REWD_008 { + dword iMonsterSN; + dword iHowMany; + union { + byte btWho; + int iWho; + }; + dword iZoneSN; + dword iX; + dword iY; + dword iRange; + dword iTeamNo; +}; + +struct STR_REWD_009 { + word shNameLen; + string szNextTriggerSN; +}; + +struct STR_REWD_010 { +}; + +struct STR_REWD_011 +{ + union + { + byte btWho; + word usWho; + }; + word nVarNo; + dword iValue; + byte btOp; +}; + +struct STR_REWD_012 { + union + { + byte btMsgType; + int iMsgType; + }; + dword iStrID; +}; + +struct STR_REWD_013 { + union { + byte btWho; + int iWho; + }; + dword iSec; + word nNameLength; + string szTriggerName; + dword m_HashNextTrigger; +}; + +struct STR_REWD_014 +{ + union + { + byte btOp; + int iOp; + }; + dword iSkillNo; +}; + +struct STR_REWD_015 { + word nSN; + byte btOp; +}; + +struct STR_REWD_016 { + word nGroupSN; +}; + +struct STR_REWD_017 { +}; + +struct STR_REWD_018 { + dword iStrID; + word nCnt; + string Data; +}; + +struct STR_REWD_019 +{ + word nZoneNo; //pos 0x00 + word nTeamNo; //pos 0x02 + word nTriggerLength; //pos 0x04 This is definitely NOT the length of the following name. PY + string TriggerName; //pos 0x06 + nTriggerlength more bytes. Name always appears to be 17 bytes long + //dword m_HashTrigger; + word m_HashTrigger; //pos 0x17 only has 2 bytes not 4. PY +}; + +struct STR_REWD_020 +{ + byte btNoType; +}; + +struct STR_REWD_021 { + dword iX; + dword iY; +}; + +struct STR_REWD_022 { + word nZonNo; + byte btOp; +}; + +struct STR_REWD_023 +{ +}; + +struct STR_REWD_024 { + int iMONEY; + byte btOP; +}; + +struct STR_REWD_025 { + short nPOINT; + byte btOP; +}; + +struct STR_REWD_026 { + word nSkillNo; + byte btOP; +}; + +struct STR_REWD_027 { + short nCONT; + byte btOP; +}; + +struct STR_REWD_028 { + dword iRange; + word nZoneNo; + dword iX; + dword iY; +}; + +struct STR_REWD_029 { + word strLen; + string LuaName; +}; + +struct STR_REWD_030 //reset skills +{ + +}; + +struct STR_REWD_050 //respawn NPC after time delay +{ + +}; + diff --git a/Src/World Server/quest/QuestConditions.cpp b/Src/World Server/quest/QuestConditions.cpp new file mode 100644 index 0000000..e417033 --- /dev/null +++ b/Src/World Server/quest/QuestConditions.cpp @@ -0,0 +1,771 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#include "../WorldServer.h" + +//Check Quest +QUESTCOND(000) +{ + GETCONDDATA(000); + if( client->questdebug ) + server->SendPM(client, "Check quest: %i", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID == data->iQuestSN) + { + client->CheckQuest = i; + client->ActiveQuest = data->iQuestSN; + if( client->questdebug ) + server->SendPM(client, "Check Quest - Found!"); + return QUEST_SUCCESS; + } + } + if( client->questdebug ) + server->SendPM(client, "Check Quest - Not Found!"); + return QUEST_FAILURE; +} + +//Check Quest Variable +QUESTCOND(001) +{ + // int iDataCnt pos 0x00 + // dword iType pos 0x02 + // short m_wVarNO pos 0x02 + // word m_wVarTYPE pos 0x04 + // word nValue pos 0x06 + // byte btOp pos 0x08 + GETCONDDATA(001); + int retVal = 1; + if( client->questdebug )server->SendPM(client, "Data Count for this quest %i",data->iDataCnt); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * sizeof(STR_QUEST_DATA); + address += (dword)data; + address += 4; + STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; + word nValue = curQst->nValue; + word tempValue = client->GetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO); + //word tempValue = client->quest.quests[curQst->m_wVarTYPE].Variables[curQst->m_wVarNO]; + //retVal = OperateValues(curQst->btOp, &tempValue, curQst->nValue); + switch(curQst->btOp) + { + case 0: + retVal = (tempValue == curQst->nValue); + break; + case 1: + retVal = (tempValue > curQst->nValue); + break; + case 2: + retVal = (tempValue >= curQst->nValue); + break; + case 3: + retVal = (tempValue < curQst->nValue); + break; + case 4: + retVal = (tempValue <= curQst->nValue); + break; + case 10: + retVal = (tempValue != curQst->nValue); + break; + default: + retVal = 0; + Log( MSG_DEBUG, "QuestCond 001 bad value for btOp %i",curQst->btOp ); + break; + } + if( client->questdebug ) + { + server->SendPM(client, "is %i < %i ? retval %i",tempValue, curQst->nValue, retVal); + server->SendPM(client, "Check Quest Var[%04x][%i] - %i (Op: %i)", curQst->m_wVarTYPE, curQst->m_wVarNO, curQst->nValue, curQst->btOp); + } + if(retVal == 0) return QUEST_FAILURE; + } + return (retVal == 0)?QUEST_FAILURE:QUEST_SUCCESS; +} + +//Check Quest Variable +QUESTCOND(002) +{ + return QUEST_COND_001(server, client, raw); +} + +//Check Stats +QUESTCOND(003) +{ + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 + //dword iValue; pos 0x08 + //byte btOp; pos 0x0c + GETCONDDATA(003); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * 0x0C; + address += (dword)data; + address += 4; + STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Check Stat[%i] %i (Op: %i)", curAbil->iType, curAbil->iValue, curAbil->btOp); + int tempValue; + switch( curAbil->iType ) + { + case sGender: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->Sex, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sJob: + tempValue = client->CharInfo->Job / 100; + if( client->questdebug ) + server->SendPM(client, "Job Tempvalue = %i", tempValue); + //if(!OperateValues(curAbil->btOp, (int*)&tempValue, curAbil->iValue)) + if(!OperateValues(curAbil->btOp, &tempValue, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sUnion: // Unions aren't implemented? - Drakia + if(!OperateValues(curAbil->btOp, (int*)&client->Union_s->unionvar[0], curAbil->iValue)) + return QUEST_FAILURE; + break; + case sStrength: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Str, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sDexterity: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Dex, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sIntelligence: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Int, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sConcentration: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Con, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sCharm: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Cha, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sSensibility: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Sen, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sLevel: + if(!OperateValues(curAbil->btOp, (int*)&client->Stats->Level, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sStatPoints: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->StatPoints, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sMoney: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->Zulies, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sEXPRate: + if(!OperateValues(curAbil->btOp, (int*)&client->Stats->xprate, curAbil->iValue)) + return QUEST_FAILURE; + break; + default: + Log(MSG_WARNING, "QuestCond 003: Type Unknown: '%i'", curAbil->iType); + break; + } + } + return QUEST_SUCCESS; +} + +//Check Items +QUESTCOND(004) +{ + GETCONDDATA(004); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * 0x10; + address += (dword)data; + address += 4; + STR_ITEM_DATA* curItem = (STR_ITEM_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Check Item - [Slot:%i][Type:%i]", curItem->iWhere, curItem->uiItemSN); + // Check quest items for a specific amount + if (curItem->iWhere == 13) // it's a quest item + { + // Ok, check the current Check'd quest. + if (client->CheckQuest < 0) return QUEST_SUCCESS; + unsigned int j = client->CheckQuest; + bool Hasitem = false; + bool FoundItem = false; + for (unsigned int k = 0; k < 5; k++) //5 quest item slots + { + if (client->quest.quests[j].Items[k].itemnum == (curItem->uiItemSN % 1000)) + { + FoundItem = true; + if( client->questdebug ) + server->SendPM(client, "Operate - btOp: %i Val1: %i Val2: %i", curItem->btOp, client->quest.quests[j].Items[k].count, curItem->iRequestCnt); + if(OperateValues(curItem->btOp, (int*)(&client->quest.quests[j].Items[k].count), curItem->iRequestCnt)) + Hasitem = true; + } + } + if(FoundItem == false) // didn't find any of the item in any of the quest slots + { + for (unsigned int k = 0; k < 5; k++) //Find the first empty slot + { + if (client->quest.quests[j].Items[k].count == 0 && Hasitem == false) + { + // Since the item doesn't exist, treat it as if there's 0 of them. + int tempInt = 0; + if(OperateValues(curItem->btOp, &tempInt, curItem->iRequestCnt)) + Hasitem = true; + if( client->questdebug ) + server->SendPM(client, "Requested item is not found. Comparing first empty slot instead"); + } + } + } + if(Hasitem == false) + { + if( client->questdebug ) + server->SendPM(client, "Requested item is not present. Returning quest failure"); + return QUEST_FAILURE; + } + } + else + { + //item slot is not 13 + // Check equipped items for a specific item? - Drakia + if(curItem->uiItemSN == 0 && curItem->iWhere != 0) + { + int itemHead = client->items[curItem->iWhere].itemtype; + if(!OperateValues(curItem->btOp, &itemHead, curItem->uiItemSN)) + return QUEST_FAILURE; + } + } + } + if( client->questdebug ) + server->SendPM(client, "QuestCond 004 Returning Quest Success"); + return QUEST_SUCCESS; +} + +//Check Party +QUESTCOND(005) +{ + //byte btIsLeader pos 0x00 + //int iLevel pos 0x02 + //byte btReversed pos 0x04 + GETCONDDATA(005); + return QUEST_SUCCESS; +} + +//Near Point +QUESTCOND(006) +{ + //int iZoneSN; pos 0x00 + //int iX; pos 0x04 + //int iY; pos 0x08 + //int iZ; pos 0x0c + //int iRadius; pos 0x10 + GETCONDDATA(006); + client->UpdatePosition(); + if(data->iZoneSN != 0) + { + if(client->Position->Map != data->iZoneSN) + return QUEST_FAILURE; + } + float dx = client->Position->current.x - (float)(data->iX / 100); + float dy = client->Position->current.y - (float)(data->iY / 100); + float distance = sqrt((dx*dx) + (dy*dy)); + return (distance < data->iRadius)?QUEST_SUCCESS:QUEST_FAILURE; +} + +//World Time +QUESTCOND(007) +{ + //dword ulTime pos 0x00 + //dword ulEndTime pos 0x04 + return QUEST_SUCCESS; +} + +//Quest Time +QUESTCOND(008) +{ + //dword ulTime pos 0x00 + //byte btOp pos 0x04 + GETCONDDATA(008); + if( client->questdebug ) + server->SendPM(client, "QuestTime - ulTime: %i btOp: %i", data->ulTime, data->btOp); + if (client->CheckQuest < 0) return QUEST_FAILURE; // Not checking a quest, don't return success + long int Time = 0; + if (server->STB_QUEST.rows[client->quest.quests[client->CheckQuest].QuestID][1] > 0) + { + Time += client->quest.quests[client->CheckQuest].StartTime; // Start time + Time += server->STB_QUEST.rows[client->quest.quests[client->CheckQuest].QuestID][1] * 10; // Time to finish + Time -= time(NULL); // Current time + if (Time < 0) Time = 0; // Time is up + Time /= 10; // Divide to get 10's of seconds + } + if (!OperateValues(data->btOp, (dword*)&Time, data->ulTime)) + return QUEST_FAILURE; + else + return QUEST_SUCCESS; +} + +//Check Skill +QUESTCOND(009) +{ + //int iSkillSN1 pos 0x00 + //int iSkillSN2 pos 0x02 + //byte btOp pos 0x04 + GETCONDDATA(009); + // Line not needed, never used - Drakia + //int checkVal = (data->btOp)?QUEST_SUCCESS:QUEST_FAILURE; + if( client->questdebug ) + server->SendPM(client, "Check Skill: %i", data->iSkillSN1); + bool s1 = false; + bool s2 = false; + for(dword i = 0; i < 30; i++) + { + if(client->bskills[i] == data->iSkillSN1) + s1 = true; + if(client->bskills[i] == data->iSkillSN2) + s2 = true; + if(client->askill[i] == data->iSkillSN1) + s1 = true; + if(client->askill[i] == data->iSkillSN2) + s2 = true; + if(client->pskill[i] == data->iSkillSN1) + s1 = true; + if(client->pskill[i] == data->iSkillSN2) + s2 = true; + } + if(data->btOp == 0) + { + if(s1 == false && s2 == false) + return QUEST_SUCCESS; //true if player does not have either skill + else + return QUEST_FAILURE; + } + else + { + if(s1 == true || s2 == true) + return QUEST_SUCCESS; //true if player has either skill + else + return QUEST_FAILURE; + } + //Log(MSG_DEBUG,"Questcond 009 btOp is neither 0 nor 1."); + //return QUEST_FAILURE; +} + +//Unknown +QUESTCOND(010) +{ + GETCONDDATA(010); + //byte btLowPcnt; pos 0x00 + //byte btHighPcnt; pos 0x01 + // I think this is a percentage chance range. + // might be something to do with the item count though??? + + //int randv = GServer->RandNumber(0,100); + //if(randv >= data->btLowPcnt && randv <= data->btHighPcnt)return QUEST_SUCCESS; + + // problem is that this is only called in quest triggers from the client where it has already + // passed the random number test. + // we need to always return true or some quests will never finish + return QUEST_SUCCESS; +} + +//Object Variable +QUESTCOND(011) +{ + + //byte btWho pos 0x00 + //word nVarNo pos 0x02 + //int iValue pos 0x04 + //byte btOp pos 0x09 + //if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + GETCONDDATA(011); + /* + if(data->btWho == 0) + { + CNpc* thisNpc = reinterpret_cast(entity); + thisNpc = thisNpc->SelectedNpc; + if(thisNpc == NULL) return QUEST_FAILURE; + + short VarValue = thisNpc->ObjVar.GetVar(data->nVarNo); + if(OperateValues(data->btOp, &VarValue, (short)data->iValue)) return QUEST_SUCCESS; + }*/ + if(data->btWho == 0) + { + + if(data->nVarNo > 19) return AI_FAILURE; + int refVal = GServer->ObjVar[client->NPCvar][data->nVarNo]; + int tempval = 0; + switch( data->btOp ) + { + case 0: + if(refVal == data->iValue)return QUEST_SUCCESS; + break; + case 1: + if(refVal > data->iValue)return QUEST_SUCCESS; + break; + case 2: + if(refVal >= data->iValue)return QUEST_SUCCESS; + break; + case 3: + if(refVal < data->iValue)return QUEST_SUCCESS; + break; + case 4: + if(refVal <= data->iValue)return QUEST_SUCCESS; + break; + case 10: + if(refVal != data->iValue)return QUEST_SUCCESS; + break; + default: + Log(MSG_WARNING,"Unknown btOp in QuestCond 011"); + break; + } + } + return QUEST_FAILURE; +} + +//check Proximity and Trigger in Zone +QUESTCOND(012) +{ + GETCONDDATA(012); + //word iZone; pos 0x00 + //dword ulZone; pos 0x00 + //int iX; pos 0x04 + //int iY; pos 0x06 + //int iEventID; pos 0x08 + //dword m_HashEventOBJ; pos 0x08 + //this should be easy enough - PY + + return QUEST_SUCCESS; +} + +//Select NPC +QUESTCOND(013) +{ + //int iNpcNo pos 0x00 + GETCONDDATA(013); + // dammit!!!! This darn function is set up to use the CCharacter class so I can't use entity (CCharacter) + // This is going to be a serious pain in the ass + // OK so we now have a temporary player structure so we shuold be able to do this. + + //Log(MSG_DEBUG,"Setting NPC ref number AICOND(017). NPC Ref = %i",data->iNpcNo); + //CMonster* monster = reinterpret_cast(entity); + //if(monster == NULL) return AI_FAILURE; + //monster->thisnpc->refNPC = data->iNpcNo; // sets the reference variable for the correct ObjVar + //Log(MSG_DEBUG,"Set NPC ref number %i successfully", monster->thisnpc->refNPC); + //not sure this is going to work properly for an NPC. + //NPC only exists as a temporary entity so refNPC may be lost after it returns from here. + + + /*if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + + CNpc* thisNpc = reinterpret_cast(entity); + std::map::iterator triggerITR = server->NpcList.find(data->iNpcNo); + if(triggerITR == server->NpcList.end()){ + thisNpc->SelectedNpc = NULL; + return QUEST_FAILURE; + } + thisNpc->SelectedNpc = triggerITR->second; + */ + client->NPCvar = data->iNpcNo; + //Actually that was kind of easy.. lol - PY + + //return QUEST_SUCCESS; + // More NPC specific stuff + return QUEST_SUCCESS; +} + +//Check Quest Flag +QUESTCOND(014) +{ + GETCONDDATA(014); + //word nSN; pos 0x00 + //byte btOp; pos 0x02 + bool dataOp = (data->btOp)?true:false; + if( client->questdebug ) + server->SendPM(client, "Check QFlag[%i] == %i", data->nSN, data->btOp); + if(client->quest.GetFlag(data->nSN) == dataOp) + { + if( client->questdebug ) + server->SendPM(client, "Quest cond 014 (check quest flag) returned Success"); + return QUEST_SUCCESS; + } + else + { + if( client->questdebug ) + server->SendPM(client, "Quest cond 014 (check quest flag) returned Failure"); + return QUEST_FAILURE; + } +} + +//Party member count between X and Y - PY +QUESTCOND(015) +{ + + //word nNumber1 pos 0x00 + //word nNumber2 pos 0x02 + GETCONDDATA(015); + + + return QUEST_SUCCESS; +} + +//Zone Time +QUESTCOND(016) +{ + + //byte btWho pos 0x00 + //dword ulWho pos 0x00 + //dword ulTime pos 0x04 + //dword ulEndTime pos 0x08 + GETCONDDATA(016); + + return QUEST_SUCCESS; +} + +//NPC & Obj Variables? +QUESTCOND(017) +{ + //dword iNpcNo pos 0x00 + //word nVarNo pos 0x04 + //word unused pos 0x06 + //dword iNpcNo pos 0x08 + //word nVarNo pos 0x0c + //word unused pos 0x0e + //byte btOp pos 0x0f + return QUEST_SUCCESS; +} + +//Time on Date +QUESTCOND(018) +{ + //Returns SUCCESS if the system time of the server is between the defined start and end times. Else returns FAILURE. + //Allows for time dependent quests + //byte btDate; pos 0x00 + //byte btHour1; pos 0x01 + //byte btMin1; pos 0x02 + //byte btHour2; pos 0x03 + //byte btMin2; pos 0x04 + GETCONDDATA(018); + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + + if(data->btDate != 0) + { + if(sTIME.wDay != data->btDate) + return QUEST_FAILURE; + } + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin >= wFrom && wMin <= wTo) + return QUEST_SUCCESS; + return QUEST_FAILURE; +} + +//Time on Day +QUESTCOND(019) +{ + //byte btWeekDay; pos 0x00 + //byte btHour1; pos 0x01 + //byte btMin1; pos 0x02 + //byte btHour2; pos 0x03 + //byte btMin2; pos 0x04 + GETCONDDATA(019); + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + if(data->btWeekDay != 0) + { + if(sTIME.wDayOfWeek != data->btWeekDay) + return QUEST_FAILURE; + } + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin >= wFrom && wMin <= wTo) + return QUEST_SUCCESS; + return QUEST_FAILURE; +} + +//Unknown +QUESTCOND(020) +{ + //int iNo1 pos 0x00 + //int iNo2 pos 0x02 + GETCONDDATA(020); + return QUEST_SUCCESS; +} + +//Unknown +QUESTCOND(021) +{ + // byte btSelObjType pos 0x00 + // byte iRadius pos 0x04 + return QUEST_SUCCESS; +} + +//Check Server/Channel +QUESTCOND(022) +{ + //word nX pos 0x00 + //word nY pos 0x02 + return QUEST_SUCCESS; +} + +//In Clan? +QUESTCOND(023) +{ + //byte btReg pos 0x00 + GETCONDDATA(023); + int Clanid = client->Clan->clanid; + if(data->btReg == 0) + { + if(Clanid == 0)return QUEST_SUCCESS; + else return QUEST_FAILURE; + } + else + { + if(Clanid != 0)return QUEST_SUCCESS; + else return QUEST_FAILURE; + } + //btReg should only ever be 1 or 0 but just in case...... + //return QUEST_FAILURE; +} + +//Clan Position +QUESTCOND(024) +{ + //check my current rank in the clan against data->nPOS using btOP + GETCONDDATA(024); + //word nPOS; pos 0x00 + //byte btOP; pos 0x02 + int myRank = client->Clan->clanrank; + switch(data->btOP) + { + case 0: + if(myRank == data->nPOS) + return QUEST_SUCCESS; + break; + case 1: + if(myRank > data->nPOS) + return QUEST_SUCCESS; + break; + case 2: + if(myRank >= data->nPOS) + return QUEST_SUCCESS; + break; + case 3: + if(myRank < data->nPOS) + return QUEST_SUCCESS; + break; + case 4: + if(myRank <= data->nPOS) + return QUEST_SUCCESS; + break; + case 10: + if(myRank != data->nPOS) + return QUEST_SUCCESS; + break; + default: + return QUEST_FAILURE; + break; + } +} + +//Clan Contribution +QUESTCOND(025) +{ + //word nCONT pos 0x00 + //byte btOP pos 0x02 + return QUEST_SUCCESS; +} + +//Clan Grade +QUESTCOND(026) +{ + //possibly compare client's current clan grade with data->nGRD using data->btOp? + GETCONDDATA(026); + //word nGRD; + //byte btOP; + int myGrade = client->Clan->grade; + switch(data->btOP) + { + case 0: + if(myGrade == data->nGRD) + return QUEST_SUCCESS; + break; + case 1: + if(myGrade > data->nGRD) + return QUEST_SUCCESS; + break; + case 2: + if(myGrade >= data->nGRD) + return QUEST_SUCCESS; + break; + case 3: + if(myGrade < data->nGRD) + return QUEST_SUCCESS; + break; + case 4: + if(myGrade <= data->nGRD) + return QUEST_SUCCESS; + break; + case 10: + if(myGrade != data->nGRD) + return QUEST_SUCCESS; + break; + default: + return QUEST_FAILURE; + break; + } +} + +//Clan Points +QUESTCOND(027) +{ + //word nPOINT pos 0x00 + //byte btOP pos 0x02 + GETCONDDATA(027); + if( client->questdebug ) + server->SendPM(client, "QUEST - Clan Points"); + switch(data->btOP) + { + case 0x01: + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x5C ); //action to update clan points (charserver) + ADDWORD ( pak, client->Clan->clanid); + ADDWORD ( pak, client->clientid ); + ADDDWORD ( pak, client->Clan->CP ); + ADDWORD ( pak, 0x00); + GServer->SendISCPacket( &pak ); + break; + //default: + //break; + } + return QUEST_SUCCESS; +} + +//Clan Funds +QUESTCOND(028) +{ + //word iMONEY; pos 0x00 + //byte btOP; pos 0x02 + GETCONDDATA(027); + return QUEST_SUCCESS; +} + +//Clan Members +QUESTCOND(029) +{ + //word nMemberCNT; pos 0x00 + //byte btOP; pos 0x02 + return QUEST_SUCCESS; +} + +//Clan Skills +QUESTCOND(030) +{ + //word nSkill1; pos 0x00 + //word nSkill2; pos 0x02 + //byte btOP; pos 0x05 + GETCONDDATA(030); + //not sure what to do here. Looks like we compare something with two skill ids + if( client->questdebug )server->SendPM(client, "Clan skills. Skill1: %i Skill2 %i btOP: %i",data->nSkill1,data->nSkill2,data->btOP); + return QUEST_SUCCESS; +} diff --git a/Src/World Server/quest/QuestConditions.cpp.wrong b/Src/World Server/quest/QuestConditions.cpp.wrong new file mode 100644 index 0000000..5a06665 --- /dev/null +++ b/Src/World Server/quest/QuestConditions.cpp.wrong @@ -0,0 +1,763 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#include "../WorldServer.h" + +//Check Quest +QUESTCOND(000) +{ + GETCONDDATA(000); + if( client->questdebug ) + server->SendPM(client, "Check quest: %i", data->iQuestSN); + for(dword i = 0; i < 10; i++) + { + if(client->quest.quests[i].QuestID == data->iQuestSN) + { + client->CheckQuest = i; + client->ActiveQuest = data->iQuestSN; + if( client->questdebug ) + server->SendPM(client, "Check Quest # %i Found!", data->iQuestSN); + return QUEST_SUCCESS; + } + } + if( client->questdebug ) + server->SendPM(client, "Check Quest - Not Found!"); + return QUEST_FAILURE; +} + +//Check Quest Variable +QUESTCOND(001) +{ + // int iDataCnt pos 0x00 + // dword iType pos 0x02 + // short m_wVarNO pos 0x02 + // word m_wVarTYPE pos 0x04 + // word nValue pos 0x06 + // byte btOp pos 0x08 + GETCONDDATA(001); + int retVal = 1; + if( client->questdebug )server->SendPM(client, "Data Count for this quest %i",data->iDataCnt); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * sizeof(STR_QUEST_DATA); + address += (dword)data; + address += 4; + STR_QUEST_DATA* curQst = (STR_QUEST_DATA*)address; + word nValue = curQst->nValue; + word tempValue = client->GetQuestVar(curQst->m_wVarTYPE, curQst->m_wVarNO); + //word tempValue = client->quest.quests[curQst->m_wVarTYPE].Variables[curQst->m_wVarNO]; + //retVal = OperateValues(curQst->btOp, &tempValue, curQst->nValue); + switch(curQst->btOp) + { + case 0: + retVal = (tempValue == curQst->nValue); + break; + case 1: + retVal = (tempValue > curQst->nValue); + break; + case 2: + retVal = (tempValue >= curQst->nValue); + break; + case 3: + retVal = (tempValue < curQst->nValue); + break; + case 4: + retVal = (tempValue <= curQst->nValue); + break; + case 10: + retVal = (tempValue != curQst->nValue); + break; + default: + retVal = 0; + Log( MSG_DEBUG, "QuestCond 001 bad value for btOp %i",curQst->btOp ); + break; + } + if( client->questdebug ) + { + server->SendPM(client, "is %i < %i ? retval %i",tempValue, curQst->nValue, retVal); + server->SendPM(client, "Check Quest Var[%04x][%i] - %i (Op: %i)", curQst->m_wVarTYPE, curQst->m_wVarNO, curQst->nValue, curQst->btOp); + } + if(retVal == 0) return QUEST_FAILURE; + } + return (retVal == 0)?QUEST_FAILURE:QUEST_SUCCESS; +} + +//Check Quest Variable +QUESTCOND(002) +{ + return QUEST_COND_001(server, client, raw); +} + +//Check Stats +QUESTCOND(003) +{ + //dword iDataCnt; pos 0x00 + //dword iType; pos 0x04 + //dword iValue; pos 0x08 + //byte btOp; pos 0x0c + GETCONDDATA(003); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * 0x0C; + address += (dword)data; + address += 4; + STR_ABIL_DATA* curAbil = (STR_ABIL_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Check Stat[%i] %i (Op: %i)", curAbil->iType, curAbil->iValue, curAbil->btOp); + int tempValue; + switch( curAbil->iType ) + { + case sGender: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->Sex, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sJob: + tempValue = client->CharInfo->Job / 100; + if( client->questdebug ) + server->SendPM(client, "Job Tempvalue = %i", tempValue); + //if(!OperateValues(curAbil->btOp, (int*)&tempValue, curAbil->iValue)) + if(!OperateValues(curAbil->btOp, &tempValue, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sUnion: // Unions aren't implemented? - Drakia + if(!OperateValues(curAbil->btOp, (int*)&client->Union_s->unionvar[0], curAbil->iValue)) + return QUEST_FAILURE; + break; + case sStrength: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Str, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sDexterity: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Dex, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sIntelligence: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Int, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sConcentration: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Con, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sCharm: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Cha, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sSensibility: + if(!OperateValues(curAbil->btOp, (int*)&client->Attr->Sen, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sLevel: + if(!OperateValues(curAbil->btOp, (int*)&client->Stats->Level, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sStatPoints: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->StatPoints, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sMoney: + if(!OperateValues(curAbil->btOp, (int*)&client->CharInfo->Zulies, curAbil->iValue)) + return QUEST_FAILURE; + break; + case sEXPRate: + if(!OperateValues(curAbil->btOp, (int*)&client->Stats->xprate, curAbil->iValue)) + return QUEST_FAILURE; + break; + default: + Log(MSG_WARNING, "QuestCond 003: Type Unknown: '%i'", curAbil->iType); + break; + } + } + return QUEST_SUCCESS; +} + +//Check Items +QUESTCOND(004) +{ + GETCONDDATA(004); + for(int i = 0; i < data->iDataCnt; i++) + { + dword address = i * 0x10; + address += (dword)data; + address += 4; + STR_ITEM_DATA* curItem = (STR_ITEM_DATA*)address; + if( client->questdebug ) + server->SendPM(client, "Check Item - [Slot:%i][Type:%i]", curItem->iWhere, curItem->uiItemSN); + // Check quest items for a specific amount + if (curItem->iWhere == 13) // it's a quest item + { + // Ok, check the current Check'd quest. + if (client->CheckQuest < 0) return QUEST_SUCCESS; + unsigned int j = client->CheckQuest; + bool Hasitem = false; + bool FoundItem = false; + for (unsigned int k = 0; k < 5; k++) //5 quest item slots + { + if (client->quest.quests[j].Items[k].itemnum == (curItem->uiItemSN % 1000)) + { + FoundItem = true; + if( client->questdebug ) + server->SendPM(client, "Operate - btOp: %i Val1: %i Val2: %i", curItem->btOp, client->quest.quests[j].Items[k].count, curItem->iRequestCnt); + if(OperateValues(curItem->btOp, (int*)(&client->quest.quests[j].Items[k].count), curItem->iRequestCnt)) + Hasitem = true; + } + } + if(FoundItem == false) // didn't find any of the item in any of the quest slots + { + for (unsigned int k = 0; k < 5; k++) //Find the first empty slot + { + if (client->quest.quests[j].Items[k].count == 0 && Hasitem == false) + { + // Since the item doesn't exist, treat it as if there's 0 of them. + int tempInt = 0; + if(OperateValues(curItem->btOp, &tempInt, curItem->iRequestCnt)) + Hasitem = true; + if( client->questdebug ) + server->SendPM(client, "Requested item is not found. Comparing first empty slot instead"); + } + } + } + if(Hasitem == false) + { + if( client->questdebug ) + server->SendPM(client, "Requested item is not present. Returning quest failure"); + return QUEST_FAILURE; + } + } + else //this needs to be recoded. It doesn't do a damn thing right now WTF? + { + //item slot is not 13 + // Check equipped items for a specific item? - Drakia + if(curItem->uiItemSN == 0 && curItem->iWhere != 0) + { + int itemHead = client->items[curItem->iWhere].itemtype; + if(!OperateValues(curItem->btOp, &itemHead, curItem->uiItemSN)) + return QUEST_FAILURE; + } + } + } + if( client->questdebug ) + server->SendPM(client, "QuestCond 004 Returning Quest Success"); + return QUEST_SUCCESS; +} + +//Check Party +QUESTCOND(005) +{ + //byte btIsLeader pos 0x00 + //int iLevel pos 0x04 + //byte btReversed pos 0x08 + GETCONDDATA(005); + return QUEST_SUCCESS; +} + +//Near Point +QUESTCOND(006) +{ + //int iZoneSN; pos 0x00 + //int iX; pos 0x04 + //int iY; pos 0x08 + //int iZ; pos 0x0c + //int iRadius; pos 0x10 + GETCONDDATA(006); + client->UpdatePosition(); + if(data->iZoneSN != 0) + { + if(client->Position->Map != data->iZoneSN) + return QUEST_FAILURE; + } + float dx = client->Position->current.x - (float)(data->iX / 100); + float dy = client->Position->current.y - (float)(data->iY / 100); + float distance = sqrt((dx*dx) + (dy*dy)); + return (distance < data->iRadius)?QUEST_SUCCESS:QUEST_FAILURE; +} + +//World Time +QUESTCOND(007) +{ + //dword ulTime pos 0x00 + //dword ulEndTime pos 0x04 + return QUEST_SUCCESS; +} + +//Quest Time +QUESTCOND(008) +{ + //dword ulTime pos 0x00 + //byte btOp pos 0x04 + GETCONDDATA(008); + if( client->questdebug ) + server->SendPM(client, "QuestTime - ulTime: %i btOp: %i", data->ulTime, data->btOp); + if (client->CheckQuest < 0) return QUEST_FAILURE; // Not checking a quest, don't return success + long int Time = 0; + if (server->STB_QUEST.rows[client->quest.quests[client->CheckQuest].QuestID][1] > 0) + { + Time += client->quest.quests[client->CheckQuest].StartTime; // Start time + Time += server->STB_QUEST.rows[client->quest.quests[client->CheckQuest].QuestID][1] * 10; // Time to finish + Time -= time(NULL); // Current time + if (Time < 0) Time = 0; // Time is up + Time /= 10; // Divide to get 10's of seconds + } + if (!OperateValues(data->btOp, (dword*)&Time, data->ulTime)) + return QUEST_FAILURE; + else + return QUEST_SUCCESS; +} + +//Check Skill +QUESTCOND(009) +{ + //int iSkillSN1 pos 0x00 + //int iSkillSN2 pos 0x02 + //byte btOp pos 0x04 + GETCONDDATA(009); + // Line not needed, never used - Drakia + //int checkVal = (data->btOp)?QUEST_SUCCESS:QUEST_FAILURE; + if( client->questdebug ) + server->SendPM(client, "Check Skill: %i", data->iSkillSN1); + bool s1 = false; + bool s2 = false; + for(dword i = 0; i < 30; i++) + { + if(client->bskills[i] == data->iSkillSN1) + s1 = true; + if(client->bskills[i] == data->iSkillSN2) + s2 = true; + if(client->askill[i] == data->iSkillSN1) + s1 = true; + if(client->askill[i] == data->iSkillSN2) + s2 = true; + if(client->pskill[i] == data->iSkillSN1) + s1 = true; + if(client->pskill[i] == data->iSkillSN2) + s2 = true; + } + if(data->btOp == 0) + { + if(s1 == false && s2 == false) + return QUEST_SUCCESS; //true if player does not have either skill + else + return QUEST_FAILURE; + } + else + { + if(s1 == true || s2 == true) + return QUEST_SUCCESS; //true if player has either skill + else + return QUEST_FAILURE; + } + //Log(MSG_DEBUG,"Questcond 009 btOp is neither 0 nor 1."); + //return QUEST_FAILURE; +} + +//random number in range +QUESTCOND(010) +{ + GETCONDDATA(010); + //byte btLowPcnt; pos 0x00 + //byte btHighPcnt; pos 0x01 + int randv = GServer->RandNumber(0,100); + if(randv >= data->btLowPcnt && randv <= data->btHighPcnt)return QUEST_SUCCESS; + return QUEST_FAILURE; +} + +//Object Variable +QUESTCOND(011) +{ + + //byte btWho pos 0x00 + //word nVarNo pos 0x02 + //int iValue pos 0x04 + //byte btOp pos 0x09 + GETCONDDATA(011); + /* + if(data->btWho == 0) + { + CNpc* thisNpc = reinterpret_cast(entity); + thisNpc = thisNpc->SelectedNpc; + if(thisNpc == NULL) return QUEST_FAILURE; + + short VarValue = thisNpc->ObjVar.GetVar(data->nVarNo); + if(OperateValues(data->btOp, &VarValue, (short)data->iValue)) return QUEST_SUCCESS; + }*/ + if(data->btWho == 0) + { + + if(data->nVarNo > 19) return AI_FAILURE; + int refVal = GServer->ObjVar[client->NPCvar][data->nVarNo]; + int tempval = 0; + switch( data->btOp ) + { + case 0: + if(refVal == data->iValue)return QUEST_SUCCESS; + break; + case 1: + if(refVal > data->iValue)return QUEST_SUCCESS; + break; + case 2: + if(refVal >= data->iValue)return QUEST_SUCCESS; + break; + case 3: + if(refVal < data->iValue)return QUEST_SUCCESS; + break; + case 4: + if(refVal <= data->iValue)return QUEST_SUCCESS; + break; + case 10: + if(refVal != data->iValue)return QUEST_SUCCESS; + break; + default: + Log(MSG_WARNING,"Unknown btOp in QuestCond 011"); + break; + } + } + return QUEST_FAILURE; +} + +//select event object +QUESTCOND(012) +{ + GETCONDDATA(012); + //word iZone; pos 0x00 + //dword ulZone; pos 0x00 + //int iX; pos 0x04 + //int iY; pos 0x06 + //int iEventID; pos 0x08 + //dword m_HashEventOBJ; pos 0x08 + //this should be easy enough - PY + + return QUEST_SUCCESS; +} + +//Select NPC +QUESTCOND(013) +{ + //int iNpcNo pos 0x00 + GETCONDDATA(013); + // dammit!!!! This darn function is set up to use the CCharacter class so I can't use entity (CCharacter) + // This is going to be a serious pain in the ass + // OK so we now have a temporary player structure so we shuold be able to do this. + + //Log(MSG_DEBUG,"Setting NPC ref number AICOND(017). NPC Ref = %i",data->iNpcNo); + //CMonster* monster = reinterpret_cast(entity); + //if(monster == NULL) return AI_FAILURE; + //monster->thisnpc->refNPC = data->iNpcNo; // sets the reference variable for the correct ObjVar + //Log(MSG_DEBUG,"Set NPC ref number %i successfully", monster->thisnpc->refNPC); + //not sure this is going to work properly for an NPC. + //NPC only exists as a temporary entity so refNPC may be lost after it returns from here. + + + /*if(entity->_EntityType != ENTITY_NPC) return QUEST_FAILURE; + + CNpc* thisNpc = reinterpret_cast(entity); + std::map::iterator triggerITR = server->NpcList.find(data->iNpcNo); + if(triggerITR == server->NpcList.end()){ + thisNpc->SelectedNpc = NULL; + return QUEST_FAILURE; + } + thisNpc->SelectedNpc = triggerITR->second; + */ + client->NPCvar = data->iNpcNo; + //Actually that was kind of easy.. lol - PY + + //return QUEST_SUCCESS; + // More NPC specific stuff + return QUEST_SUCCESS; +} + +//Check Quest Flag +QUESTCOND(014) +{ + GETCONDDATA(014); + //word nSN; pos 0x00 + //byte btOp; pos 0x02 + bool dataOp = (data->btOp)?true:false; + if( client->questdebug ) + server->SendPM(client, "Check QFlag[%i] == %i", data->nSN, data->btOp); + if(client->quest.GetFlag(data->nSN) == dataOp) + { + if( client->questdebug ) + server->SendPM(client, "Quest cond 014 (check quest flag) returned Success"); + return QUEST_SUCCESS; + } + else + { + if( client->questdebug ) + server->SendPM(client, "Quest cond 014 (check quest flag) returned Failure"); + return QUEST_FAILURE; + } +} + +//Party member count between X and Y - PY +QUESTCOND(015) +{ + + //word nNumber1 pos 0x00 + //word nNumber2 pos 0x02 + GETCONDDATA(015); + + + return QUEST_SUCCESS; +} + +//Zone Time +QUESTCOND(016) +{ + + //byte btWho pos 0x00 + //dword ulWho pos 0x00 + //dword ulTime pos 0x04 + //dword ulEndTime pos 0x08 + GETCONDDATA(016); + + return QUEST_SUCCESS; +} + +//NPC & Obj Variables? +QUESTCOND(017) +{ + //dword iNpcNo pos 0x00 + //word nVarNo pos 0x04 + //word unused pos 0x06 + //dword iNpcNo pos 0x08 + //word nVarNo pos 0x0c + //word unused pos 0x0e + //byte btOp pos 0x0f + return QUEST_SUCCESS; +} + +//Time on Date +QUESTCOND(018) +{ + //Returns SUCCESS if the system time of the server is between the defined start and end times. Else returns FAILURE. + //Allows for time dependent quests + //byte btDate; pos 0x00 + //byte btHour1; pos 0x01 + //byte btMin1; pos 0x02 + //byte btHour2; pos 0x03 + //byte btMin2; pos 0x04 + GETCONDDATA(018); + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + + if(data->btDate != 0) + { + if(sTIME.wDay != data->btDate) + return QUEST_FAILURE; + } + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin >= wFrom && wMin <= wTo) + return QUEST_SUCCESS; + return QUEST_FAILURE; +} + +//Time on Day +QUESTCOND(019) +{ + //byte btWeekDay; pos 0x00 + //byte btHour1; pos 0x01 + //byte btMin1; pos 0x02 + //byte btHour2; pos 0x03 + //byte btMin2; pos 0x04 + GETCONDDATA(019); + SYSTEMTIME sTIME; + GetLocalTime(&sTIME); + if(data->btWeekDay != 0) + { + if(sTIME.wDayOfWeek != data->btWeekDay) + return QUEST_FAILURE; + } + word wMin = ((sTIME.wHour * 60) + sTIME.wMinute); + word wFrom = (data->btHour1 * 60) + data->btMin1; + word wTo = (data->btHour2 * 60) + data->btMin2; + if(wMin >= wFrom && wMin <= wTo) + return QUEST_SUCCESS; + return QUEST_FAILURE; +} + +//Team number between iNo1 and iNo2 +QUESTCOND(020) +{ + //int iNo1 pos 0x00 + //int iNo2 pos 0x02 + GETCONDDATA(020); + return QUEST_SUCCESS; +} + +//Distance check +QUESTCOND(021) +{ + // byte btSelObjType pos 0x00 + // byte iRadius pos 0x04 + return QUEST_SUCCESS; +} + +//Check Server/Channel +QUESTCOND(022) +{ + //word nX pos 0x00 + //word nY pos 0x02 + return QUEST_SUCCESS; +} + +//In Clan? +QUESTCOND(023) +{ + //byte btReg pos 0x00 + GETCONDDATA(023); + int Clanid = client->Clan->clanid; + if(data->btReg == 0) + { + if(Clanid == 0)return QUEST_SUCCESS; + else return QUEST_FAILURE; + } + else + { + if(Clanid != 0)return QUEST_SUCCESS; + else return QUEST_FAILURE; + } + //btReg should only ever be 1 or 0 but just in case...... + //return QUEST_FAILURE; +} + +//Clan Position +QUESTCOND(024) +{ + //check my current rank in the clan against data->nPOS using btOP + GETCONDDATA(024); + //word nPOS; pos 0x00 + //byte btOP; pos 0x02 + int myRank = client->Clan->clanrank; + switch(data->btOP) + { + case 0: + if(myRank == data->nPOS) + return QUEST_SUCCESS; + break; + case 1: + if(myRank > data->nPOS) + return QUEST_SUCCESS; + break; + case 2: + if(myRank >= data->nPOS) + return QUEST_SUCCESS; + break; + case 3: + if(myRank < data->nPOS) + return QUEST_SUCCESS; + break; + case 4: + if(myRank <= data->nPOS) + return QUEST_SUCCESS; + break; + case 10: + if(myRank != data->nPOS) + return QUEST_SUCCESS; + break; + default: + return QUEST_FAILURE; + break; + } +} + +//Clan Contribution +QUESTCOND(025) +{ + //word nCONT pos 0x00 + //byte btOP pos 0x02 + return QUEST_SUCCESS; +} + +//Clan Grade +QUESTCOND(026) +{ + //possibly compare client's current clan grade with data->nGRD using data->btOp? + GETCONDDATA(026); + //word nGRD; + //byte btOP; + int myGrade = client->Clan->grade; + switch(data->btOP) + { + case 0: + if(myGrade == data->nGRD) + return QUEST_SUCCESS; + break; + case 1: + if(myGrade > data->nGRD) + return QUEST_SUCCESS; + break; + case 2: + if(myGrade >= data->nGRD) + return QUEST_SUCCESS; + break; + case 3: + if(myGrade < data->nGRD) + return QUEST_SUCCESS; + break; + case 4: + if(myGrade <= data->nGRD) + return QUEST_SUCCESS; + break; + case 10: + if(myGrade != data->nGRD) + return QUEST_SUCCESS; + break; + default: + return QUEST_FAILURE; + break; + } +} + +//Clan Points +QUESTCOND(027) +{ + //word nPOINT pos 0x00 + //byte btOP pos 0x02 + GETCONDDATA(027); + if( client->questdebug ) + server->SendPM(client, "QUEST - Clan Points"); + switch(data->btOP) + { + case 0x01: + BEGINPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x5C ); //action to update clan points (charserver) + ADDWORD ( pak, client->Clan->clanid); + ADDWORD ( pak, client->clientid ); + ADDDWORD ( pak, client->Clan->CP ); + ADDWORD ( pak, 0x00); + GServer->SendISCPacket( &pak ); + break; + //default: + //break; + } + return QUEST_SUCCESS; +} + +//Clan Funds +QUESTCOND(028) +{ + //word iMONEY; pos 0x00 + //byte btOP; pos 0x02 + GETCONDDATA(027); + return QUEST_SUCCESS; +} + +//Clan Members +QUESTCOND(029) +{ + //word nMemberCNT; pos 0x00 + //byte btOP; pos 0x02 + return QUEST_SUCCESS; +} + +//Clan Skills +QUESTCOND(030) +{ + //word nSkill1; pos 0x00 + //word nSkill2; pos 0x02 + //byte btOP; pos 0x05 + GETCONDDATA(030); + //not sure what to do here. Looks like we compare something with two skill ids + if( client->questdebug )server->SendPM(client, "Clan skills. Skill1: %i Skill2 %i btOP: %i",data->nSkill1,data->nSkill2,data->btOP); + return QUEST_SUCCESS; +} diff --git a/Src/World Server/quest/QuestConditions.h b/Src/World Server/quest/QuestConditions.h new file mode 100644 index 0000000..bba0760 --- /dev/null +++ b/Src/World Server/quest/QuestConditions.h @@ -0,0 +1,239 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +#define QUESTCOND(cond) int QUEST_COND_ ## cond (class CWorldServer* server, class CPlayer* client, byte* raw) +typedef int (*fpQuestCond)(class CWorldServer*, class CPlayer*, byte*); +#define GETCONDDATA(cond) STR_COND_ ## cond * data = (STR_COND_ ## cond *)raw + +QUESTCOND(000);QUESTCOND(001);QUESTCOND(002); +QUESTCOND(003);QUESTCOND(004);QUESTCOND(005); +QUESTCOND(006);QUESTCOND(007);QUESTCOND(008); +QUESTCOND(009);QUESTCOND(010);QUESTCOND(011); +QUESTCOND(012);QUESTCOND(013);QUESTCOND(014); +QUESTCOND(015);QUESTCOND(016);QUESTCOND(017); +QUESTCOND(018);QUESTCOND(019);QUESTCOND(020); +QUESTCOND(021);QUESTCOND(022);QUESTCOND(023); +QUESTCOND(024);QUESTCOND(025);QUESTCOND(026); +QUESTCOND(027);QUESTCOND(028);QUESTCOND(029); +QUESTCOND(030); + +struct STR_COND_000 +{ + int iQuestSN; +}; + +struct STR_COND_001 +{ + dword iDataCnt; + //int iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_COND_002 +{ + dword iDataCnt; + //int iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_COND_003 +{ + dword iDataCnt; //pos 0x00 + STR_ABIL_DATA* CheckData; // pos = variable. it repeats iDataCnt times +}; + +struct STR_COND_004 +{ + dword iDataCnt; //pos 0x00 + STR_ITEM_DATA* CheckData; // pos = variable. it repeats iDataCnt times +}; + +struct STR_COND_005 +{ + byte btIsLeader; + int iLevel; + byte btReversed; +}; + +struct STR_COND_006 +{ + int iZoneSN; //pos 0x00 + int iX; //pos 0x04 + int iY; //pos 0x08 + int iZ; //pos 0x0c + int iRadius; //pos 0x10 +}; + +struct STR_COND_007 +{ + dword ulTime; + dword ulEndTime; +}; + +struct STR_COND_008 +{ + dword ulTime; + byte btOp; +}; + +struct STR_COND_009 +{ + int iSkillSN1; + int iSkillSN2; + byte btOp; +}; + +struct STR_COND_010 +{ + byte btLowPcnt; + byte btHighPcnt; +}; + +struct STR_COND_011 +{ + union + { + byte btWho; //pos 0x00 + word usWho; + }; + word nVarNo; //pos 0x02 + int iValue; //pos 0x04 + byte btOp; //pos 0x09 +}; + +struct STR_COND_012 +{ + union + { + word iZone; + dword ulZone; + }; + int iX; + int iY; + union + { + int iEventID; + dword m_HashEventOBJ; + }; +}; + +struct STR_COND_013 +{ + int iNpcNo; +}; + +struct STR_COND_014 +{ + word nSN; + byte btOp; +}; + +struct STR_COND_015 +{ + word nNumber1; + word nNumber2; +}; + +struct STR_COND_016 +{ + union + { + byte btWho; + dword ulWho; + }; + dword ulTime; + dword ulEndTime; +}; + +struct STR_COND_017 +{ + STR_NPCVAR NpcVar1; + STR_NPCVAR NpcVar2; + byte btOp; +}; + +struct STR_COND_018 +{ + byte btDate; + byte btHour1; + byte btMin1; + byte btHour2; + byte btMin2; +}; + +struct STR_COND_019 +{ + byte btWeekDay; + byte btHour1; + byte btMin1; + byte btHour2; + byte btMin2; +}; + +struct STR_COND_020 +{ + int iNo1; + int iNo2; +}; + +struct STR_COND_021 +{ + union + { + byte btSelObjType; + dword ulSelObjType; + }; + int iRadius; +}; + +struct STR_COND_022 +{ + word nX; + word nY; +}; + +struct STR_COND_023 +{ + byte btReg; +}; + +struct STR_COND_024 +{ + word nPOS; + byte btOP; +}; + +struct STR_COND_025 +{ + word nCONT; + byte btOP; +}; + +struct STR_COND_026 +{ + word nGRD; + byte btOP; +}; + +struct STR_COND_027 +{ + word nPOINT; + byte btOP; +}; + +struct STR_COND_028 +{ + word iMONEY; + byte btOP; +}; + +struct STR_COND_029 +{ + word nMemberCNT; + byte btOP; +}; + +struct STR_COND_030 +{ + word nSkill1; + word nSkill2; + byte btOP; +}; diff --git a/Src/World Server/quest/QuestTrigger.h b/Src/World Server/quest/QuestTrigger.h new file mode 100644 index 0000000..0b72461 --- /dev/null +++ b/Src/World Server/quest/QuestTrigger.h @@ -0,0 +1,98 @@ +// Props to ExJam for this code :D Ported to OSpRose by Drakia +class CQuestTrigger { +public: + struct SQuestDatum { + int size; + int opcode; + byte* data; + }; + + strings TriggerName; + dword TriggerHash; + + byte CheckNext; + dword id; + + dword ConditionCount; + SQuestDatum** Conditions; + + dword ActionCount; + SQuestDatum** Actions; +private: +}; + +struct STR_NPCVAR { + dword iNpcNo; + word nVarNo; + word unused; +}; + +struct STR_ITEM_DATA { + dword uiItemSN; + dword iWhere; + dword iRequestCnt; + byte btOp; +}; + +struct STR_QUEST_DATA +{ + union + { + dword iType; + struct + { + short m_wVarNO; + word m_wVarTYPE; + }; + }; + word nValue; + byte btOp; +}; + +struct STR_ABIL_DATA +{ + dword iType; + int iValue; + byte btOp; +}; + +#define QUEST_FAILURE 6 +#define QUEST_SUCCESS 5 +template int OperateValues(byte btOp, T* value1PTR, T value2) +{ + T value1 = *value1PTR; + switch(btOp) + { + case 0: + return (value1 == value2); + case 1: + return (value1 > value2); + case 2: + return (value1 >= value2); + case 3: + return (value1 < value2); + case 4: + return (value1 <= value2); + case 5: + (*value1PTR) = value2; + return 1; + case 6: + (*value1PTR) = value1 + value2; + return 1; + case 7: + (*value1PTR) = value1 - value2; + return 1; + case 9: + (*value1PTR) += 1; + return 1; + case 10: + return (value1 != value2); + default: + Log(MSG_WARNING, "OperateValues unknown btOp='%u'", btOp); + return 0; + } +} +dword GetRewardValue(dword function, dword amount, CPlayer* client, word nDupCNT); + +#include "QuestConditions.h" +#include "QuestActions.h" diff --git a/Src/World Server/quest/Quests.cpp b/Src/World Server/quest/Quests.cpp new file mode 100644 index 0000000..aa7d74f --- /dev/null +++ b/Src/World Server/quest/Quests.cpp @@ -0,0 +1,157 @@ +// Props to ExJam for this code :D +#include "../WorldServer.h" +#include "QuestActions.h" + +void CWorldServer::ReadQSD(strings path, dword index){ + CRoseFile* fh = new CRoseFile(path, FM_READ | FM_BINARY); + + if(fh->IsOpen()) + { // goto done; + + //Log(MSG_LOAD, "Loading %s ", path); + + fh->Seek(4, SEEK_CUR); + dword BlockCount = fh->Get(); + fh->Seek(fh->Get(), SEEK_CUR); + for(dword i = 0; i < BlockCount; i++) + { + dword RecordCount = fh->Get(); + fh->Seek(fh->Get(), SEEK_CUR); + for(dword j = 0; j < RecordCount; j++) + { + CQuestTrigger* trigger = new CQuestTrigger(); + trigger->id=((index*0x10000)+(i*0x100)+j); + trigger->CheckNext = fh->Get(); + trigger->ConditionCount = fh->Get(); + trigger->ActionCount = fh->Get(); + dword len = fh->Get(); + trigger->TriggerName = new char[len+1]; + fh->Read(trigger->TriggerName, len, 1); + trigger->TriggerName[len] = 0; + + if(trigger->ConditionCount > 0) + { + trigger->Conditions = new CQuestTrigger::SQuestDatum*[trigger->ConditionCount]; + for(dword k = 0; k < trigger->ConditionCount; k++) + { + CQuestTrigger::SQuestDatum* data = new CQuestTrigger::SQuestDatum(); + data->size = fh->Get(); + data->opcode = fh->Get(); + data->data = new byte[data->size - 8]; + fh->Read(data->data, data->size - 8, 1); + trigger->Conditions[k] = data; + } + } + else + { + trigger->Conditions = NULL; + } + + if(trigger->ActionCount > 0) + { + trigger->Actions = new CQuestTrigger::SQuestDatum*[trigger->ActionCount]; + for(dword k = 0; k < trigger->ActionCount; k++) + { + CQuestTrigger::SQuestDatum* data = new CQuestTrigger::SQuestDatum(); + data->size = fh->Get(); + data->opcode = fh->Get() - 0x01000000; + data->data = new byte[data->size - 8]; + fh->Read(data->data, data->size - 8, 1); + trigger->Actions[k] = data; + } + } + else + { + trigger->Actions = NULL; + } + + trigger->TriggerHash = MakeStrHash(trigger->TriggerName); + TriggerList.push_back( trigger ); + } + } + } + else + Log( MSG_ERROR, "QSD File: '%s' did not load properly", path ); + + fh->Close(); + delete fh; +} + +void CWorldServer::LoadQuestData(){ + CStrStb* stbQuest = new CStrStb("3DDATA\\STB\\LIST_QUESTDATA.STB"); + Log(MSG_INFO, "Loading QSD files"); + for(dword i = 1; i < stbQuest->Rows(); i++){ + if(stbQuest->Data(i, 0)){ + GServer->ReadQSD(stbQuest->Data(i, 0),i); + } + } + + Log(MSG_INFO, "Finished loading quest data "); + + qstCondFunc[0] = &QUEST_COND_000; + qstCondFunc[1] = &QUEST_COND_001; + qstCondFunc[2] = &QUEST_COND_002; + qstCondFunc[3] = &QUEST_COND_003; + qstCondFunc[4] = &QUEST_COND_004; + qstCondFunc[5] = &QUEST_COND_005; + qstCondFunc[6] = &QUEST_COND_006; + qstCondFunc[7] = &QUEST_COND_007; + qstCondFunc[8] = &QUEST_COND_008; + qstCondFunc[9] = &QUEST_COND_009; + qstCondFunc[10] = &QUEST_COND_010; + qstCondFunc[11] = &QUEST_COND_011; + qstCondFunc[12] = &QUEST_COND_012; + qstCondFunc[13] = &QUEST_COND_013; + qstCondFunc[14] = &QUEST_COND_014; + qstCondFunc[15] = &QUEST_COND_015; + qstCondFunc[16] = &QUEST_COND_016; + qstCondFunc[17] = &QUEST_COND_017; + qstCondFunc[18] = &QUEST_COND_018; + qstCondFunc[19] = &QUEST_COND_019; + qstCondFunc[20] = &QUEST_COND_020; + qstCondFunc[21] = &QUEST_COND_021; + qstCondFunc[22] = &QUEST_COND_022; + qstCondFunc[23] = &QUEST_COND_023; + qstCondFunc[24] = &QUEST_COND_024; + qstCondFunc[25] = &QUEST_COND_025; + qstCondFunc[26] = &QUEST_COND_026; + qstCondFunc[27] = &QUEST_COND_027; + qstCondFunc[28] = &QUEST_COND_028; + qstCondFunc[29] = &QUEST_COND_029; + qstCondFunc[30] = &QUEST_COND_030; + + qstRewdFunc[0] = &QUEST_REWD_000; + qstRewdFunc[1] = &QUEST_REWD_001; + qstRewdFunc[2] = &QUEST_REWD_002; + qstRewdFunc[3] = &QUEST_REWD_003; + qstRewdFunc[4] = &QUEST_REWD_004; + qstRewdFunc[5] = &QUEST_REWD_005; + qstRewdFunc[6] = &QUEST_REWD_006; + qstRewdFunc[7] = &QUEST_REWD_007; + qstRewdFunc[8] = &QUEST_REWD_008; + qstRewdFunc[9] = &QUEST_REWD_009; + qstRewdFunc[10] = &QUEST_REWD_010; + qstRewdFunc[11] = &QUEST_REWD_011; + qstRewdFunc[12] = &QUEST_REWD_012; + qstRewdFunc[13] = &QUEST_REWD_013; + qstRewdFunc[14] = &QUEST_REWD_014; + qstRewdFunc[15] = &QUEST_REWD_015; + qstRewdFunc[16] = &QUEST_REWD_016; + qstRewdFunc[17] = &QUEST_REWD_017; + qstRewdFunc[18] = &QUEST_REWD_018; + qstRewdFunc[19] = &QUEST_REWD_019; + qstRewdFunc[20] = &QUEST_REWD_020; + qstRewdFunc[21] = &QUEST_REWD_021; + qstRewdFunc[22] = &QUEST_REWD_022; + qstRewdFunc[23] = &QUEST_REWD_023; + qstRewdFunc[24] = &QUEST_REWD_024; + qstRewdFunc[25] = &QUEST_REWD_025; + qstRewdFunc[26] = &QUEST_REWD_026; + qstRewdFunc[27] = &QUEST_REWD_027; + qstRewdFunc[28] = &QUEST_REWD_028; + qstRewdFunc[29] = &QUEST_REWD_029; + qstRewdFunc[30] = &QUEST_REWD_030; + //qstRewdFunc[50] = &QUEST_REWD_050; + + delete stbQuest; +} diff --git a/Src/World Server/quest/strhashing.cpp b/Src/World Server/quest/strhashing.cpp new file mode 100644 index 0000000..eb8492d --- /dev/null +++ b/Src/World Server/quest/strhashing.cpp @@ -0,0 +1,17 @@ +#include "../WorldServer.h" + +unsigned long MakeStrHash(const char *str) +{ + register unsigned long result = 0xDEADC0DE; + register unsigned long tkey = 0x7FED7FED; + register char c = 0; + while (*(str)){ + c = (*str<'a'||*str>'z')?*str:*str-32; + result += tkey; + tkey *= 0x21; + result ^= keys[c]; + tkey += result + c + 3; + str++; + } + return result; +} diff --git a/Src/World Server/quest/strhashing.h b/Src/World Server/quest/strhashing.h new file mode 100644 index 0000000..c05baa0 --- /dev/null +++ b/Src/World Server/quest/strhashing.h @@ -0,0 +1,20 @@ +const unsigned long keys[256] = { + 0x697A5, 0x6045C, 0xAB4E2, 0x409E4, 0x71209, 0x32392, 0xA7292, 0xB09FC, 0x4B658, 0xAAAD5, 0x9B9CF, 0xA326A, 0x8DD12, 0x38150, 0x8E14D, 0x2EB7F, + 0xE0A56, 0x7E6FA, 0xDFC27, 0xB1301, 0x8B4F7, 0xA7F70, 0xAA713, 0x6CC0F, 0x6FEDF, 0x2EC87, 0xC0F1C, 0x45CA4, 0x30DF8, 0x60E99, 0xBC13E, 0x4E0B5, + 0x6318B, 0x82679, 0x26EF2, 0x79C95, 0x86DDC, 0x99BC0, 0xB7167, 0x72532, 0x68765, 0xC7446, 0xDA70D, 0x9D132, 0xE5038, 0x2F755, 0x9171F, 0xCB49E, + 0x6F925, 0x601D3, 0x5BD8A, 0x2A4F4, 0x9B022, 0x706C3, 0x28C10, 0x2B24B, 0x7CD55, 0xCA355, 0xD95F4, 0x727BC, 0xB1138, 0x9AD21, 0xC0ACA, 0xCD928, + 0x953E5, 0x97A20, 0x345F3, 0xBDC03, 0x7E157, 0x96C99, 0x968EF, 0x92AA9, 0xC2276, 0xA695D, 0x6743B, 0x2723B, 0x58980, 0x66E08, 0x51D1B, 0xB97D2, + 0x6CAEE, 0xCC80F, 0x3BA6C, 0xB0BF5, 0x9E27B, 0xD122C, 0x48611, 0x8C326, 0xD2AF8, 0xBB3B7, 0xDED7F, 0x4B236, 0xD298F, 0xBE912, 0xDC926, 0xC873F, + 0xD0716, 0x9E1D3, 0x48D94, 0x9BD91, 0x5825D, 0x55637, 0xB2057, 0xBCC6C, 0x460DE, 0xAE7FB, 0x81B03, 0x34D8F, 0xC0528, 0xC9B59, 0x3D260, 0x6051D, + 0x93757, 0x8027F, 0xB7C34, 0x4A14E, 0xB12B8, 0xE4945, 0x28203, 0xA1C0F, 0xAA382, 0x46ABB, 0x330B9, 0x5A114, 0xA754B, 0xC68D0, 0x9040E, 0x6C955, + 0xBB1EF, 0x51E6B, 0x9FF21, 0x51BCA, 0x4C879, 0xDFF70, 0x5B5EE, 0x29936, 0xB9247, 0x42611, 0x2E353, 0x26F3A, 0x683A3, 0xA1082, 0x67333, 0x74EB7, + 0x754BA, 0x369D5, 0x8E0BC, 0xABAFD, 0x6630B, 0xA3A7E, 0xCDBB1, 0x8C2DE, 0x92D32, 0x2F8ED, 0x7EC54, 0x572F5, 0x77461, 0xCB3F5, 0x82C64, 0x35FE0, + 0x9203B, 0xADA2D, 0xBAEBD, 0xCB6AF, 0xC8C9A, 0x5D897, 0xCB727, 0xA13B3, 0xB4D6D, 0xC4929, 0xB8732, 0xCCE5A, 0xD3E69, 0xD4B60, 0x89941, 0x79D85, + 0x39E0F, 0x6945B, 0xC37F8, 0x77733, 0x45D7D, 0x25565, 0xA3A4E, 0xB9F9E, 0x316E4, 0x36734, 0x6F5C3, 0xA8BA6, 0xC0871, 0x42D05, 0x40A74, 0x2E7ED, + 0x67C1F, 0x28BE0, 0xE162B, 0xA1C0F, 0x2F7E5, 0xD505A, 0x9FCC8, 0x78381, 0x29394, 0x53D6B, 0x7091D, 0xA2FB1, 0xBB942, 0x29906, 0xC412D, 0x3FCD5, + 0x9F2EB, 0x8F0CC, 0xE25C3, 0x7E519, 0x4E7D9, 0x5F043, 0xBBA1B, 0x6710A, 0x819FB, 0x9A223, 0x38E47, 0xE28AD, 0xB690B, 0x42328, 0x7CF7E, 0xAE108, + 0xE54BA, 0xBA5A1, 0xA09A6, 0x9CAB7, 0xDB2B3, 0xA98CC, 0x5CEBA, 0x9245D, 0x5D083, 0x8EA21, 0xAE349, 0x54940, 0x8E557, 0x83EFD, 0xDC504, 0xA6059, + 0xB85C9, 0x9D162, 0x7AEB6, 0xBED34, 0xB4963, 0xE367B, 0x4C891, 0x9E42C, 0xD4304, 0x96EAA, 0xD5D69, 0x866B8, 0x83508, 0x7BAEC, 0xD03FD, 0xDA122 +}; + +unsigned long MakeStrHash(const char *str); \ No newline at end of file diff --git a/Src/World Server/questactions.h.wrong b/Src/World Server/questactions.h.wrong new file mode 100644 index 0000000..382e9c2 --- /dev/null +++ b/Src/World Server/questactions.h.wrong @@ -0,0 +1,196 @@ +#define QUESTREWD(reward) int QUEST_REWD_ ## reward ## (class CWorldServer* server, class CPlayer* client, byte* raw) +typedef int (*fpQuestRewd)(class CWorldServer*, class CPlayer*, byte*); +#define GETREWDDATA(rewd) STR_REWD_ ## rewd ## * data = (STR_REWD_ ## rewd ## *)raw; + +QUESTREWD(000);QUESTREWD(001);QUESTREWD(002); +QUESTREWD(003);QUESTREWD(004);QUESTREWD(005); +QUESTREWD(006);QUESTREWD(007);QUESTREWD(008); +QUESTREWD(009);QUESTREWD(010);QUESTREWD(011); +QUESTREWD(012);QUESTREWD(013);QUESTREWD(014); +QUESTREWD(015);QUESTREWD(016);QUESTREWD(017); +QUESTREWD(018);QUESTREWD(019);QUESTREWD(020); +QUESTREWD(021);QUESTREWD(022);QUESTREWD(023); +QUESTREWD(024);QUESTREWD(025);QUESTREWD(026); +QUESTREWD(027);QUESTREWD(028); + +struct STR_REWD_000 { + dword iQuestSN; + byte btOp; +}; + +struct STR_REWD_001 { + dword uiItemSN; + union { + byte btOp; + word nOp; + }; + word nDupCNT; + byte btPartyOpt; +}; + +struct STR_REWD_002 { + dword iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_REWD_003 { + dword iDataCnt; + STR_ABIL_DATA* CheckData; + byte btPartyOpt; +}; + +struct STR_REWD_004 { + dword iDataCnt; + STR_QUEST_DATA* CheckData; +}; + +struct STR_REWD_005 { + byte btTarget; + union { + byte btEquation; + word nEquation; + }; + int iValue; + dword iItemSN; + union { + byte btPartyOpt; + word nPartyOpt; + }; + word nItemOpt; +}; + +struct STR_REWD_006 { + dword iPercentOfHP; + dword iPercentOfMP; + byte btPartyOpt; +}; + +struct STR_REWD_007 { + dword iZoneSN; + dword iX; + dword iY; + byte btPartyOpt; +}; + +struct STR_REWD_008 { + dword iMonsterSN; + dword iHowMany; + union { + byte btWho; + int iWho; + }; + dword iZoneSN; + dword iX; + dword iY; + dword iRange; + dword iTeamNo; +}; + +struct STR_REWD_009 { + word shNameLen; + string szNextTriggerSN; +}; + +struct STR_REWD_010 { +}; + +//UNKNOWN STRUCT FOR 11 OMG OMG OMG +struct STR_REWD_011 { +}; + +struct STR_REWD_012 { + union { + byte btMsgType; + int iMsgType; + }; + dword iStrID; +}; + +struct STR_REWD_013 { + union { + byte btWho; + int iWho; + }; + dword iSec; + word nNameLength; + string szTriggerName; + dword m_HashNextTrigger; +}; + +struct STR_REWD_014 { + union { + byte btOp; + int iOp; + }; + dword iSkillNo; +}; + +struct STR_REWD_015 { + word nSN; + byte btOp; +}; + +struct STR_REWD_016 { + word nGroupSN; +}; + +struct STR_REWD_017 { +}; + +struct STR_REWD_018 { + dword iStrID; + word nCnt; + string Data; +}; + +struct STR_REWD_019 { + word nZoneNo; + word nTeamNo; + word nTriggerLength; + string TriggerName; + dword m_HashTrigger; +}; + +struct STR_REWD_020 { + byte btNoType; +}; + +struct STR_REWD_021 { + dword iX; + dword iY; +}; + +struct STR_REWD_022 { + word nZonNo; + byte btOp; +}; + +struct STR_REWD_023 { +}; + +struct STR_REWD_024 { + int iMONEY; + byte btOP; +}; + +struct STR_REWD_025 { + short nPOINT; + byte btOP; +}; + +struct STR_REWD_026 { + word nSkillNo; + byte btOP; +}; + +struct STR_REWD_027 { + short nCONT; + byte btOP; +}; + +struct STR_REWD_028 { + dword iRange; + word nZoneNo; + dword iX; + dword iY; +}; diff --git a/Src/World Server/startup.cpp b/Src/World Server/startup.cpp new file mode 100644 index 0000000..5346e4a --- /dev/null +++ b/Src/World Server/startup.cpp @@ -0,0 +1,1297 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" +bool CWorldServer::LoadSTBData( ) +{ + Log( MSG_LOAD, "STB Data " ); + STBStoreData( "3DData\\STB\\LIST_NPC.STB", &STB_NPC ); + STBStoreData( "3DData\\STB\\LIST_SKILL.STB", &STB_SKILL ); + STBStoreData( "3DData\\STB\\LIST_STATUS.STB", &STB_STATUS ); + STBStoreData( "3DData\\STB\\LIST_QUEST.STB", &STB_QUEST ); + STBStoreData( "3DData\\STB\\LIST_FACEITEM.STB", &STB_ITEM[0] ); + STBStoreData( "3DData\\STB\\LIST_CAP.STB", &STB_ITEM[1] ); + STBStoreData( "3DData\\STB\\LIST_BODY.STB", &STB_ITEM[2] ); + STBStoreData( "3DData\\STB\\LIST_ARMS.STB", &STB_ITEM[3] ); + STBStoreData( "3DData\\STB\\LIST_FOOT.STB", &STB_ITEM[4] ); + STBStoreData( "3DData\\STB\\LIST_BACK.STB", &STB_ITEM[5] ); + STBStoreData( "3DData\\STB\\LIST_JEWEL.STB", &STB_ITEM[6] ); + STBStoreData( "3DData\\STB\\LIST_WEAPON.STB", &STB_ITEM[7] ); + STBStoreData( "3DData\\STB\\LIST_SUBWPN.STB", &STB_ITEM[8] ); + STBStoreData( "3DData\\STB\\LIST_USEITEM.STB", &STB_ITEM[9] ); + STBStoreData( "3DData\\STB\\LIST_JEMITEM.STB", &STB_ITEM[10] ); + STBStoreData( "3DData\\STB\\LIST_NATURAL.STB", &STB_ITEM[11] ); + STBStoreData( "3DData\\STB\\LIST_QUESTITEM.STB", &STB_ITEM[12] ); + STBStoreData( "3DData\\STB\\LIST_PAT.STB", &STB_ITEM[13] ); + STBStoreData( "3DData\\STB\\LIST_PRODUCT.STB", &STB_PRODUCT ); + STBStoreData( "3DData\\STB\\LIST_BREAK.STB", &STB_BREAK ); + STBStoreData( "3DData\\STB\\LIST_SELL.STB", &STB_SELL ); + STBStoreData( "3DData\\STB\\LIST_ZONE.STB", &STB_ZONE ); + //STBStoreData( "3DData\\STB\\ITEM_DROP.STB", &STB_DROP ); + STBStoreData("3DData\\STB\\LIST_GRADE.STB", &ListGrade); + STBStoreData("3DData\\STB\\LIST_UPGRADE.STB", &ListUpGrade); + return true; +} + +bool CWorldServer::LoadNPCData( ) +{ + Log( MSG_INFO, "Loading NPC Data" ); + for (unsigned int i = 0; iid = i; + newnpc->life = STB_NPC.rows[i][1]; + newnpc->stance = mRUNNING; + newnpc->wspeed = STB_NPC.rows[i][2]; + newnpc->rspeed = STB_NPC.rows[i][3]; + newnpc->dspeed = STB_NPC.rows[i][4]; + newnpc->weapon = STB_NPC.rows[i][5]; + newnpc->subweapon = STB_NPC.rows[i][6]; + newnpc->level = STB_NPC.rows[i][7]; + newnpc->hp = STB_NPC.rows[i][8]; + newnpc->atkpower = STB_NPC.rows[i][9]; + //reduce attack power by 10% to improve combat + newnpc->atkpower = (int)(newnpc->atkpower * 0.9); + newnpc->hitrate = STB_NPC.rows[i][10]; + newnpc->defense = STB_NPC.rows[i][11]; + newnpc->magicdefense = STB_NPC.rows[i][12]; + newnpc->dodge = STB_NPC.rows[i][13]; + //reduce dodge by 20% to improve combat + newnpc->dodge = (int)(newnpc->dodge * 0.8); + newnpc->atkspeed = STB_NPC.rows[i][14]; + newnpc->magicattack = STB_NPC.rows[i][15]; + newnpc->AI = STB_NPC.rows[i][16]; + newnpc->exp = STB_NPC.rows[i][17]; + newnpc->dropid = STB_NPC.rows[i][18]; + newnpc->money = STB_NPC.rows[i][19]; + newnpc->item = STB_NPC.rows[i][20]; + newnpc->tab1 = STB_NPC.rows[i][21]; + newnpc->tab2 = STB_NPC.rows[i][22]; + newnpc->tab3 = STB_NPC.rows[i][23]; + newnpc->specialtab = STB_NPC.rows[i][24]; + newnpc->atkdistance = STB_NPC.rows[i][26]/100; + newnpc->aggresive = STB_NPC.rows[i][27]; + newnpc->die_quest = STB_NPC.rows[i][41]; //this contains the hash of the quest trigger.We can use it to run quest triggers directly + newnpc->shp = STB_NPC.rows[i][42]; //PY: What the heck. This column of the STB defines the height of a flying monster such as a bee + newnpc->dialogid = 0; + //Log( MSG_DEBUG, "NPC %i lvl %i with AI %i", newnpc->id, newnpc->level, newnpc->AI); + NPCData.push_back( newnpc ); + } + return true; +} + +bool CWorldServer::LoadStatusData( ) +{ + Log( MSG_INFO, "Loading Stats Data " ); + for (unsigned int i = 0; i < STB_STATUS.rowcount; i++) + { + CStatus* newstatus = new (nothrow) CStatus; + if (newstatus == NULL) + { + Log(MSG_WARNING, "\nError allocing memory: status_data"); + return false; + } + newstatus->id = i; + newstatus->type = STB_STATUS.rows[i][1]; // Unk + newstatus->duplication = STB_STATUS.rows[i][2]; // Are results stackable? + newstatus->dir = STB_STATUS.rows[i][3]; // 0: Up 1: Down 2: ? + newstatus->repeat = STB_STATUS.rows[i][4]; // 1: Effect over time 2: Once only (eg. Increase max Stat) 3: Status Effect (taunt, fainted etc.) + newstatus->ability[0] = STB_STATUS.rows[i][5]; // Status # to call? + newstatus->amount[0] = STB_STATUS.rows[i][6]; // Amount + newstatus->ability[1] = STB_STATUS.rows[i][7]; // Second Status + newstatus->amount[1] = STB_STATUS.rows[i][8]; // Second amount + newstatus->decrease = STB_STATUS.rows[i][17]; // 0: Increase 1: Decrease + + StatusList.push_back(newstatus); + } + return true; +} + +bool CWorldServer::LoadSkillData( ) +{ + Log( MSG_INFO, "Loading Skills data " ); + for (unsigned int i = 0; iid=i; + newskill->skillid = STB_SKILL.rows[i][1]; // Skillid. Should be the same for all levels of a given skill + newskill->level = STB_SKILL.rows[i][2]; // Skills Level + newskill->sp = STB_SKILL.rows[i][3]; // Cost to get skill + newskill->type = STB_SKILL.rows[i][4]; // + newskill->skilltype = STB_SKILL.rows[i][5]; // Type of skill + newskill->range = STB_SKILL.rows[i][6]/100; // Range of skill + newskill->target = STB_SKILL.rows[i][7]; // Skill Target + newskill->aoerange = STB_SKILL.rows[i][8]/100; // AOE Range + newskill->atkpower = STB_SKILL.rows[i][9]; // Attack Power + newskill->status[0] = STB_SKILL.rows[i][11]; // status + newskill->status[1] = STB_SKILL.rows[i][12]; // status + newskill->success = STB_SKILL.rows[i][13]; // Success Rate + if(newskill->success == 0) + newskill->success = 100; //success rate is stored as NULL where it is 100%. eg dual scratch + newskill->duration = STB_SKILL.rows[i][14]; // Duration + newskill->costtype[0] = STB_SKILL.rows[i][16]; //not all costs are in MP + newskill->costamount[0] = STB_SKILL.rows[i][17]; //some are in HP or Stamina + newskill->mp = STB_SKILL.rows[i][17]; + newskill->costtype[1] = STB_SKILL.rows[i][18]; // All driveby skills have 2 costs + newskill->costamount[1] = STB_SKILL.rows[i][19]; // The second is normally cart gauge + newskill->cooldown = STB_SKILL.rows[i][20]; // The time it takes to be able to use the skill again. + newskill->buff[0] = STB_SKILL.rows[i][21]; // Stat + //if(newskill->id == 1403) + // Log(MSG_DEBUG,"Skill 1403 buff[0] value is %i",newskill->buff[0]); + newskill->value1[0] = STB_SKILL.rows[i][22]; // Int Value + newskill->value2[0] = STB_SKILL.rows[i][23]; // % Value + newskill->buff[1] = STB_SKILL.rows[i][24]; // Stat + //if(newskill->id == 1403) + // Log(MSG_DEBUG,"Skill 1403 buff[1] value is %i",newskill->buff[1]); + newskill->value1[1] = STB_SKILL.rows[i][25]; // Int Value + newskill->value2[1] = STB_SKILL.rows[i][26]; // % Value + //newskill->success=STB_SKILL.rows[i][69]; //Dunno what this field is really but it sure isn't success rate <_< + newskill->weapon[0] = STB_SKILL.rows[i][30]; // Item Type must be equipped (Only 1) + //if(newskill->weapon[0] != 0 && newskill->id < 1500) + // Log(MSG_DEBUG,"Skill %i weapon 1 id is %i",newskill->id,newskill->weapon[0]); + newskill->weapon[1] = STB_SKILL.rows[i][31]; // Item Type must be equipped + //if(newskill->weapon[1] != 0 && newskill->id < 1500) + // Log(MSG_DEBUG,"Skill %i weapon 2 id is %i",newskill->id,newskill->weapon[1]); + newskill->weapon[2] = STB_SKILL.rows[i][32]; // Item Type must be equipped + //if(newskill->weapon[2] != 0 && newskill->id < 1500) + // Log(MSG_DEBUG,"Skill %i weapon 3 id is %i",newskill->id,newskill->weapon[2]); + newskill->weapon[3] = STB_SKILL.rows[i][33]; // Item Type must be equipped + //if(newskill->weapon[3] != 0 && newskill->id < 1500) + // Log(MSG_DEBUG,"Skill %i weapon 4 id is %i",newskill->id,newskill->weapon[3]); + newskill->weapon[4] = STB_SKILL.rows[i][34]; // Item Type must be equipped + //if(newskill->weapon[4] != 0 && newskill->id < 1500) + // Log(MSG_DEBUG,"Skill %i weapon 5 id is %i",newskill->id,newskill->weapon[4]); + newskill->c_class[0] = STB_SKILL.rows[i][35]; // Required Class (Only One) + newskill->c_class[1] = STB_SKILL.rows[i][36]; // Required Class + newskill->c_class[2] = STB_SKILL.rows[i][37]; // Required Class + newskill->c_class[3] = STB_SKILL.rows[i][38]; // Required Class + newskill->c_class[4] = 0; + newskill->rskill[0] = STB_SKILL.rows[i][39]; // Required Skill + newskill->rskill[1] = STB_SKILL.rows[i][41]; // Required Skill + newskill->rskill[2] = STB_SKILL.rows[i][43]; // Required Skill + newskill->lskill[0] = STB_SKILL.rows[i][40]; // Required Skill Level + newskill->lskill[1] = STB_SKILL.rows[i][42]; // Required Skill Level + newskill->lskill[2] = STB_SKILL.rows[i][44]; // Required Skill Level + + + + //WTF are these for??? + //newskill->buff[2]=0;STB_SKILL.rows[i][11];//for damage support + //newskill->value1[2]=0;STB_SKILL.rows[i][9];//for damage support + //newskill->value2[2]=0; + + newskill->req[0] = STB_SKILL.rows[i][45]; //the requirement type (usually 31 = level) + newskill->clevel = STB_SKILL.rows[i][46]; //requirement amount + newskill->reqam[0] = STB_SKILL.rows[i][46]; //this need not always be level but is usually + newskill->req[1] = STB_SKILL.rows[i][47]; //the requirement type (in most cases blank) + newskill->reqam[1] = STB_SKILL.rows[i][48]; //requirement amount + newskill->zuly = (STB_SKILL.rows[i][85] * 100); // Required zuly + if ((STB_SKILL.rows[i][8])>0) + { + newskill->aoe = 1; + newskill->aoeradius = STB_SKILL.rows[i][8]/100; + } + else + { + newskill->aoe = 0; + newskill->aoeradius = 0; + } + if ((STB_SKILL.rows[i][28])>0) + { + newskill->script = 1; + newskill->svalue1 = STB_SKILL.rows[i][28]; + } + else + { + newskill->script=0; + newskill->svalue1=0; + } + newskill->nbuffs = 0; + if(newskill->buff[0] != 0 || newskill->status[0] != 0) + newskill->nbuffs += 1; + if(newskill->buff[1]!= 0 || newskill->status[1] != 0) + newskill->nbuffs += 2; + + + SkillList.push_back( newskill ); + } + return true; +} + +bool CWorldServer::LoadTeleGateData( ) +{ + Log( MSG_INFO, "Loading Telegates data " ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id, srcx, srcy, srcmap, destx, desty, destmap FROM list_telegates"); + if(result==NULL) return false; + while( row = mysql_fetch_row(result) ) + { + CTeleGate* thisgate = new (nothrow) CTeleGate; + if(thisgate==NULL) + { + Log(MSG_ERROR, "Error allocing memory " ); + DB->QFree( ); + return false; + } + thisgate->id = atoi(row[0]); + thisgate->src.x = (float)atof(row[1]); + thisgate->src.y = (float)atof(row[2]); + thisgate->srcMap = atoi(row[3]); + thisgate->dest.x = (float)atof(row[4]); + thisgate->dest.y = (float)atof(row[5]); + thisgate->destMap = atoi(row[6]); + TeleGateList.push_back( thisgate ); + } + DB->QFree( ); + return true; +} + +bool CWorldServer::LoadRespawnData( ) +{ + Log( MSG_INFO, "Loading RespawnZones data " ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id,x,y,map,radius,type FROM list_respawnzones"); + if(result==NULL) return false; + while( row = mysql_fetch_row(result) ) + { + CRespawnPoint* thisrespawnpoint = new (nothrow) CRespawnPoint; + if(thisrespawnpoint==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + DB->QFree( ); + return false; + } + thisrespawnpoint->id = atoi(row[0]); + thisrespawnpoint->dest.x = (float)atof(row[1]); + thisrespawnpoint->dest.y = (float)atof(row[2]); + thisrespawnpoint->destMap = atoi(row[3]); + thisrespawnpoint->radius = atoi(row[4]); + thisrespawnpoint->masterdest = (atoi(row[5]) == 1); + MapList.Index[thisrespawnpoint->destMap]->RespawnList.push_back( thisrespawnpoint ); + } + DB->QFree( ); + return true; +} + + +bool CWorldServer::LoadMobGroups() +{ + if(GServer->Config.SpawnType == 0) + { + Log(MSG_INFO, "Loading MobGroups data " ); + vector mobGroups; + MYSQL_ROW row; + bool flag = true; + char* tmp = NULL; + MYSQL_RES *result = DB->QStore("SELECT id, map, x, y, `range`, respawntime, `limit`, tacticalpoints, moblist FROM list_mobgroups"); + if (result == NULL) return false; + while (row = mysql_fetch_row(result)) + { + CMobGroup* thisgroup = new (nothrow) CMobGroup; + if (thisgroup == NULL) { + Log(MSG_ERROR, "Error allocating memory"); + DB->QFree(); + return false; + } + thisgroup->id = atoi(row[0]); + thisgroup->map = atoi(row[1]); + thisgroup->point.x = atof(row[2]); + thisgroup->point.y = atof(row[3]); + thisgroup->range = atoi(row[4]); + thisgroup->respawntime = atoi(row[5]); + thisgroup->limit = atoi(row[6]); + thisgroup->tacticalpoints = atoi(row[7]); + char* mobList = row[8]; + + thisgroup->lastRespawnTime = clock(); + thisgroup->active = 0; + thisgroup->basicKills = 0; + thisgroup->curTac = 0; + thisgroup->curBasic = 0; + + thisgroup->basicMobs.clear(); + thisgroup->tacMobs.clear(); + + // Fill in basic/tac mobs + tmp = strtok(mobList, ",|"); + while (tmp != NULL) { + int mobId = atoi(tmp); + tmp = strtok(NULL, ",|"); + if (tmp == NULL) { + Log(MSG_ERROR, "MobGroup %i is invalid", thisgroup->id); + flag = false; + break; + } + int amount = atoi(tmp); + tmp = strtok(NULL, ",|"); + if (tmp == NULL) { + Log(MSG_ERROR, "MobGroup %i is invalid", thisgroup->id); + flag = false; + break; + } + int tactical = atoi(tmp); + CMob *thismob = new (nothrow) CMob; + if (thismob == NULL) { + Log(MSG_ERROR, "Error allocating memory"); + DB->QFree(); + return false; + } + thismob->amount = amount; + thismob->tactical = (unsigned int)tactical/2; + thismob->mobId = mobId; + thismob->thisnpc = GetNPCDataByID( thismob->mobId ); + thismob->mapdrop = GetDropData( thisgroup->map ); //hmm. We don't even use these drops anymore + thismob->mobdrop = GetDropData( thismob->thisnpc->dropid ); + if (thismob->thisnpc == NULL) { + Log(MSG_WARNING, "Invalid mobId - Mob %i will not be added", atoi(row[0])); + delete thismob; + continue; + } + if (thismob->tactical) + thisgroup->tacMobs.push_back(thismob); + else + thisgroup->basicMobs.push_back(thismob); + tmp = strtok(NULL, ",|"); + } + if (!flag) { + delete thisgroup; + continue; + } + MapList.Index[thisgroup->map]->MobGroupList.push_back(thisgroup); + mobGroups.push_back(thisgroup); + } + DB->QFree( ); + } + return true; +} + +bool CWorldServer::LoadMonsterSpawn( ) +{ + Log( MSG_INFO, "Loading SpawnZones data" ); + //clear the respawns data first + + for(int i=0;iMonsterSpawnList.clear(); + } + MYSQL_ROW row; + //MYSQL_RES *result = DB->QStore("SELECT id,map,montype,min,max,respawntime,points, spawntype, triggermontype, triggerammount, agressive, areatrigger, lim, spawnk FROM list_spawnareas"); + MYSQL_RES *result = DB->QStore("SELECT id, map, montype, min, max, respawntime, x, y, radius, spawntype, triggermontype, triggerammount, agressive, areatrigger, lim, spawnk, aip, refvar, refval FROM list_special_spawns"); + + if(result == NULL) + { + DB->QFree( ); + return false; + } + Log( MSG_INFO, "Loaded database data" ); + while(row = mysql_fetch_row(result)) + { + bool flag = true; + char* tmp; + CSpawnArea* thisspawn = new (nothrow) CSpawnArea; + if(thisspawn==NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + DB->QFree( ); + return false; + } + thisspawn->id = atoi(row[0]); + if(thisspawn->id > GServer->maxSpawnId) + GServer->maxSpawnId = thisspawn->id; + thisspawn->map = atoi(row[1]); + thisspawn->montype = atoi(row[2]); + thisspawn->min = 0;//atoi(row[3]); + thisspawn->max = atoi(row[4]); + thisspawn->respawntime = atoi(row[5]); + thisspawn->point.x = atoi(row[6]); + if(thisspawn->map == 3) + thisspawn->point.x -= 100; //for some reason some JP map has an X offset. + thisspawn->point.y = atoi(row[7]); + thisspawn->radius = atoi(row[8]); + if (thisspawn->respawntime == 0) + thisspawn->respawntime=5; + thisspawn->amon = 0; + thisspawn->lastRespawnTime = clock(); + thisspawn->type = atoi(row[9]); + thisspawn->triggertype = atoi(row[10]); + thisspawn->triggeramount = atoi(row[11]); + thisspawn->aggressive = atoi(row[12]); + thisspawn->typeoftrigger = atoi(row[13]); + thisspawn->limit = atoi(row[14]); + thisspawn->spawnkey = atoi(row[15]); + thisspawn->AI = atoi(row[16]); + thisspawn->RefVar = atoi(row[17]); + thisspawn->RefVal = atoi(row[18]); + thisspawn->triggercount = 0; + //if(thisspawn->id >= 10000) //tower defense spawn + // thisspawn->triggercount = 10000; //disables the spawn until it is reset to 0 + //if(flag) + //{ + thisspawn->thisnpc = GetNPCDataByID( thisspawn->montype ); + if(thisspawn->thisnpc == NULL) + { + Log( MSG_WARNING, "Invalid montype - Spawn %i will not be added", thisspawn->id ); + delete thisspawn; + continue; + } + MapList.Index[thisspawn->map]->MonsterSpawnList.push_back( thisspawn ); + //} + } + DB->QFree( ); + return true; +} + +bool CWorldServer::LoadNPCs( ) +{ + Log( MSG_INFO, "Loading NPC spawn " ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT type,map,dir,x,y,dialogid FROM list_npcs"); + if(result == NULL) return false; + while(row = mysql_fetch_row(result)) + { + CNPC* thisnpc = new (nothrow) CNPC; + if(thisnpc == NULL) + { + Log(MSG_ERROR, "Error allocing memory" ); + DB->QFree( ); + return false; + } + thisnpc->clientid = GetNewClientID(); + thisnpc->npctype = atoi(row[0]); + thisnpc->posMap = atoi(row[1]); + thisnpc->dir = (float)atof(row[2]); + thisnpc->pos.x = (float)atof(row[3]); + thisnpc->pos.y = (float)atof(row[4]); + thisnpc->thisnpc = GetNPCDataByID( thisnpc->npctype ); + thisnpc->thisnpc->_EntityType = ENTITY_NPC; + if( thisnpc->thisnpc == NULL) + { + + delete thisnpc; + continue; + } + thisnpc->thisnpc->dialogid = atoi(row[5]); // This is global to NPC type + //Log( MSG_INFO, "creating npc type %i",thisnpc->npctype ); + MapList.Index[thisnpc->posMap]->AddNPC( thisnpc ); + thisnpc->thisnpc->eventid = 0; + } + DB->QFree( ); + return true; +} + +bool CWorldServer::LoadPYDropsData( ) +{ + Log( MSG_INFO, "Loading PYDrops Data" ); + MDropList.clear(); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id,type,min_level,max_level,prob,mob,map,alt FROM item_drops"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + while(row = mysql_fetch_row(result)) + { + CMDrops* newdrop = new (nothrow) CMDrops; + assert(newdrop); + newdrop->itemnum = atoi(row[0]); + newdrop->itemtype = atoi(row[1]); + newdrop->level_min = atoi(row[2]); + newdrop->level_max = atoi(row[3]); + newdrop->prob = atoi(row[4]); + newdrop->mob = atoi(row[5]); + newdrop->map = atoi(row[6]); + char *tmp; + if((tmp = strtok( row[7] , "|"))==NULL) + newdrop->alt[0]=0; + else + newdrop->alt[0]=atoi(tmp); + for(unsigned int i=1;i<8; i++) + { + if((tmp = strtok( NULL , "|"))==NULL) + newdrop->alt[i]=0; + else + newdrop->alt[i]=atoi(tmp); + } + //if(newdrop->mob != 0) + //Log( MSG_INFO, "found a non zero mob value %i",newdrop->mob ); + MDropList.push_back( newdrop ); + } + DB->QFree( ); + Log( MSG_INFO, "PYDrops loaded" ); + return true; +} + +bool CWorldServer::LoadNewDrops( ) +{ + Log( MSG_INFO, "Loading New Drops Data" ); + MDropList.clear(); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT * FROM new_drops"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + while(row = mysql_fetch_row(result)) + { + CMDrops* newdrop = new (nothrow) CMDrops; + assert(newdrop); + + UINT readIndex = 1; + newdrop->Dropid = atoi(row[0]); + for(int i=0;i<50;i++) + { + newdrop->item[i] = atoi(row[readIndex]); + readIndex++; + newdrop->chance[i] = atoi(row[readIndex]); + readIndex++; + if(i == 0) + { + newdrop->RunningTotal[i] = newdrop->chance[i]; + } + else + { + newdrop->RunningTotal[i] = newdrop->RunningTotal[i-1] + newdrop->chance[i]; + } + //if(newdrop->Dropid == 26) + // Log( MSG_INFO, "item %i chance %i total %i",newdrop->item[i],newdrop->chance[i],newdrop->RunningTotal[i] ); + + } + newdrop->TotalChance = newdrop->RunningTotal[49]; + MDropList.push_back( newdrop ); + } + DB->QFree( ); + Log( MSG_INFO, "New Drops loaded" ); + return true; +} + +bool CWorldServer::LoadSkillBookDropsData( ) +{ + Log( MSG_INFO, "Loading Skillbook data" ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id,itemtype,min,max,prob FROM list_skillbooks"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + int c = 0; + while(row = mysql_fetch_row(result)) + { + + c++; + CMDrops* newdrop = new (nothrow) CMDrops; + assert(newdrop); + newdrop->itemnum = atoi(row[0]); + newdrop->itemtype = atoi(row[1]); + newdrop->level_min = atoi(row[2]); + newdrop->level_max = atoi(row[3]); + newdrop->prob = atoi(row[4]); + SkillbookList.push_back( newdrop ); + } + DB->QFree( ); + Log( MSG_INFO, "Skillbook Data loaded" ); + return true; +} + +bool CWorldServer::LoadLTB( ) +{ + Log(MSG_INFO, "Loading LTB strings"); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id,npc,ltbstring,questnpc,questltb FROM ltb"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + int index = 0; + while( row = mysql_fetch_row(result) ) + { + strcpy(GServer->Ltbstring[atoi(row[0])].NPCname,row[1]); + strcpy(GServer->Ltbstring[atoi(row[0])].LTBstring,row[2]); + strcpy(GServer->Ltbstring[atoi(row[0])].QuestNpcName,row[3]); + strcpy(GServer->Ltbstring[atoi(row[0])].QuestLTB,row[4]); + } + DB->QFree( ); + Log( MSG_INFO, "LTB Data Loaded" ); + return true; +} + +bool CWorldServer::LoadConfig( ) +{ + Log( MSG_INFO, "Loading database config" ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT exp_rate, drop_rate, zuly_rate, blue_chance, stat_chance, slot_chance, refine_chance, rare_refine, kill_on_fail, spawntype, player_damage, monster_damage, player_acc, monster_acc, pvp_acc, skill_damage, dropmultiple, attkmult, skillmult, dropdelay, basedroprate, ktpointrate, movespeedmod, maxlevel FROM list_config"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + if (mysql_num_rows(result) == 0) + { + DB->QFree( ); + return false; + } + while( row = mysql_fetch_row(result) ) + { + GServer->Config.EXP_RATE = atoi(row[0]); + GServer->Config.DROP_RATE = atoi(row[1]); + GServer->Config.ZULY_RATE = atoi(row[2]); + GServer->Config.BlueChance = atoi(row[3]); + GServer->Config.StatChance = atoi(row[4]); + GServer->Config.SlotChance = atoi(row[5]); + GServer->Config.RefineChance = atoi(row[6]); + GServer->Config.Rare_Refine = atoi(row[7]); + GServer->Config.KillOnFail = atoi(row[8]); + GServer->Config.SpawnType = atoi(row[9]); + GServer->Config.PlayerDmg = atoi(row[10]); + GServer->Config.MonsterDmg = atoi(row[11]); + // Not implemented in server yet - Drakia + //GServer->Config.PlayerAcc = atoi(row[12]); + //GServer->Config.MonsterAcc = atoi(row[13]); + //GServer->Config.PvpAcc = atoi(row[14]); + //GServer->Config.SkillDmg = atoi(row[15]); + GServer->Config.DropMultiple = atoi(row[16]); + GServer->Config.AttkMult = atoi(row[17]); + GServer->Config.SkillMult = atoi(row[18]); + GServer->Config.DropDelay = atoi(row[19]); + GServer->Config.BASE_DROP_CHANCE = atoi(row[20]); + GServer->Config.KTPointRate = atoi(row[21]); + GServer->MOVE_SPEED_MOD = atoi(row[22]); + GServer->Config.MaxLevel = atoi(row[23]); + } + DB->QFree( ); + Log( MSG_INFO, "Config Data Loaded" ); + return true; +} + + +bool CWorldServer::LoadMonsters( ) +{ + Log( MSG_INFO, "Monsters Spawn" ); + // Do our monster spawnin + + //old method + /*for(UINT i=0;iMonsterSpawnList.size();j++) + { + CSpawnArea* thisspawn = thismap->MonsterSpawnList.at(j); + thisspawn->mapdrop = GetDropData( thisspawn->map ); + thisspawn->mobdrop = GetDropData( thisspawn->thisnpc->dropid ); + for(UINT k=0;kmax;k++) + { + fPoint position = RandInPoly( thisspawn->points, thisspawn->pcount ); + thismap->AddMonster( thisspawn->montype, position, 0, thisspawn->mobdrop, thisspawn->mapdrop, thisspawn->id ); + } + } + }*/ + + //PY method + if(GServer->Config.SpawnType == 1) + { + Log( MSG_INFO,"PY method"); + for(UINT i=0;iMonsterSpawnList.size();j++) + { + CSpawnArea* thisspawn = thismap->MonsterSpawnList.at(j); + if(thisspawn->id >= 10000)continue; //don't spawn the tower def monsters + //thisspawn->mapdrop = GetDropData( thisspawn->map ); + //thisspawn->mobdrop = GetDropData( thisspawn->thisnpc->dropid ); + for(UINT k=0;kmax;k++) + { + if( thisspawn->type == 1 ) + { + fPoint position = RandInCircle( thisspawn->point, thisspawn->radius ); + //fPoint position = RandInPoly( thisspawn->points, thisspawn->pcount ); + thismap->AddMonster( thisspawn->montype, position, 0, thisspawn->id, thisspawn->aggressive, false, thisspawn->limit); + } + } + } + } + //Log( MSG_INFO, "Finished Monsters Spawns" ); + return true; + } + + //IFO method + if(GServer->Config.SpawnType == 0) + { + Log(MSG_LOAD,"IFO method"); + for (UINT i = 0; i < MapList.Map.size(); i++) + { + CMap *thismap = MapList.Map.at(i); + for (UINT j = 0; j < thismap->MobGroupList.size(); j++) + { + CMobGroup* thisgroup = thismap->MobGroupList.at(j); + // Load some basic mobs onto map + for (UINT k = 0; k < thisgroup->limit; k++) + { + CMob* thismob = thisgroup->basicMobs.at(thisgroup->curBasic); + thisgroup->curBasic++; + if (thisgroup->curBasic >= thisgroup->basicMobs.size()) thisgroup->curBasic = 0; + for (UINT l = 0; l < thismob->amount; l++) + { + fPoint position = RandInCircle( thisgroup->point, thisgroup->range ); + thismap->AddMonster( thismob->mobId, position, 0, thisgroup->id,1 ); + } + } + } + } + } + return true; +} + +/* +bool CWorldServer::LoadUpgrade( )//for succes rate +{ + Log( MSG_INFO, "Loading Refine Data " ); + FILE* fh = NULL; + fh = fopen("data/refine_data.csv", "r"); + if(fh==NULL) + { + Log(MSG_ERROR, "\nError loading file data/refine_data.csv" ); + return false; + } + char line[50]; + fgets( line, 50, fh );// this is the column name + while(!feof(fh)) + { + memset( &line, '\0', 50 ); + fgets( line, 50, fh ); + upgrade[GetUIntValue(",",&line)] = GetUIntValue(","); + } + fclose(fh); + return true; +} */ + +bool CWorldServer::LoadUpgrade( )//Load success rates for Bindrune and Talismans from STB +{ + Log( MSG_INFO, "Loading Upgrades data " ); + for (unsigned int i = 0; iQExecute("UPDATE accounts set online=false"); + return true; +} + +bool CWorldServer::LoadEquip( ) +{ + Log( MSG_INFO, "Loading Equip Data " ); + for(int j=0;j<9;j++) + { + EquipList[j+1].STBMax = STB_ITEM[j].rowcount; + for(unsigned int i=0;iid = i; + newequip->itemtype = (j+1); + newequip->equiptype = (j+1); + //get back to this later. It's a pain in the ass reading a string out of the STB + //strcpy(newequip->name, STB_ITEM[j].rows[i][0]); + newequip->type = STB_ITEM[j].rows[i][4]; + newequip->price = STB_ITEM[j].rows[i][5]; + newequip->pricerate = STB_ITEM[j].rows[i][6]; + newequip->weight = STB_ITEM[j].rows[i][7]; + newequip->quality = STB_ITEM[j].rows[i][8]; + newequip->level = STB_ITEM[j].rows[i][13]; + newequip->material = STB_ITEM[j].rows[i][14]; + newequip->unionpoint = STB_ITEM[j].rows[i][15]; + newequip->durability = STB_ITEM[j].rows[i][29]; + newequip->defense = STB_ITEM[j].rows[i][31]; + newequip->magicresistence = STB_ITEM[j].rows[i][32]; + newequip->attackdistance = STB_ITEM[j].rows[i][33];//Speed of travel/Range + if(newequip->equiptype == SHOE) + { + newequip->movespeed = STB_ITEM[j].rows[i][33]; + } + else + { + newequip->movespeed = 0; + } + + if (STB_ITEM[j].fieldcount>35) + { + newequip->attackpower = STB_ITEM[j].rows[i][35]; + newequip->attackspeed = STB_ITEM[j].rows[i][36]; + newequip->magicattack = STB_ITEM[j].rows[i][37]; //new. Staffs and wands are designated as magic weapons. + if(newequip->magicattack > 1)newequip->magicattack = 0; //all the blue named spears have weird numbers here. Maybe why NA had a massive damage glitch with them. + } + else + { + newequip->attackpower = 0; + newequip->attackspeed = 0; + newequip->magicattack = 0; + } + + + for(int k=0;k<3;k++) + newequip->occupation[k] = STB_ITEM[j].rows[i][(16+k)]; + for(int k=0;k<2;k++) + newequip->condition1[k] = STB_ITEM[j].rows[i][(19+k)]; + for(int k=0;k<2;k++) + newequip->condition2[k] = STB_ITEM[j].rows[i][(21+k)]; + for(int k=0;k<2;k++) + newequip->stat1[k] = STB_ITEM[j].rows[i][(24+k)]; + for(int k=0;k<2;k++) + newequip->stat2[k] = STB_ITEM[j].rows[i][(27+k)]; + + newequip->refinetype = STB_ITEM[j].rows[i][45]; //new this is the recipe for refining the item + newequip->itemgrade = STB_ITEM[j].rows[i][46]; + newequip->raretype = STB_ITEM[j].rows[i][47]; + + if(newequip->id > 4999) + { + //Log(MSG_WARNING,"Equipment type %i ID %i is outside of accepted range",newequip->equiptype, newequip->id ); + //item not added + //return false; + } + else + { + EquipList[newequip->equiptype].Data.push_back( newequip ); + EquipList[newequip->equiptype].Index[newequip->id] = newequip; // Index to read more quickly the data + } + //delete newequip; + } + } + return true; +} + +bool CWorldServer::LoadJemItem( )//STB_ITEM[10] +{ + Log( MSG_INFO, "Loading Jem Data " ); + EquipList[11].STBMax = STB_ITEM[10].rowcount; + for(unsigned int i=0;iid = i; + thisjem->itemtype = 11; + thisjem->type = STB_ITEM[10].rows[i][4]; + thisjem->price = STB_ITEM[10].rows[i][5]; + thisjem->pricerate = STB_ITEM[10].rows[i][6]; + thisjem->weight = STB_ITEM[10].rows[i][7]; + thisjem->quality = STB_ITEM[10].rows[i][8]; + thisjem->material = STB_ITEM[10].rows[i][14]; + thisjem->unionpoint = STB_ITEM[10].rows[i][15]; + thisjem->stat1[0] = STB_ITEM[10].rows[i][16]; + thisjem->stat1[1] = STB_ITEM[10].rows[i][17]; + thisjem->stat2[0] = STB_ITEM[10].rows[i][18]; + thisjem->stat2[1] = STB_ITEM[10].rows[i][19]; + if(thisjem->id > 999) + { + Log(MSG_WARNING,"Gem ID %i outside of accepted range",thisjem->id ); + return false; + } + JemList.Data.push_back( thisjem ); + JemList.Index[thisjem->id] = thisjem; + } + return true; +} + +bool CWorldServer::LoadNaturalItem( )//STB_ITEM[11] +{ + Log( MSG_INFO, "Loading Natural Data " ); + for(unsigned int i=0;iid = i; + thisnatural->itemtype = 12; + thisnatural->type = STB_ITEM[11].rows[i][4]; + thisnatural->price = STB_ITEM[11].rows[i][5]; + thisnatural->pricerate = STB_ITEM[11].rows[i][6]; + thisnatural->weight = STB_ITEM[11].rows[i][7]; + thisnatural->quality = STB_ITEM[11].rows[i][8]; + thisnatural->unionpoint = STB_ITEM[11].rows[i][15]; + thisnatural->pricevalue = STB_ITEM[11].rows[i][16]; + if(thisnatural->id > 999) + { + Log(MSG_WARNING,"Natural item ID %i outside of accepted range",thisnatural->id ); + return false; + } + NaturalList.Data.push_back( thisnatural ); + NaturalList.Index[thisnatural->id] = thisnatural; + } + return true; +} + +bool CWorldServer::LoadPatItem( ) +{ + Log( MSG_INFO, "Loading PAT Data " ); + EquipList[14].STBMax = STB_ITEM[13].rowcount; + for(unsigned int i=0;iid = i; + newpat->itemtype = 14; + newpat->type = STB_ITEM[13].rows[i][4]; + newpat->price = STB_ITEM[13].rows[i][5]; + newpat->pricerate = STB_ITEM[13].rows[i][6]; + newpat->weight = STB_ITEM[13].rows[i][7]; + newpat->quality = STB_ITEM[13].rows[i][8]; + newpat->material = STB_ITEM[13].rows[i][14]; + newpat->unionpoint = STB_ITEM[13].rows[i][15]; + newpat->partversion = STB_ITEM[13].rows[i][17]; + newpat->level = STB_ITEM[13].rows[i][22]; // extra field added rev 70 + newpat->condition[1] = STB_ITEM[13].rows[i][24]; // extra field added rev 70 + newpat->modifier[1] = STB_ITEM[13].rows[i][25]; // extra field added rev 70 + newpat->condition[2] = STB_ITEM[13].rows[i][27]; // extra field added rev 70 + newpat->modifier[2] = STB_ITEM[13].rows[i][28]; // extra field added rev 70 + newpat->durability = STB_ITEM[13].rows[i][29]; + newpat->maxfuel = STB_ITEM[13].rows[i][31]; + newpat->fuelcons = STB_ITEM[13].rows[i][32]; + newpat->speed = STB_ITEM[13].rows[i][33]; + newpat->attackdistance = STB_ITEM[13].rows[i][35]; + newpat->attackpower = STB_ITEM[13].rows[i][36]; + newpat->attackspeed = STB_ITEM[13].rows[i][37]; + if(newpat->id > 999) + { + Log(MSG_WARNING,"PAT item ID %i outside of accepted range",newpat->id ); + return false; + } + PatList.Data.push_back( newpat ); + PatList.Index[newpat->id] = newpat; + } + return true; +} + +bool CWorldServer::LoadBreak( ) //STB_BREAK +{ + Log( MSG_INFO, "Bypassing Break Data till we actually add some"); + return true; + unsigned int STBcol = 3; + unsigned int Ind = 1; + for(unsigned int i=0;iid = i; + newbreak->ItemId = STB_BREAK.rows[i][2]; + for(unsigned int j=1;j<=20;j++) + { + if(i > 700) + Log( MSG_INFO, "STB row %i item %i",i,j); + for(unsigned int k=1;k<=4;k++) + { + newbreak->Mat[Ind]=STB_BREAK.rows[i][STBcol]; + newbreak->Min[Ind]=STB_BREAK.rows[i][STBcol+3]; + newbreak->Max[Ind]=STB_BREAK.rows[i][STBcol+4]; + newbreak->Chance[Ind]=STB_BREAK.rows[i][STBcol+5]; + } + STBcol += 4; + } + newbreak->MinDis=STB_BREAK.rows[i][STBcol]; + newbreak->MaxDis=STB_BREAK.rows[i][STBcol+1]; + newbreak->ItemCount=STB_BREAK.rows[i][STBcol+2]; + if(newbreak->id > 5000) + { + Log(MSG_WARNING,"Break item ID %i outside of accepted range",newbreak->id ); + return false; + } + BreakList.Data.push_back( newbreak ); + BreakList.Index[newbreak->id] = newbreak; + } + Log( MSG_INFO, "Loaded Break Data "); + return true; +} + +bool CWorldServer::LoadProductItem( )//STB_PRODUCT +{ + Log( MSG_INFO, "Loading Product Data " ); + for(unsigned int i=0;iid = i; + newproduct->matType=STB_PRODUCT.rows[i][1]; + newproduct->item[0]=STB_PRODUCT.rows[i][2]; + newproduct->itemtype[0]=(int)(newproduct->item[0] / 1000); + newproduct->itemid[0]=newproduct->item[0] - (newproduct->itemtype[0] * 1000); + newproduct->amount[0]=STB_PRODUCT.rows[i][3]; + newproduct->item[1]=STB_PRODUCT.rows[i][4]; + newproduct->itemtype[1]=(int)(newproduct->item[1] / 1000); + newproduct->itemid[1]=newproduct->item[1] - (newproduct->itemtype[1] * 1000); + newproduct->amount[1]=STB_PRODUCT.rows[i][5]; + newproduct->item[2]=STB_PRODUCT.rows[i][6]; + newproduct->itemtype[2]=(int)(newproduct->item[2] / 1000); + newproduct->itemid[2]=newproduct->item[2] - (newproduct->itemtype[2] * 1000); + newproduct->amount[2]=STB_PRODUCT.rows[i][7]; + newproduct->item[3]=STB_PRODUCT.rows[i][8]; + newproduct->itemtype[3]=(int)(newproduct->item[3] / 1000); + newproduct->itemid[3]=newproduct->item[3] - (newproduct->itemtype[3] * 1000); + newproduct->amount[3]=STB_PRODUCT.rows[i][9]; + if(newproduct->id > 1999) + { + Log(MSG_WARNING,"Product ID %i outside of accepted range",newproduct->id ); + return false; + } + ProductList.Data.push_back( newproduct ); + ProductList.Index[newproduct->id] = newproduct; + } + return true; +} + +bool CWorldServer::LoadSellData( )//STB_SELL +{ + Log( MSG_INFO, "Loading Sell Data" ); + //Log(MSG_WARNING,"STB rowcount: %i",STB_SELL.rowcount ); + for(unsigned int i=0;iid = i; + for(unsigned int j=2;jitem[j-2] = STB_SELL.rows[i][j]; + } + if(newsell->id > 999) + { + Log(MSG_WARNING,"List_Sell ID %i outside of accepted range",newsell->id ); + return false; + } + SellList.Data.push_back( newsell ); + SellList.Index[newsell->id] = newsell; + } + Log( MSG_INFO, "Loaded Sell Data" ); + return true; +} + +bool CWorldServer::LoadConsItem( ) +{ + Log( MSG_INFO, "Loading Consumable Data " ); + for(unsigned int i=0;iid = i; + newuse->itemtype = 10; + newuse->restriction = STB_ITEM[9].rows[i][3]; + newuse->type = STB_ITEM[9].rows[i][4]; + newuse->price = STB_ITEM[9].rows[i][5]; + newuse->pricerate = STB_ITEM[9].rows[i][6]; + newuse->weight = STB_ITEM[9].rows[i][7]; + newuse->quality = STB_ITEM[9].rows[i][8]; + newuse->unionpoint = STB_ITEM[9].rows[i][15]; + newuse->pricevalue = STB_ITEM[9].rows[i][16]; + newuse->usecondition[0]= STB_ITEM[9].rows[i][17]; + newuse->usecondition[1]= STB_ITEM[9].rows[i][18]; + newuse->useeffect[0]= STB_ITEM[9].rows[i][19]; + newuse->useeffect[1]= STB_ITEM[9].rows[i][20]; + //if(newuse->id == 285) + // Log(MSG_WARNING,"Usitem useeffect[1] = %i",newuse->useeffect[1] ); + if(newuse->id > 1999) + { + Log(MSG_WARNING,"Usitem ID %i outside of accepted range",newuse->id ); + return false; + } + UseList.Data.push_back( newuse ); + UseList.Index[newuse->id] = newuse; + } + return true; +} + +bool CWorldServer::LoadWayPoints() +{ + Log( MSG_INFO, "Loading Waypoints" ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT mapid,wpnum,mapx,mapy,wptype FROM list_waypoints"); + if(result==NULL) + { + DB->QFree( ); + return false; + } + if (mysql_num_rows(result) == 0) + { + DB->QFree( ); + Log( MSG_INFO, "list_waypoints currently contains no data" ); + return true; + } + // first clear the waypoint list + for(int i=0;i<100;i++) + { + for(int j=0;j<100;j++) + { + GServer->WPList[i][j].pos.x = 0; + GServer->WPList[i][j].pos.y = 0; + GServer->WPList[i][j].WPType = 0; + } + } + while( row = mysql_fetch_row(result) ) + { + int thismap = atoi(row[0]); + int thisindex = atoi(row[1]); + GServer->WPList[thismap][thisindex].pos.x = atof(row[2]); + GServer->WPList[thismap][thisindex].pos.y = atof(row[3]); + GServer->WPList[thismap][thisindex].WPType = atoi(row[4]); + } + DB->QFree( ); + return true; +} + +bool CWorldServer::LoadZoneData( ) +{ + Log( MSG_INFO, "Loading Zone Data " ); + + for(unsigned int i=0;iid = i; + newzone->dungeon = (STB_ZONE.rows[i][4] == 1); + newzone->dayperiod = STB_ZONE.rows[i][13]; + newzone->morningtime = STB_ZONE.rows[i][14]; + newzone->daytime = STB_ZONE.rows[i][15]; + newzone->eveningtime = STB_ZONE.rows[i][16]; + newzone->nighttime = STB_ZONE.rows[i][17]; + newzone->allowpvp = STB_ZONE.rows[i][18]; + newzone->allowpat = STB_ZONE.rows[i][30]==0?true:false; + if (i==20||i==22) + newzone->ghost = 0; + else + newzone->ghost = 1; + newzone->MapTime = 0; + //TD stuff + newzone->TDNextMon = 0; + newzone->TDNextSpawn = 0; + newzone->lastTDSpawnTime = clock(); + newzone->LastUpdate = clock( ); + newzone->CurrentTime = 0; + newzone->TDMobDelay = 1500; + for(unsigned int j=0;j<=100;j++) + { + newzone->TDMobList[j] = 0; + } + //end TD stuff + newzone->MonsterSpawnList.clear(); + if(GServer->Config.SpawnType == 0) + newzone->MobGroupList.clear(); + newzone->crystalhealth = 100; + newzone->mapXPRate = 2; //always 2 as long as the crystal is alive + MapList.Map.push_back(newzone); + MapList.Index[newzone->id] = newzone; + } + return true; +} + +bool CWorldServer::LoadItemStats( ) +{ + Log( MSG_INFO, "Loading Item Stats " ); + for(unsigned int i=0;irowcount, 4, 1, fh ); + fread( &data->fieldcount, 4, 1, fh ); + data->rowcount--; + data->fieldcount--; + // Read the data + fseek( fh, dataoffset, SEEK_SET ); + int* tmp = new int[data->rowcount*data->fieldcount]; + data->rows = new int*[data->rowcount]; + for( unsigned i = 0; i < data->rowcount; i++ ) data->rows[i] = &tmp[i*data->fieldcount]; + for( unsigned j = 0; j < data->rowcount*data->fieldcount; j++ ) { + fread( &fieldlen, 2, 1, fh ); + fread( tmpfield, 1, fieldlen, fh ); + tmpfield[fieldlen]=0; + tmp[j] = atoi( tmpfield ); + } + } + + return 0; +} + +//void STBFreeData( CSTBData* data ) +//{ +// delete [] data->rows[0]; +// delete [] data->rows; +//} diff --git a/Src/World Server/stbhandler.cpp b/Src/World Server/stbhandler.cpp new file mode 100644 index 0000000..8df1cc4 --- /dev/null +++ b/Src/World Server/stbhandler.cpp @@ -0,0 +1,53 @@ +// Written by Brett19 +#include "worldserver.h" + +// ----------------------------------------------------------------------------------------- +// STB reading shit +// ----------------------------------------------------------------------------------------- +int STBStoreData( const char* filename, CSTBData* data ) +{ + char tmpfield[512]; + unsigned dataoffset = 0; + unsigned short fieldlen = 0; + + FILE* fh = fopen( filename, "rb" ); + if ( fh == 0 ) + { + Log( MSG_ERROR, "Could not load STB '%s'", filename ); + return 1; + } + else + { + // Read the header + fseek( fh, 4, SEEK_SET ); + fread( &dataoffset, 4, 1, fh ); + fread( &data->rowcount, 4, 1, fh ); + fread( &data->fieldcount, 4, 1, fh ); + data->rowcount--; + data->fieldcount--; + // Read the data + fseek( fh, dataoffset, SEEK_SET ); + int* tmp = new int[data->rowcount * data->fieldcount]; + data->rows = new int*[data->rowcount]; + for( unsigned i = 0; i < data->rowcount; i++ ) + { + data->rows[i] = &tmp[i * data->fieldcount]; + } + for( unsigned j = 0; j < data->rowcount * data->fieldcount; j++ ) + { + fread( &fieldlen, 2, 1, fh ); + fread( tmpfield, 1, fieldlen, fh ); + tmpfield[fieldlen]=0; + tmp[j] = atoi( tmpfield ); + } + fclose( fh ); + } + + return 0; +} + +void STBFreeData( CSTBData* data ) +{ + delete [] data->rows[0]; + delete [] data->rows; +} diff --git a/Src/World Server/stbhandler.h b/Src/World Server/stbhandler.h new file mode 100644 index 0000000..b15ec6a --- /dev/null +++ b/Src/World Server/stbhandler.h @@ -0,0 +1,11 @@ +// Written by Brett19 +#include "worldserver.h" + +struct CSTBData { + unsigned rowcount; + unsigned fieldcount; + int** rows; +}; + +int STBStoreData( const char* filename, CSTBData* data ); +void STBFreeData( CSTBData* data ); diff --git a/Src/World Server/stlhandler.cpp b/Src/World Server/stlhandler.cpp new file mode 100644 index 0000000..23e375e --- /dev/null +++ b/Src/World Server/stlhandler.cpp @@ -0,0 +1,137 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.dev-osrose.com + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +//LMA: Code taken from IFONPCDUMPER from Drakia and adpated. + +#include "worldserver.h" + + + +//Reading and storing a STL in a single map :) +int STLStoreData( const char* filename, int family) +{ + entry** index; + map table; + char tempfield[512]; + UINT entryCount; + UINT langCount; + int temp=0; + + + FILE* fh = fopen( filename, "rb" ); + if (fh==0) + { + Log( MSG_ERROR, "Could not load STL '%s'", filename ); + return 0; + } + + fread(&temp,1,1,fh); + fread(tempfield,1,temp,fh); + fread(&entryCount,4,1,fh); + + if(family == 6) + Log(MSG_DEBUG,"%s:: %i",filename,entryCount); + + index = new entry*[entryCount]; + for (UINT i = 0; i < entryCount; i++) + { + entry item; + temp=0; + fread(&temp,1,1,fh); + fread(tempfield,1,temp,fh); + tempfield[temp]=0; + + for(int k=0;k=48&&tempfield[k]<=57) + break; + tempfield[k]=48; + } + + item.stringId=atoi(tempfield); + fread(&item.id,4,1,fh); + table[item.stringId] = item; + index[i] = &table[item.stringId]; + } + + vector langOffset; + fread(&langCount,4,1,fh); + + for (UINT i = 0; i < langCount; i++) + { + temp=0; + fread(&temp,4,1,fh); + langOffset.push_back(temp); + } + + + UINT i=STL_ENGLISH; + fseek( fh, langOffset.at(i), SEEK_SET ); + + for (UINT j = 0; j < entryCount; j++) + { + entry* Entry = index[j]; + language lang; + temp=0; + fread(&temp,4,1,fh); + lang.offset=temp; + Entry->lang.push_back(lang); + } + + for (UINT j = 0; j < entryCount; j++) + { + entry* Entry = index[j]; + //language *item = &Entry->lang.at(i); + language *item = &Entry->lang.at(0); + fseek( fh, item->offset, SEEK_SET ); + temp=0; + fread(&temp,1,1,fh); + + //no text? + if(temp==0) + { + continue; + } + + fread(item->text,1,temp,fh); + item->text[temp]=0; + + //LMA: let's do the map ^_^ + UINT mapid=family*100000+index[j]->stringId; + if(GServer->STLNameList.find(mapid)!=GServer->STLNameList.end()) + { + Log(MSG_WARNING,"STL ID %u (line %i) already in map %s",mapid,j,GServer->STLNameList[mapid]); + continue; + } + + char* new_text=new char[temp+1]; + memcpy(new_text,item->text,temp); + new_text[temp]=0; + GServer->STLNameList[mapid]=new_text; + } + + fclose( fh ); + delete[] index; + + + return 1; +} + diff --git a/Src/World Server/stlhandler.h b/Src/World Server/stlhandler.h new file mode 100644 index 0000000..d64581d --- /dev/null +++ b/Src/World Server/stlhandler.h @@ -0,0 +1,42 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.dev-osrose.com + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +//LMA: Code taken from IFONPCDUMPER from Drakia and adpated. +#define STL_ENGLISH 1 +#include "worldserver.h" + + +int STLStoreData(const char* filename, int family); + +struct language +{ + char text[512]; + UINT offset; +}; + +struct entry +{ + UINT id; + UINT stringId; + vector lang; +}; + + diff --git a/Src/World Server/strhashing.cpp b/Src/World Server/strhashing.cpp new file mode 100644 index 0000000..e90aa2b --- /dev/null +++ b/Src/World Server/strhashing.cpp @@ -0,0 +1,19 @@ +// Written by Brett19 + +#include "worldserver.h" + +unsigned long MakeStrHash(const char *str) +{ + register unsigned long result = 0xDEADC0DE; + register unsigned long tkey = 0x7FED7FED; + register char c = 0; + while (*(str)){ + c = (*str<'a'||*str>'z')?*str:*str-32; + result += tkey; + tkey *= 0x21; + result ^= keys[c]; + tkey += result + c + 3; + str++; + } + return result; +} diff --git a/Src/World Server/strhashing.h b/Src/World Server/strhashing.h new file mode 100644 index 0000000..14eff75 --- /dev/null +++ b/Src/World Server/strhashing.h @@ -0,0 +1,20 @@ +const unsigned long keys[256] = { + 0x697A5, 0x6045C, 0xAB4E2, 0x409E4, 0x71209, 0x32392, 0xA7292, 0xB09FC, 0x4B658, 0xAAAD5, 0x9B9CF, 0xA326A, 0x8DD12, 0x38150, 0x8E14D, 0x2EB7F, + 0xE0A56, 0x7E6FA, 0xDFC27, 0xB1301, 0x8B4F7, 0xA7F70, 0xAA713, 0x6CC0F, 0x6FEDF, 0x2EC87, 0xC0F1C, 0x45CA4, 0x30DF8, 0x60E99, 0xBC13E, 0x4E0B5, + 0x6318B, 0x82679, 0x26EF2, 0x79C95, 0x86DDC, 0x99BC0, 0xB7167, 0x72532, 0x68765, 0xC7446, 0xDA70D, 0x9D132, 0xE5038, 0x2F755, 0x9171F, 0xCB49E, + 0x6F925, 0x601D3, 0x5BD8A, 0x2A4F4, 0x9B022, 0x706C3, 0x28C10, 0x2B24B, 0x7CD55, 0xCA355, 0xD95F4, 0x727BC, 0xB1138, 0x9AD21, 0xC0ACA, 0xCD928, + 0x953E5, 0x97A20, 0x345F3, 0xBDC03, 0x7E157, 0x96C99, 0x968EF, 0x92AA9, 0xC2276, 0xA695D, 0x6743B, 0x2723B, 0x58980, 0x66E08, 0x51D1B, 0xB97D2, + 0x6CAEE, 0xCC80F, 0x3BA6C, 0xB0BF5, 0x9E27B, 0xD122C, 0x48611, 0x8C326, 0xD2AF8, 0xBB3B7, 0xDED7F, 0x4B236, 0xD298F, 0xBE912, 0xDC926, 0xC873F, + 0xD0716, 0x9E1D3, 0x48D94, 0x9BD91, 0x5825D, 0x55637, 0xB2057, 0xBCC6C, 0x460DE, 0xAE7FB, 0x81B03, 0x34D8F, 0xC0528, 0xC9B59, 0x3D260, 0x6051D, + 0x93757, 0x8027F, 0xB7C34, 0x4A14E, 0xB12B8, 0xE4945, 0x28203, 0xA1C0F, 0xAA382, 0x46ABB, 0x330B9, 0x5A114, 0xA754B, 0xC68D0, 0x9040E, 0x6C955, + 0xBB1EF, 0x51E6B, 0x9FF21, 0x51BCA, 0x4C879, 0xDFF70, 0x5B5EE, 0x29936, 0xB9247, 0x42611, 0x2E353, 0x26F3A, 0x683A3, 0xA1082, 0x67333, 0x74EB7, + 0x754BA, 0x369D5, 0x8E0BC, 0xABAFD, 0x6630B, 0xA3A7E, 0xCDBB1, 0x8C2DE, 0x92D32, 0x2F8ED, 0x7EC54, 0x572F5, 0x77461, 0xCB3F5, 0x82C64, 0x35FE0, + 0x9203B, 0xADA2D, 0xBAEBD, 0xCB6AF, 0xC8C9A, 0x5D897, 0xCB727, 0xA13B3, 0xB4D6D, 0xC4929, 0xB8732, 0xCCE5A, 0xD3E69, 0xD4B60, 0x89941, 0x79D85, + 0x39E0F, 0x6945B, 0xC37F8, 0x77733, 0x45D7D, 0x25565, 0xA3A4E, 0xB9F9E, 0x316E4, 0x36734, 0x6F5C3, 0xA8BA6, 0xC0871, 0x42D05, 0x40A74, 0x2E7ED, + 0x67C1F, 0x28BE0, 0xE162B, 0xA1C0F, 0x2F7E5, 0xD505A, 0x9FCC8, 0x78381, 0x29394, 0x53D6B, 0x7091D, 0xA2FB1, 0xBB942, 0x29906, 0xC412D, 0x3FCD5, + 0x9F2EB, 0x8F0CC, 0xE25C3, 0x7E519, 0x4E7D9, 0x5F043, 0xBBA1B, 0x6710A, 0x819FB, 0x9A223, 0x38E47, 0xE28AD, 0xB690B, 0x42328, 0x7CF7E, 0xAE108, + 0xE54BA, 0xBA5A1, 0xA09A6, 0x9CAB7, 0xDB2B3, 0xA98CC, 0x5CEBA, 0x9245D, 0x5D083, 0x8EA21, 0xAE349, 0x54940, 0x8E557, 0x83EFD, 0xDC504, 0xA6059, + 0xB85C9, 0x9D162, 0x7AEB6, 0xBED34, 0xB4963, 0xE367B, 0x4C891, 0x9E42C, 0xD4304, 0x96EAA, 0xD5D69, 0x866B8, 0x83508, 0x7BAEC, 0xD03FD, 0xDA122 +}; + +unsigned long MakeStrHash(const char *str); diff --git a/Src/World Server/worldmap.cpp b/Src/World Server/worldmap.cpp new file mode 100644 index 0000000..27b6fa0 --- /dev/null +++ b/Src/World Server/worldmap.cpp @@ -0,0 +1,379 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ + +#include "worldmap.h" + +CMap::CMap( ) +{ + id = 0; + dayperiod = 0; + morningtime = 0; + daytime = 0; + eveningtime = 0; + nighttime = 0; + allowpvp = 0; + allowpat = 0; + MapTime = 0; + LastUpdate = clock( ); + CurrentTime = 0; + RespawnList.clear(); + MonsterSpawnList.clear(); + //if(GServer->Config.SpawnType == 0) + MobGroupList.clear(); + MonsterList.clear(); + DropsList.clear(); + PlayerList.clear(); + NPCList.clear(); + TeleGateList.clear(); + MonsterMutex = PTHREAD_MUTEX_INITIALIZER; + DropMutex = PTHREAD_MUTEX_INITIALIZER; +} + +CMap::~CMap( ) +{ + for(UINT i=0;iConfig.SpawnType == 0) + { + for(UINT i = 0; i < MobGroupList.size(); i++) + { + CMobGroup *thisgroup = MobGroupList.at(i); + for (UINT j = 0; j < thisgroup->basicMobs.size(); j++) + delete thisgroup->basicMobs.at(j); + for (UINT j = 0; j < thisgroup->tacMobs.size(); j++) + delete thisgroup->tacMobs.at(j); + delete thisgroup; + } + } + for(UINT i=0;iClearClientID( player->clientid ); + if(clearobject) + { + BEGINPACKET( pak, 0x794 ); + ADDWORD ( pak, player->clientid ); + GServer->SendToVisible( &pak, player, false ); + } + for(UINT i=0;iGetNPCDataByID( montype ); + if(thisnpc == NULL) + { + Log( MSG_WARNING, "Add Monster:: Invalid montype %u", montype ); + return NULL; + } + //if(AI == 999) Log( MSG_WARNING, "AddMonster Cast CNPCData " ); + CMonster* monster = new (nothrow) CMonster( position, montype, this->id, owner, spawnid ); + if(monster == NULL) + { + Log( MSG_WARNING, "Add Monster %u:: Error allocing memory",montype); + return NULL; + } + //if(AI == 999) Log( MSG_WARNING, "AddMonster Cast CMonster " ); + monster->thisnpc = thisnpc; + monster->Stats->MaxHP = monster->GetMaxHP( ); + monster->Stats->HP = monster->Stats->MaxHP; + monster->Stats->MaxMP = monster->GetMaxMP( ); + monster->Stats->MP = monster->Stats->MaxMP; + monster->thisnpc->_EntityType = ENTITY_MONSTER; + monster->Status->Stance = 0; // WTF is Status->Stance even used for???? + monster->Stats->stance = 0; //mWalking + monster->Status->spawnid = spawnid; //this way we can easily find which spawn a mob belongs to + monster->SetStats( ); + //if(AI == 999) Log( MSG_WARNING, "AddMonster Stats set " ); + if(AI != 0 && monster->monAI != 999) + { + //Set monster's AI to the defined value + //if(AI == 999) Log( MSG_WARNING, "AddMonster Setting monAI " ); + monster->monAI = AI; + CAip* script = NULL; + //if(AI == 999) Log( MSG_WARNING, "AddMonster Created CAip script " ); + if(monster->monAI == 999) AI = 30; //should create an AIP script that does nothing + for(unsigned j=0; j < GServer->AipList.size(); j++) + { + if (GServer->AipList.at(j)->AInumber == AI) + { + script = GServer->AipList.at(j); + break; + } + } + //if(monster->monAI == 999) Log( MSG_WARNING, "AddMonster finished scanning the AIPList " ); + if(script == NULL && monster->monAI != 999) + { + Log( MSG_WARNING, "Invalid AI script for monster type %i using AI %i", montype,monster->monAI ); + monster->AItimer = 4000; + } + else + { + //Log( MSG_WARNING, "Setting timer for monster type %i using AI %i", montype,monster->monAI ); + if(monster->monAI == 999) + { + monster->AItimer = 4000; + } + else + { + monster->AItimer = script->minTime * 1000; //set AI timer value for this monster + } + //Log( MSG_WARNING, "Set the timer from the script" ); + } + if (monster->AItimer == 0) + monster->AItimer = 4000; + //Log( MSG_WARNING, "AddMonster end of main clause " ); + } + else + { + //Set monster's AI to the default value for this monster type + monster->monAI = monster->thisnpc->AI; + CAip* script = NULL; + for(unsigned j=0; j < GServer->AipList.size(); j++) + { + if (GServer->AipList.at(j)->AInumber == monster->monAI) + { + script = GServer->AipList.at(j); + break; + } + } + if(script == NULL && monster->monAI != 999) //don't give error messag eif it's 999 + { + //Log( MSG_WARNING, "Invalid AI script for monster type %i using AI %i", montype,AI ); + monster->AItimer = 4000; + } + else + { + monster->AItimer = script->minTime * 1000; //set AI timer value for this monster + } + if (monster->AItimer == 0) + monster->AItimer = 4000; + //if(AI == 999) Log( MSG_WARNING, "AddMonster end of ELSE clause " ); + } + if(IsTD) + { + + //monster->thisnpc->AI = 331; + monster->monAI = 999; + monster->thisnpc->aggresive = 0; + monster->NextWayPoint = 1; + monster->Position->destiny = monster->Position->current; + monster->AItimer = 1000; //shorter testing time because it doesn't run an AIP + } + + if(limit == 0) + { + monster->Status->dayonly = false; + monster->Status->nightonly = false; + } + if(limit == 1) + { + monster->Status->dayonly = true; + monster->Status->nightonly = false; + } + if(limit == 2) + { + monster->Status->dayonly = false; + monster->Status->nightonly = true; + } + if(aggro != 1) + monster->thisnpc->aggresive = aggro; + monster->Stats->HP = monster->Stats->MaxHP; + /* BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x8005 ); + GServer->SendToVisible( &pak, monster);*/ + MonsterList.push_back( monster ); + if(AI == 999) Log( MSG_WARNING, "Added to monster list " ); + if(spawnid != 0) + { + if(GServer->Config.SpawnType == 1) + { + CSpawnArea* thisspawn = GServer->GetSpawnArea( spawnid, this->id ); + if(thisspawn!=NULL) + thisspawn->amon++; + } + if(GServer->Config.SpawnType == 0) + { + CMobGroup* thisgroup = GServer->GetMobGroup( spawnid, this->id ); + if(thisgroup != NULL) + thisgroup->active++; + } + } + monster->activecycle = GServer->RandNumber(1, 10); + monster->SpawnTime = clock( ); + monster->lastAiUpdate = clock(); + monster->hitcount = 0xFF; + //monster->OnSpawn( false ); + monster->hitcount = 0; + return monster; +} + + +// Delete a monster +bool CMap::DeleteMonster( CMonster* monster, bool clearobject, UINT i ) +{ + if(monster == NULL) return false; + GServer->ClearClientID( monster->clientid ); + if(monster->Position->respawn != 0) + { + if(GServer->Config.SpawnType == 1) + { + CSpawnArea* thisspawn = GServer->GetSpawnArea( monster->Position->respawn, monster->Position->Map ); + if(thisspawn != NULL) + { + if(thisspawn->amon >= thisspawn->max)// reset spawn timer if the spawn is full + thisspawn->lastRespawnTime = clock(); + thisspawn->amon--; + UpdateSpawnCounter( thisspawn->montype, thisspawn->id, 2); + } + } + if(GServer->Config.SpawnType == 0) + { + CMobGroup* thisgroup = GServer->GetMobGroup( monster->Position->respawn, monster->Position->Map ); + if(thisgroup!=NULL) + { + if(thisgroup->active >= thisgroup->limit)// reset spawn timer if the spawn is full + thisgroup->lastRespawnTime = clock(); + thisgroup->active--; + thisgroup->basicKills++; + } + } + } + if(clearobject) // is this for clearing Summons? + { + //Log( MSG_INFO, "Deleting monster %i (clearobject)",monster->montype); + BEGINPACKET( pak, 0x799 ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, monster->clientid ); + ADDWORD ( pak, 0x8005 ); + //ADDDWORD ( pak, monster->Stats->HP ); + //ADDDWORD ( pak, 16 ); + GServer->SendToVisible( &pak, monster ); + } + if(i != 0) + { + //Log( MSG_INFO, "Deleting monster %i from monster list. montype: %i",i,monster->montype); + MonsterList.erase( MonsterList.begin()+i ); + //Log( MSG_INFO, "erased from list"); + delete monster; + //Log( MSG_INFO, "deleted"); + return true; + } + for(UINT j=0;jmontype); + MonsterList.erase( MonsterList.begin() + j ); + delete monster; + return true; + } + } + //Log( MSG_INFO, "Deleting monster %i from monster list anyway",monster->montype); + delete monster; + return false; +} + +//add a new drop +bool CMap::AddDrop( CDrop* drop ) +{ + drop->droptime = time(NULL); + DropsList.push_back( drop ); + return true; +} + +bool CMap::DeleteDrop( CDrop* drop ) +{ + BEGINPACKET( pak, 0x794 ); + ADDWORD ( pak, drop->clientid ); + GServer->SendToVisible( &pak, drop ); + GServer->ClearClientID( drop->clientid ); + for(UINT j=0;jclientid ); + GServer->SendToMap( &pak, npc->posMap ); + GServer->ClearClientID( npc->clientid ); + Log(MSG_INFO,"Sent message to players in map to delete NPC"); + for(UINT j=0;j RespawnList; // RespawnZones List + vector MonsterSpawnList; // Monster spawn in this map +#ifdef USEIFO + vector MobGroupList; // Spawn "Zones" +#endif + vector MonsterList; // Monster List in this map + vector DropsList; // Droped Items + vector PlayerList; // Client list in this map; + vector NPCList; // NPC in this map + vector TeleGateList; // Telegates from this map + std::vector PointList; + + pthread_mutex_t MonsterMutex, DropMutex; +}; + +#endif diff --git a/Src/World Server/worldmap.h.bak b/Src/World Server/worldmap.h.bak new file mode 100644 index 0000000..2386ab2 --- /dev/null +++ b/Src/World Server/worldmap.h.bak @@ -0,0 +1,119 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + depeloped with Main erose/hrose source server + some change from the original eich source +*/ +#ifndef __ROSE_MAP__ +#define __ROSE_MAP__ +#include "worldserver.h" +#include "player.h" +#include "character.h" +#include "worldmonster.h" +#include "datatypes.h" +class CMonster; + +class CMap +{ + public: + CMap( ); + ~CMap( ); + + bool AddPlayer( CPlayer* player ); + bool RemovePlayer( CPlayer* player, bool clearobject=true ); + + //CMonster* AddMonster( UINT montype, fPoint position, UINT owner=0, UINT spawnid=0, bool GetDropData=false ); + CMonster* AddMonster( UINT montype, fPoint position, UINT owner=0, UINT spawnid=0, UINT aggro = 0, bool IsTD = false, UINT limit = 0, UINT AI = 0); + bool DeleteMonster( CMonster*, bool clearobject=false, UINT i=0 ); + bool AddDrop( CDrop* drop ); + bool DeleteDrop( CDrop* drop ); + bool AddNPC( CNPC* npc ); + bool DeleteNPC( CNPC* npc ); + + CMonster* ConverToMonster( CMonster* monster, UINT newmonttype, bool heal=true ); + CMonster* ConverToMonster( CNPC* npc, UINT newmonttype ); + + bool TeleportPlayer( CPlayer* player, fPoint coord, bool TelePassenger=true ); + bool pakTeleport( CPlayer* player, unsigned int Map, fPoint coord, bool TelePassenger ); + + CRespawnPoint* GetNearRespawn( CPlayer* player ); + CRespawnPoint* GetFirstRespawn( ); + CRespawnPoint* GetStartRespawn( ); + + CCharacter* GetCharInMap( unsigned int id ); + CPlayer* GetPlayerInMap( UINT id ); + CMonster* GetMonsterInMap( UINT id ); + CPlayer* GetCharIDInMap( UINT id ); + CDrop* GetDropInMap( UINT id ); + CNPC* GetNPCInMap( UINT id ); + void UpdateTime( ); + void CleanDrops( ); + void RespawnMonster( ); + void UpdateSpawnCounter( UINT montype, UINT spawnid, UINT key ); + bool IsNight( ); + + void LoadZon(strings zon); + void LoadData(strings zonpath, CStrStb* stbEvent); + void AddIfo(strings ifo, CStrStb* stbEvent); + + UINT id; // id from map + UINT dayperiod; // duration of 1 day + UINT morningtime; // morning start time + UINT daytime; // day start time + UINT eveningtime; // evening start time + UINT nighttime; // nighttime start time + UINT ZoneTime; // how many 6 minute segments since start of the day + UINT ghost; // ghosttime? + BYTE allowpvp; // pvp allowed? + bool dungeon; // Map is a dungeon? + bool allowpat; // pat allowed? + // Time values + UINT MapTime; // Past time in this map + UINT Date; + UINT hour; + UINT minute; + UINT ObjVar[20]; //for storing map variables used in AIP + clock_t LastUpdate;// Last time update in the map + UINT CurrentTime; // 0 = morning | 1 = day | 2 = evening | 3 = night | 4+ = invalid + //TDMobList + UINT TDMobList[100]; //List of TD monsters waiting to be spawned + UINT TDNextSpawn; //index pointer for next spawn in TDMobList + UINT TDNextMon; //index pointer for next addition to TDMobList + clock_t lastTDSpawnTime; //time of the last spawn + UINT TDMobDelay; // delay between spawns (ms) + UINT crystalhealth; // current health of the base crystal + UINT portalhealth; // current health of the spawn portal + UINT mapXPRate; // exp rate for this map + + + + vector RespawnList; // RespawnZones List + vector MonsterSpawnList; // Monster spawn in this map +#ifdef USEIFO + vector MobGroupList; // Spawn "Zones" +#endif + vector MonsterList; // Monster List in this map + vector DropsList; // Droped Items + vector PlayerList; // Client list in this map; + vector NPCList; // NPC in this map + vector TeleGateList; // Telegates from this map + std::vector PointList; + + pthread_mutex_t MonsterMutex, DropMutex; +}; + +#endif diff --git a/Src/World Server/worldpackets.cpp b/Src/World Server/worldpackets.cpp new file mode 100644 index 0000000..d495295 --- /dev/null +++ b/Src/World Server/worldpackets.cpp @@ -0,0 +1,4598 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Send Characters information +void CWorldServer::pakPlayer( CPlayer *thisclient ) +{ + unsigned cbyte = 0; + CRespawnPoint* thisrespawn = GetRespawnByID( thisclient->Position->respawn ); + if(thisclient->Position->saved == 20&&thisrespawn->destMap!=20) + thisclient->Position->saved = 22; + if(thisrespawn != NULL) + { + thisclient->Position->destiny = thisrespawn->dest; + thisclient->Position->current = thisclient->Position->destiny; + //Log(MSG_DEBUG,"(pakPlayer) Destiny and current set to X: %f Y: %f.",thisclient->Position->current.x,thisclient->Position->current.y); + thisclient->Position->Map = thisrespawn->destMap; + } + else + { + thisclient->Position->destiny.x = 5300; + thisclient->Position->destiny.y = 5200; + thisclient->Position->current = thisclient->Position->destiny; + thisclient->Position->Map = 1; + } + CMap* map = MapList.Index[thisclient->Position->Map]; + map->AddPlayer( thisclient ); + BEGINPACKET(pak, 0x715); + ADDBYTE( pak, thisclient->CharInfo->Sex ); // Sex + ADDWORD( pak, thisclient->Position->Map ); // Map + ADDFLOAT( pak, thisclient->Position->current.x*100 ); // Pos X + ADDFLOAT( pak, thisclient->Position->current.y*100 ); // Pos Y + ADDWORD( pak, thisclient->Position->saved ); + ADDDWORD( pak, thisclient->CharInfo->Face ); // Face + ADDDWORD( pak, thisclient->CharInfo->Hair ); // Hair + ADDDWORD( pak, BuildItemShow(thisclient->items[2]) ); // Cap + ADDDWORD( pak, BuildItemShow(thisclient->items[3]) ); // Body + ADDDWORD( pak, BuildItemShow(thisclient->items[5]) ); // Gloves + ADDDWORD( pak, BuildItemShow(thisclient->items[6]) ); // Boots + ADDDWORD( pak, BuildItemShow(thisclient->items[1]) ); // Face + ADDDWORD( pak, BuildItemShow(thisclient->items[4]) ); // Back + ADDDWORD( pak, BuildItemShow(thisclient->items[7]) ); // Weapon + ADDDWORD( pak, BuildItemShow(thisclient->items[8]) ); // SubWeapon + ADDBYTE( pak, 0x01 ); // Birth Stone + ADDBYTE( pak, 0x00 ); + ADDBYTE( pak, 0x00 ); + ADDWORD( pak, thisclient->CharInfo->Job ); // Job + ADDBYTE( pak, thisclient->Union_s->unionvar[0]); // Union Variable [0] + ADDBYTE( pak, 0x00 ); //rank + ADDBYTE( pak, 0x00 ); //fame + ADDWORD( pak, thisclient->Attr->Str ); // Str + ADDWORD( pak, thisclient->Attr->Dex ); // Dex + ADDWORD( pak, thisclient->Attr->Int ); // Int + ADDWORD( pak, thisclient->Attr->Con ); // Con + ADDWORD( pak, thisclient->Attr->Cha ); // Cha + ADDWORD( pak, thisclient->Attr->Sen ); // Sen + ADDWORD( pak, thisclient->Stats->HP ); // Current HP + ADDWORD( pak, thisclient->Stats->MP ); // Current MP + ADDWORD( pak, thisclient->CharInfo->Exp ); // Exp + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, thisclient->Stats->Level ); // Level + ADDWORD( pak, thisclient->CharInfo->StatPoints ); // Stat Points + ADDWORD( pak, thisclient->CharInfo->SkillPoints ); // Skill Points + ADDBYTE( pak, 0x64 ); // Body Size + ADDBYTE( pak, 0x64 ); // Head Size + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, thisclient->Union_s->unionvar[1] );//union points Junon Order + ADDWORD( pak, thisclient->Union_s->unionvar[2] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[3] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[4] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[5] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[6] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[7] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[8] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[9] );//union points + ADDWORD( pak, thisclient->Union_s->unionvar[10] );//union point + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDBYTE( pak, 0x00 ); + ADDWORD( pak, thisclient->CharInfo->stamina ); // Stamina + + for(short i=0; i<326; i++) + ADDBYTE( pak, 0x00 ); + for (char i=0; i<30; i++) + ADDWORD( pak, thisclient->bskills[i] );//Basic skills + for (char i=0; i<30; i++) + ADDWORD( pak, thisclient->askill[i]+thisclient->askilllvl[i] );//Active skills + for (char i=0; i<30; i++) + ADDWORD( pak, thisclient->pskill[i]+thisclient->pskilllvl[i] );//Passive skills + for(short i=0; i<60; i++)//unused ???? + ADDBYTE( pak, 0 ); + for (char i=0; i<32; i++) + ADDWORD( pak, thisclient->quickbar[i] ); // QUICKBAR ROW 1 STARTS + ADDWORD( pak, thisclient->CharInfo->charid ); + ADDWORD( pak, 0x00 ); + for(unsigned j = 0; j < strlen(thisclient->CharInfo->charname); j++) + { + ADDBYTE(pak, thisclient->CharInfo->charname[j] ); + } + + ADDBYTE( pak, 0x00 ); + ADDBYTE( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); +} + +// Send Inventory Information +void CWorldServer::pakInventory( CPlayer *thisclient ) +{ + unsigned cbyte=0; + STARTPACKET(pak, 0x716, 854 ); + SETQWORD( pak, 0x000, thisclient->CharInfo->Zulies ); + cbyte += 8; + for(unsigned j=0; j<140; j++) + { + //Log(MSG_INFO,"slot: %i itemid: %i type: %i count: %i",j, thisclient->items[j].itemnum,thisclient->items[j].itemtype,thisclient->items[j].count); + SETWORD( pak, cbyte+0, BuildItemHead( thisclient->items[j] ) ); + SETDWORD( pak, cbyte+2, BuildItemData( thisclient->items[j] ) ); + cbyte += 6; + } + cbyte-= 6; + thisclient->client->SendPacket( &pak ); +} + +// Send Quest information +void CWorldServer::pakQuestData( CPlayer *thisclient ) +{ + //Log( MSG_DEBUG, "Starting pakQuestData"); + CPacket pakout; + //Log( MSG_DEBUG, "creating packet"); + pakout.StartPacket( 0x71b ); + //Log( MSG_DEBUG, "Starting packet"); + for(dword i = 0; i < 5; i++) pakout.AddWord(thisclient->quest.EpisodeVar[i]); + //Log( MSG_DEBUG, "added episode vars"); + for(dword i = 0; i < 3; i++) pakout.AddWord(thisclient->quest.JobVar[i]); + //Log( MSG_DEBUG, "added job vars"); + for(dword i = 0; i < 7; i++) pakout.AddWord(thisclient->quest.PlanetVar[i]); + //Log( MSG_DEBUG, "added planet vars"); + for(dword i = 0; i < 10; i++) pakout.AddWord(thisclient->quest.UnionVar[i]); + //Log( MSG_DEBUG, "added union vars"); + for( unsigned i = 0; i < 10; i++ ) + { + //Log( MSG_DEBUG, "starting main loop %i",i); + pakout.AddWord( thisclient->quest.quests[i].QuestID ); + //Log( MSG_DEBUG, "added quest ID"); + long int Time = 0; + //Log( MSG_DEBUG, "Initialized Time"); + //Log( MSG_DEBUG, "This Quest quest id = %i",thisclient->quest.quests[i].QuestID); + //Log( MSG_DEBUG, "This Quest STB quest id = %i",STB_QUEST.rows[thisclient->quest.quests[i].QuestID][1]); + if (thisclient->quest.quests[i].QuestID > 0 && STB_QUEST.rows[thisclient->quest.quests[i].QuestID][1] > 0) + { + //Log( MSG_DEBUG, "client has a timed quest"); + Time += thisclient->quest.quests[i].StartTime; // Start time + Time += STB_QUEST.rows[thisclient->quest.quests[i].QuestID][1] * 10; // Time to finish + Time -= time(NULL); // Current time + if (Time < 0) Time = 0; // Time is up + Time /= 10; // Divide to get 10's of seconds + //Log( MSG_DEBUG, "Timed variables set %i",Time); + } + //Log( MSG_DEBUG, "end of time quest conditional"); + pakout.AddDWord( Time ); // Time Left + //Log( MSG_DEBUG, "Time added"); + + for(dword j = 0; j < 10; j++) pakout.AddWord(thisclient->quest.quests[i].Variables[j]); + //Log( MSG_DEBUG, "added quest vars"); + for(dword j = 0; j < 4; j++) pakout.AddByte(thisclient->quest.quests[i].Switches[j]); + //Log( MSG_DEBUG, "added quest switches"); + for(dword j = 0; j < 6; j++) + { + if (thisclient->quest.quests[i].Items[j].itemnum != 0) + { + pakout.AddWord( thisclient->quest.quests[i].Items[j].GetPakHeader() ); + pakout.AddDWord( thisclient->quest.quests[i].Items[j].GetPakData() ); + } + else + { + pakout.AddWord( 0 ); + pakout.AddDWord( 0 ); + } + } + } + //Log( MSG_DEBUG, "finished main quest loop"); + // testing out new PY quest flag method + for( unsigned i = 0; i < 0x40; i++ ) + { + byte tempval = 0; + for(int j=0;j<8;j++) + { + tempval += (byte)thisclient->quest.Qflags[i][j] * (int)pow(2.0,(double)j); + } + pakout.AddByte(tempval); + //pakout.AddByte( thisclient->quest.flags[i] ); + } + //Log( MSG_DEBUG, "finished quest flags"); + // Wishlist? - Not Implemented + for (unsigned i = 0; i < 180; i++) + pakout.AddByte( 0 ); + //Log( MSG_DEBUG, "Finished loading all the stuff into pakout"); + thisclient->client->SendPacket( &pakout ); +} + +// Handle quest triggers +bool CWorldServer::pakQuestTrigger( CPlayer* thisclient, CPacket* pak ) +{ + byte action = pak->GetByte( 0 ); + // action 1 = send text + // action 2 = delete quest + // action 3 = get quest + // action 4 = unknown + // action 5 = success + + byte slot = pak->GetByte( 1 ); + dword hash = pak->GetDWord( 2 ); + + if( thisclient->questdebug ) SendPM( thisclient, "Event Trigger recieved [%08x] Action %i", hash, action); + + if (action == 2) + { + if( thisclient->questdebug ) SendPM( thisclient, "Delete quest - Slot %i", slot); + for (dword i = slot; i < 9; i++) thisclient->quest.quests[i] = thisclient->quest.quests[i+1]; + thisclient->quest.quests[9].Clear(); + CPacket pakout(0x730); + pakout.AddByte(0x03); + pakout.AddByte(slot); + pakout.AddDWord(hash); + thisclient->client->SendPacket(&pakout); + return true; + } + if (action != 3) return false; + int success = thisclient->ExecuteQuestTrigger(hash); + CPacket pakout(0x730); + pakout.AddByte(success); + pakout.AddByte(0); + pakout.AddDWord(hash); + thisclient->client->SendPacket(&pakout); + return true; +} + +// Get this user set up with the encryption and stuff +bool CWorldServer::pakDoIdentify( CPlayer *thisclient, CPacket *P ) +{ + MYSQL_ROW row; + thisclient->Session->userid = GETDWORD((*P), 0); + memcpy( thisclient->Session->password, &P->Buffer[4], 32 ); + MYSQL_RES *result = DB->QStore("SELECT username,lastchar,accesslevel,zulystorage,ktpoints,newpoints,logtime,totlogtime FROM accounts WHERE id=%i AND password='%s'", thisclient->Session->userid, thisclient->Session->password); + if(result==NULL) return false; + if (mysql_num_rows( result ) != 1) + { + Log( MSG_HACK, "Someone tried to connect to world server with an invalid account" ); + DB->QFree( ); + return false; + } + Log( MSG_DEBUG, "Loading Session Data" ); + row = mysql_fetch_row(result); + strncpy_s( thisclient->Session->username, row[0],16 ); + strncpy_s( thisclient->CharInfo->charname, row[1],16 ); + thisclient->Session->accesslevel = atoi(row[2]); + thisclient->CharInfo->Storage_Zulies = atoi( row[3] ); + thisclient->Session->KTPoints = atoi(row[4]); //load KTPoints + thisclient->Session->NewPoints = atoi(row[5]); //load newPoints. This is where additional KTPoints are added when they are purchased + thisclient->Session->logtime = atoi(row[6]); //current log time in hours. + thisclient->Session->TotalLogTime = atoi(row[7]); //total log time for this account + thisclient->Session->award = 0; + thisclient->Session->RespawnChoice = 0; + thisclient->Session->Respawned = 0; + thisclient->Session->inGame = true; + DB->QFree( ); + if(thisclient->Session->NewPoints != 0) + { + // Add NewPoints to KTPoints + //Log( MSG_DEBUG, "Loaded up some new KTPoints and stuff" ); + thisclient->Session->KTPoints += thisclient->Session->NewPoints; + // Set newpoints to zero both here and in the database + thisclient->Session->NewPoints = 0; + GServer->DB->QExecute("UPDATE accounts SET newpoints=%i, ktpoints=%i WHERE id=%i AND password='%s'", + thisclient->Session->NewPoints,thisclient->Session->KTPoints,thisclient->Session->userid, thisclient->Session->password); + //GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + // thisclient->Session->KTPoints,thisclient->Session->userid, thisclient->Session->password); + } + if (thisclient->Session->logtime >= Config.KTPointRate) //make the KTPoint reward system variable + { + while(thisclient->Session->logtime > Config.KTPointRate) + { + thisclient->Session->logtime -= Config.KTPointRate; + thisclient->Session->award ++; + } + thisclient->Session->KTPoints += thisclient->Session->award; + //thisclient->Union_s->unionvar[9] = thisclient->Session->KTPoints; + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i,logtime=%i WHERE id=%i AND password='%s'", + thisclient->Session->KTPoints, thisclient->Session->logtime, thisclient->Session->userid, thisclient->Session->password); + } + + if(!thisclient->loaddata( )) return false; + Log( MSG_INFO, "User '%s'(#%i) logged in with character '%s'", thisclient->Session->username, thisclient->Session->userid, thisclient->CharInfo->charname); + BEGINPACKET( pak, 0x070c ); + ADDBYTE ( pak, 0 ); + ADDDWORD ( pak, 0x87654321 ); + ADDDWORD ( pak, 0x00000000 ); + thisclient->client->SendPacket( &pak ); + pakPlayer(thisclient); + //Log( MSG_DEBUG, "ran pakPlayer"); + pakInventory(thisclient); + //Log( MSG_DEBUG, "ran pakInventory"); + pakQuestData(thisclient); + //Log( MSG_DEBUG, "ran pakQuestData"); + + RESETPACKET( pak, 0x7de ); + ADDWORD ( pak, 0x1001 ); // 0x1001 to 0x1013 (game plan?) + ADDDWORD ( pak, 2 ); // options (plan time?) [2 = unlimited] + thisclient->client->SendPacket( &pak ); + + // Welcome message to the server + RESETPACKET( pak, 0x702 ); + ADDSTRING ( pak, Config.WELCOME_MSG ); + ADDBYTE ( pak, 0 ); + thisclient->client->SendPacket( &pak ); + SendSysMsg( thisclient, "Pre-Evolution server presented by SpiritFox productions and Kuro-Tejina " ); + thisclient->SetStats( ); + + if(thisclient->Session->award != 0) + { + char buffer[200]; + sprintf ( buffer, "You have been awarded %i KTPoints for time logged into the server",thisclient->Session->award); + SendSysMsg(thisclient, buffer); + + } + return true; + +} + + +// Give the user an ID +bool CWorldServer::pakDoID( CPlayer* thisclient, CPacket* P ) +{ + // Assign a new id to this person + thisclient->clientid = GetNewClientID(); + if (thisclient->clientid <= 0) + { + Log( MSG_WARNING, "User '%s'(#%i) denied access. Server is full.", thisclient->Session->username, thisclient->Session->userid ); + return false; + } + Log( MSG_INFO, "User '%s'(#%i) assigned id #%i", thisclient->Session->username, thisclient->Session->userid, thisclient->clientid ); + //Log( MSG_DEBUG, "User Session Ingame = %i", thisclient->Session->inGame); + //thisclient->Session->inGame = true; + if( thisclient->Party->party ) + { + BEGINPACKET( pak, 0x7d5 ); + ADDDWORD ( pak, thisclient->CharInfo->charid ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisclient->GetMaxHP( ) ); + ADDWORD ( pak, thisclient->Stats->HP ); + ADDDWORD ( pak, 0x01000000 ); + ADDDWORD ( pak, 0x0000000f ); + ADDWORD ( pak, 0x1388 ); + thisclient->Party->party->SendToMembers( &pak, thisclient ); + } + BEGINPACKET( pak, 0x721 ); + ADDWORD ( pak, 0x0022 ); + ADDWORD ( pak, 0x0002 ); + ADDWORD ( pak, 0x0000 ); + thisclient->client->SendPacket( &pak ); + //Log( MSG_DEBUG, "Packet 0x721 sent"); + RESETPACKET( pak, 0x753 ); + ADDWORD ( pak, thisclient->clientid ); // USER ID + //Log( MSG_DEBUG, "User ID added"); + ADDWORD ( pak, thisclient->Stats->HP ); // CURRENT HP + //Log( MSG_DEBUG, "Current HP added"); + ADDWORD ( pak, thisclient->Stats->MP ); // CURRENT MP + //Log( MSG_DEBUG, "Current MP added"); + ADDDWORD ( pak, thisclient->CharInfo->Exp ); // CURRENT EXP + ADDDWORD ( pak, 0x00000000 ); // LVL EXP (UNSUSED) + //Log( MSG_DEBUG, "Current XP added"); + //Log( MSG_DEBUG, "Basic info added"); + // thanks to StrikeX to post this source + //[economy] + /* + ADDWORD ( pak, 0x0063 ); // World Rate + ADDBYTE ( pak, 0x70 ); + ADDBYTE ( pak, 0x69 ); + ADDBYTE ( pak, 0x68 ); + ADDBYTE ( pak, 0x67 ); + ADDWORD ( pak, 0x0062 ); // Town rate + ADDBYTE ( pak, 0x61 ); // misc rate + ADDBYTE ( pak, 0x32 ); //1 + ADDBYTE ( pak, 0x32 ); //2 + ADDBYTE ( pak, 0x32 ); //3 + ADDBYTE ( pak, 0x32 ); //4 + ADDBYTE ( pak, 0x32 ); //5 + ADDBYTE ( pak, 0x32 ); //6 + ADDBYTE ( pak, 0x32 ); //7 + ADDBYTE ( pak, 0x32 ); //8 + ADDBYTE ( pak, 0x32 ); //9 + ADDBYTE ( pak, 0x32 ); //10 + ADDBYTE ( pak, 0x32 ); //11 + */ + ADDWORD ( pak, 0x0064 ); //world rate + ADDDWORD ( pak, 0x0C1F4B79);//0x1841f50b ); + ADDWORD ( pak, 0x0064 ); //town rate + ADDDWORD ( pak, 0x3232cd50 ); + ADDDWORD ( pak, 0x32323235 ); + ADDDWORD ( pak, 0x35323232 ); + //Log( MSG_DEBUG, "rates added"); + CMap* map = MapList.Index[thisclient->Position->Map]; + //Log( MSG_DEBUG, "position added"); + if(map->allowpvp!=0) + ADDWORD(pak, 0x0001)//player vs player map + else + ADDWORD(pak, 0x0000)//non player vs player map + ADDWORD (pak, 0x0000 );//?? + // Map Time + ADDDWORD( pak, map->MapTime ); + //ADDBYTE ( pak, 0xde );// (new) + //ADDWORD ( pak, 0x0008 );//this change something in the mapicon (time) (new) + if(map->allowpvp==1) + { + ADDWORD(pak, 0x0000 );//red icon (map) + ADDWORD(pak, 0x0005 );//red icon (map) + } // pvp all vs all + else if(map->allowpvp==2) // pvp group vs group + { + /*ADDWORD(pak, thisclient->Clan->clanid );*/ + ADDWORD(pak, 51); + } + else + { + ADDWORD(pak, 0x0002 );//white icon (map) + ADDWORD(pak, 0x0000 );//white icon (map) + } + thisclient->client->SendPacket( &pak ); + //Log( MSG_DEBUG, "Sent 0x753 packet"); + // Sort out weight stuff, make user not able to run if overweight + int weight = ( thisclient->GetCurrentWeight() / thisclient->GetMaxWeight() ) * 100; + if(weight>110) + thisclient->Status->CanAttack = false; + else + thisclient->Status->CanAttack = true; + if(weight>100) + thisclient->Status->CanRun = false; + else + thisclient->Status->CanRun = true; + + thisclient->Stats->Move_Speed = thisclient->GetMoveSpeed( ); + RESETPACKET( pak, 0x762 ); + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, weight ); + thisclient->client->SendPacket( &pak ); + // set speed + RESETPACKET(pak, 0x782 ); + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, thisclient->Status->Stance ); + ADDWORD ( pak, thisclient->Stats->Mspd_base ); + SendToVisible( &pak, thisclient ); + thisclient->CleanPlayerVector( ); + thisclient->Session->inGame = true; + thisclient->Session->Respawned = 0; + //Log( MSG_DEBUG, "Completed DoID"); + return true; +} + +// Move Characters in map +bool CWorldServer::pakMoveChar( CPlayer* thisclient, CPacket* P ) +{ + if( thisclient->Shop->open || (!thisclient->Ride->Drive && thisclient->Ride->Ride) || !thisclient->Status->CanMove ) + return true; + thisclient->UpdatePosition( ); + thisclient->Position->lastMoveTime = clock(); + if( thisclient->Status->Stance == SITTING ) + thisclient->Status->Stance = RUNNING; + ClearBattle( thisclient->Battle ); + thisclient->Battle->target = GETWORD((*P), 0x00 ); + thisclient->Position->destiny.x = GETFLOAT((*P), 0x02 )/100; + thisclient->Position->destiny.y = GETFLOAT((*P), 0x06 )/100; + if(thisclient->Ride->Drive) //update the destination for anyone riding in the back of my cart + { + CPlayer* otherclient = GServer->GetClientByCID( thisclient->Ride->charid ); + if(otherclient != NULL) + { + otherclient->Stats->Move_Speed = thisclient->Stats->Move_Speed; + otherclient->Position->destiny.x = GETFLOAT((*P), 0x02 )/100; + otherclient->Position->destiny.y = GETFLOAT((*P), 0x06 )/100; + otherclient->UpdatePosition( ); + //hmm not sending an update packet regarding otherclient are we? Do we need a packet here? + BEGINPACKET( pak1, 0x79a ); + ADDWORD ( pak1, otherclient->clientid ); // USER ID + ADDWORD ( pak1, otherclient->Battle->target ); // TARGET + //LMA: it's in fact the distance from current to destiny and not the base speed... + ADDWORD ( pak1, (DWORD) (distance(otherclient->Position->current,otherclient->Position->destiny)*100)); + //ADDWORD ( pak, thisclient->Stats->Move_Speed ); // MSPEED + ADDFLOAT ( pak1, GETFLOAT((*P), 0x02 ) ); // POSITION X + ADDFLOAT ( pak1, GETFLOAT((*P), 0x06 ) ); // POSITION Y + ADDWORD ( pak1, GETWORD((*P), 0x0a ) ); // POSITION Z (NOT USED) + SendToVisible( &pak1, otherclient ); + } + } + BEGINPACKET( pak, 0x79a ); + ADDWORD ( pak, thisclient->clientid ); // USER ID + ADDWORD ( pak, thisclient->Battle->target ); // TARGET + //LMA: it's in fact the distance from current to destiny and not the base speed... + ADDWORD ( pak, (DWORD) (distance(thisclient->Position->current,thisclient->Position->destiny)*100)); + //ADDWORD ( pak, thisclient->Stats->Move_Speed ); // MSPEED + ADDFLOAT ( pak, GETFLOAT((*P), 0x02 ) ); // POSITION X + ADDFLOAT ( pak, GETFLOAT((*P), 0x06 ) ); // POSITION Y + ADDWORD ( pak, GETWORD((*P), 0x0a ) ); // POSITION Z (NOT USED) + SendToVisible( &pak, thisclient ); + //Log(MSG_DEBUG,"Recieved move command and set destiny to X: %f Y: %f.",thisclient->Position->destiny.x,thisclient->Position->destiny.y); + return true; +} + +// Stop Character movement +bool CWorldServer::pakStopChar( CPlayer* thisclient, CPacket* P ) +{ + ClearBattle( thisclient->Battle ); + thisclient->Position->destiny.x = GETFLOAT((*P), 0x00 )/100; + thisclient->Position->destiny.y = GETFLOAT((*P), 0x04 )/100; + thisclient->Position->current=thisclient->Position->destiny; + BEGINPACKET( pak, 0x770 ); + ADDWORD ( pak, thisclient->clientid ); // USER ID + ADDFLOAT ( pak, thisclient->Position->destiny.x*100 ); // POSITION X + ADDFLOAT ( pak, thisclient->Position->destiny.y*100 ); // POSITION Y + ADDWORD ( pak, GETWORD((*P), 0x08 ) ); // POSITION Z (NOT USED) + SendToVisible( &pak, thisclient ); + return true; +} + +// Spawn NPC +bool CWorldServer::pakSpawnNPC( CPlayer* thisclient, CNPC* thisnpc ) +{ + //Log(MSG_DEBUG,"spawning NPC type %i at X: %f Y: %f.",thisnpc->npctype,thisnpc->pos.x,thisnpc->pos.y); + BEGINPACKET( pak, 0x791 ); + ADDWORD( pak, thisnpc->clientid ); + ADDFLOAT( pak, thisnpc->pos.x*100 ); + ADDFLOAT( pak, thisnpc->pos.y*100 ); + ADDWORD( pak, 0xcdcd ); + ADDWORD( pak, 0xcdcd ); + ADDWORD( pak, 0xcdcd ); + ADDWORD( pak, 0xcdcd ); + ADDBYTE( pak, 0x00 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x03e8 ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x0001 ); + ADDWORD( pak, 0x0000 );//Buffs + ADDWORD( pak, 0x0000 );//Buffs + ADDWORD( pak, 0x0000 );//buffs + ADDWORD( pak, thisnpc->npctype ); + if(thisnpc->thisnpc->dialogid != 0) + { + ADDWORD( pak, thisnpc->thisnpc->dialogid ); + } + else + {// fixed by tomiz [npc dialogs fixed [still not all]] + unsigned int factor = 900; + //if (thisnpc->npctype >= 1000 && thisnpc->npctype <=1041 || thisnpc->npctype >= 1043 && thisnpc->npctype <=1084 || thisnpc->npctype >= 1086 && thisnpc->npctype <=1119 || thisnpc->npctype >= 1131 && thisnpc->npctype <=1199 || thisnpc->npctype >= 1207 && thisnpc->npctype <=1299) factor=900; + //else if (thisnpc->npctype >= 1200 && thisnpc->npctype <=1203) factor=896; // event npc + //else if (thisnpc->npctype == 1042 ) factor=899; // Storage Adventure Plain -> Fabrizio - Junon Clan Field -> Nell + //else if (thisnpc->npctype == 1081 ) factor=300; + //else if (thisnpc->npctype == 1085 ) factor=901; + //else if (thisnpc->npctype == 1205 ) factor=901; //santa + //else if (thisnpc->npctype == 1206 ) factor=1019; //Loelsch + //else if (thisnpc->npctype == 1120 ) factor=896; // dunno if that dialog are corect 1120 [Clan Clerk] Regina + //else if (thisnpc->npctype == 1204 ) factor=897; // event npc another warp to zant dialog npc 1203 [Event Info] Lucielle Fete + //else if (thisnpc->npctype == 1473 ) factor=1457; // should be ok 1473 Melendino adventure plain + //else if (thisnpc->npctype == 1752 ) factor=1528; // Clan Merchant Aliche Patt ver 141 + //else if (thisnpc->npctype >= 1122 && thisnpc->npctype <=1130) factor=905; + //else if (thisnpc->npctype == 1502 ) factor=1198; //santa + //else if (thisnpc->npctype >= 1503 && thisnpc->npctype <=1599) factor=1210; + //else if (thisnpc->npctype >= 1750 && thisnpc->npctype <=1755 || !thisnpc->npctype == 1752 ) factor=1000; //should be ok + ADDWORD( pak, thisnpc->npctype - factor ); + } + ADDFLOAT( pak, thisnpc->dir ); + //if(thisnpc->npctype == 1115) + //{ + // ADDBYTE( pak, GServer->Config.Cfmode ); // Burland Clan Field open/close + // ADDBYTE ( pak, 0 ); + //} + //else + //{ + if(thisnpc->thisnpc->eventid != 0) + { + ADDWORD (pak, thisnpc->thisnpc->eventid); + } + else + { + ADDWORD( pak, 0 ); + } + //} + thisclient->client->SendPacket( &pak ); + return true; +} + +// Changes stance +bool CWorldServer::pakChangeStance( CPlayer* thisclient, CPacket* P ) +{ + /* #define ATTACKING 1 + #define WALKING 2 + #define RUNNING 3 + #define DRIVING 4 + #define SITTING + run-walk-run stancenum == 0 + sit-stand-sit stancenum == 1 + drive-no drive-drive == 2 +*/ + if(thisclient->Shop->open) + return true; + BYTE stancenum = GETBYTE((*P),0x00); + BYTE newstancenum = stancenum; + switch (stancenum) + { + case 0: + if(thisclient->Status->Stance == RUNNING) //change 3 to 2 running to walking + { + newstancenum = 2; + thisclient->Status->Stance = WALKING; + } + else if(thisclient->Status->Stance == WALKING) // Running + { + newstancenum = 3; + thisclient->Status->Stance = RUNNING; + } + break; + case 1: + if (thisclient->Status->Stance == RUNNING||thisclient->Status->Stance == WALKING)//>Status->Stance!=DRIVING && thisclient->Status->Stance>0x01) + { + newstancenum = 1; + thisclient->Status->Stance = SITTING; + } + else if (thisclient->Status->Stance == SITTING)//(thisclient->Status->Stance<0x04) + { + newstancenum = 3; + thisclient->Status->Stance = RUNNING; + } + break; + case 2: + if (thisclient->Status->Stance==RUNNING || thisclient->Status->Stance==WALKING) //getting in a cart + {// Clean Buff + newstancenum = 4; + thisclient->Status->Stance = DRIVING; + thisclient->Ride->Ride = false; + thisclient->Ride->Drive = true; + thisclient->Ride->charid = 0; + } + else if (thisclient->Status->Stance==DRIVING) //getting out of cart + {// Clean Buff + newstancenum = 3; + thisclient->Status->Stance = RUNNING; + thisclient->Ride->Ride = false; + thisclient->Ride->Drive = false; + thisclient->Ride->charid = 0; + } + break; + default: + newstancenum = 3; + thisclient->Status->Stance = RUNNING; + break; + } +// if(!thisclient->Status->CanMove) +// thisclient->Status->Stance = RUNNING; + if (!thisclient->Status->CanRun && newstancenum == 3) { + newstancenum = 2; + thisclient->Status->Stance = WALKING; + } + thisclient->Stats->Move_Speed = thisclient->GetMoveSpeed( ); + BEGINPACKET( pak, 0x782 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, newstancenum ); + //ADDWORD( pak, thisclient->Stats->Move_Speed ); + SendToVisible( &pak, thisclient ); + + //Log( MSG_INFO, "Stancenum %i newstancenum %i Stance %i", stancenum, newstancenum, thisclient->Status->Stance); + return true; +} + +// Spawn Drop +bool CWorldServer::pakSpawnDrop( CPlayer* thisclient, CDrop* thisdrop ) +{ + BEGINPACKET( pak, 0x7a6 ); + ADDFLOAT( pak, thisdrop->pos.x*100 ); + ADDFLOAT( pak, thisdrop->pos.y*100 ); + if (thisdrop->type==1) + { + // -- ZULY -- + ADDWORD( pak, 0xccdf );//0xccccccdf ); + ADDDWORD( pak, thisdrop->amount ); + ADDWORD( pak, thisdrop->clientid ); + ADDWORD( pak, 0x0000 ); + ADDWORD( pak, 0x5f90 ); + } + else + { + // -- ITEM -- + ADDWORD( pak, BuildItemHead( thisdrop->item ) ); + ADDDWORD( pak, thisdrop->amount ); + ADDWORD( pak, thisdrop->clientid ); + ADDDWORD( pak, BuildItemData( thisdrop->item ) ); + } + thisclient->client->SendPacket( &pak ); + return true; +} + +// Player Left +void CWorldServer::pakClearUser( CPlayer* thisclient ) +{ + BEGINPACKET( pak, 0x794 ); + ADDWORD( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient, false ); +} + +// Drop items on map +bool CWorldServer::pakDoDrop( CPlayer* thisclient, CPacket* P ) +{ + BYTE itemid = GETBYTE((*P), 0x0); + if(!CheckInventorySlot(thisclient, itemid )) + return false; + DWORD amount = GETDWORD((*P), 0x1); + if (itemid == 0) + { + if ( amount<1 ) return true; + if ( thisclient->CharInfo->Zulies < amount ) return true; + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GetNewClientID(); + thisdrop->type = 1; // ZULY + thisdrop->pos = RandInCircle( thisclient->Position->current, 3 ); + thisdrop->posMap = thisclient->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = amount; + thisdrop->owner = 0; + CMap* map = MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + thisclient->CharInfo->Zulies -= amount; + BEGINPACKET( pak, 0x71d ); + ADDQWORD( pak, thisclient->CharInfo->Zulies ); + thisclient->client->SendPacket( &pak ); + } + else + { + CDrop* thisdrop = new CDrop; + assert(thisdrop); + thisdrop->clientid = GetNewClientID(); + thisdrop->type = 2; // ITEM + thisdrop->pos = RandInCircle( thisclient->Position->current, 3 ); + thisdrop->posMap = thisclient->Position->Map; + thisdrop->droptime = time(NULL); + thisdrop->amount = amount; + thisdrop->item = thisclient->items[itemid]; + thisdrop->item.count = amount; + thisdrop->owner = 0; + CMap* map = MapList.Index[thisdrop->posMap]; + map->AddDrop( thisdrop ); + // fixed by tomiz [item count drop correctly and dissaper from inventory] + if(thisclient->items[itemid].itemtype >=10 && thisclient->items[itemid].itemtype <= 13) + { + if(thisclient->items[itemid].countitems[itemid].count -= amount; + if(thisclient->items[itemid].count <= 0) ClearItem(thisclient->items[itemid]); + } + else + { + ClearItem(thisclient->items[itemid]); + } + thisclient->UpdateInventory( itemid ); + } + return true; +} + +// Picks up item +bool CWorldServer::pakPickDrop( CPlayer* thisclient, CPacket* P ) +{ + try + { + if(thisclient->Shop->open) + return true; + thisclient->RestartPlayerVal( ); + WORD dropid = GETWORD((*P), 0x00); + CDrop* thisdrop = GetDropByID( dropid, thisclient->Position->Map ); + if (thisdrop == NULL) + { + SendSysMsg( thisclient, "That is not a valid drop" ); + return true; + } + if (thisdrop->item.itemnum > 250 && thisdrop->item.itemnum < 272 && thisdrop->item.itemtype == 7)//&&thisdrop->item.itemtype<174)//251/261/271/socket jewel 171Ring 172Necklace 173Earrings + { + thisdrop->item.socketed = true; + thisdrop->item.durability = 100; + thisdrop->item.lifespan = 100; + thisdrop->item.refine = 0; + thisdrop->item.stats = 0; + } + //now find the item type for crafting + if(thisdrop->item.itemtype > 0 && thisdrop->item.itemtype < 10) + thisdrop->item.type = GServer->EquipList[thisdrop->item.itemtype].Index[thisdrop->item.itemnum]->type; + if(thisdrop->item.itemtype == 10) + thisdrop->item.type = GServer->UseList.Index[thisdrop->item.itemnum]->type; + if(thisdrop->item.itemtype == 11) + thisdrop->item.type = GServer->JemList.Index[thisdrop->item.itemnum]->type; + if(thisdrop->item.itemtype == 12) + thisdrop->item.type = GServer->NaturalList.Index[thisdrop->item.itemnum]->type; + if(thisdrop->item.itemtype == 14) + thisdrop->item.type = GServer->PatList.Index[thisdrop->item.itemnum]->type; + + bool flag = false; + BEGINPACKET( pak, 0x7a7 ); + ADDWORD ( pak, thisdrop->clientid ); + CPlayer* dropowner = 0; + if( (thisdrop->owner == 0 || thisdrop->owner == thisclient->CharInfo->charid || time(NULL)-thisdrop->droptime >= 30 ) || ( thisclient->Party->party!=NULL && thisclient->Party->party == thisdrop->thisparty )) + { + if( thisclient->Party->party!=NULL ) + { + unsigned int dropparty = thisclient->Party->party->Option/0x10; + if( dropparty == 8 ) // Orderly + { + unsigned int num = 0; + bool dpflag = false; + if( thisdrop->type == 1 ) + { + num = thisclient->Party->party->LastZulies; + thisclient->Party->party->LastZulies++; + if( thisclient->Party->party->LastZulies>=thisclient->Party->party->Members.size() ) + thisclient->Party->party->LastZulies = 0; + } + else + if( thisdrop->type == 2 ) + { + num = thisclient->Party->party->LastItem; + thisclient->Party->party->LastItem++; + if( thisclient->Party->party->LastItem>=thisclient->Party->party->Members.size() ) + thisclient->Party->party->LastItem = 0; + } + unsigned int n = 0; + while( !dpflag ) + { + n++; + if( num>=thisclient->Party->party->Members.size() ) + num = 0; + dropowner = thisclient->Party->party->Members.at( num ); + if( dropowner == NULL ) + num++; + else + { + dpflag = true; + num++; + } + if(n > 20) // not founded yet? >.> + { + dropowner = thisclient; + dpflag = true; + } + } + } + else // Equal Loot + { + dropowner = thisclient; + } + } + else + { + dropowner = thisclient; + } + if( thisdrop->type == 1 ) //Zulie + { + dropowner->CharInfo->Zulies += thisdrop->amount; + ADDWORD( pak, 0 ); + ADDBYTE( pak, 0 ); + ADDWORD( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + dropowner->client->SendPacket( &pak ); + flag = true; + } + else + if( thisdrop->type == 2 ) // Item + { + if (thisdrop->item.type == 320) //instant effect item + { + RESETPACKET( pak,0x7a3 ); + ADDWORD ( pak, dropowner->clientid ); + ADDWORD ( pak, thisdrop->item.itemnum ); + SendToVisible( &pak, dropowner ); + flag = true; + + switch (UseList.Index[thisdrop->item.itemnum]->useeffect[0]) + { + case 16: + dropowner->Stats->HP += (UseList.Index[thisdrop->item.itemnum]->useeffect[1]); + if(dropowner->Stats->HP > dropowner->Stats->MaxHP ) + dropowner->Stats->HP = dropowner->Stats->MaxHP; + break; + case 17: + dropowner->Stats->MP += (UseList.Index[thisdrop->item.itemnum]->useeffect[1]); + if(dropowner->Stats->MP > dropowner->Stats->MaxMP ) + dropowner->Stats->MP = dropowner->Stats->MaxMP; + break; + case 76: + dropowner->CharInfo->stamina += (UseList.Index[thisdrop->item.itemnum]->useeffect[1]); + if(dropowner->CharInfo->stamina > 5000) + dropowner->CharInfo->stamina = 5000; + break; + case 92: + if(dropowner->Clan->clanid>0) + { + dropowner->Clan->CP += (UseList.Index[thisdrop->item.itemnum]->useeffect[1]); + DB->QExecute( "UPDATE list_clan set cp=%i where id=%i", dropowner->Clan->CP,dropowner->Clan->clanid); + RESETPACKET( pak, 0x7e0 ); + ADDBYTE ( pak, 0x5C ); //action to update clan points (charserver) + ADDWORD ( pak, dropowner->Clan->clanid); + ADDWORD ( pak, dropowner->clientid ); + ADDDWORD ( pak, dropowner->Clan->CP ); + ADDWORD ( pak, 0x00); + SendISCPacket( &pak ); + } + else + { + Log( MSG_WARNING, "Player collected clanpoints but is not in a clan"); + } + break; + default: + Log( MSG_WARNING, "Unknown autoconsume type: %i %i",(UseList.Index[thisdrop->item.itemnum]->usecondition[0]),(UseList.Index[thisdrop->item.itemnum]->usecondition[1])); + break; + } + } + else + { + unsigned int tempslot = dropowner->AddItem( thisdrop->item ); + if(tempslot != 0xffff)// we have slot + { + unsigned int slot1 = tempslot; + unsigned int slot2 = 0xffff; + if(tempslot > MAX_INVENTORY) + { + slot1 = tempslot / 1000; + slot2 = tempslot % 1000; + } + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, slot1 ); + ADDBYTE ( pak, 0x00 ); + ADDWORD ( pak, BuildItemHead( dropowner->items[slot1] ) ); + ADDDWORD ( pak, BuildItemData( dropowner->items[slot1] ) ); + dropowner->client->SendPacket( &pak ); + dropowner->UpdateInventory( slot1, slot2 ); + flag = true; + } + else + { + ADDBYTE (pak, 0x03); + dropowner->client->SendPacket(&pak); + } + } + } + } + else + { + ADDBYTE (pak, 0x02); + thisclient->client->SendPacket(&pak); + } + if( flag ) + { + if( thisclient->Party->party!=NULL ) + { + unsigned int dropparty = thisclient->Party->party->Option/0x10; + if( dropparty == 8 ) + { + RESETPACKET( pak, 0x7d3 ); + ADDWORD ( pak, dropowner->clientid ); + if( thisdrop->type == 1 ) + { + ADDWORD ( pak, 0xccdf ); + ADDDWORD( pak, thisdrop->amount ); + } + else + if( thisdrop->type == 2 ) + { + ADDWORD ( pak, BuildItemHead( thisdrop->item ) ); + ADDDWORD ( pak, BuildItemData( thisdrop->item ) ); + } + thisclient->Party->party->SendToMembers( &pak, dropowner ); + } + } + CMap* map = MapList.Index[thisdrop->posMap]; + pthread_mutex_lock( &map->DropMutex ); + map->DeleteDrop( thisdrop ); + pthread_mutex_unlock( &map->DropMutex ); + } + return true; + } + catch(...) + { + Log( MSG_ERROR, "Error in pakpickdrop"); + return false; + } +} + +// Changes equipment +bool CWorldServer::pakChangeEquip( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + WORD srcslot = GETWORD((*P), 0); + WORD destslot = GETWORD((*P), 2); + if(!CheckInventorySlot(thisclient, srcslot)) + return false; + if(!CheckInventorySlot(thisclient, destslot)) + return false; + if( destslot == 0 ) destslot = thisclient->GetNewItemSlot( thisclient->items[srcslot] ); + if( destslot == 0xffff ) return true; + /* + if( srcslot==7 || destslot==7 ) + { + // clear the buffs if we change the weapon + for(UINT i=0;i<15;i++) + { + switch(thisclient->MagicStatus[i].Buff) + { + case A_ATTACK: + case A_ACCUR: + case A_HASTE: + case A_CRITICAL: + thisclient->MagicStatus[i].Duration = 0; + break; + } + } + } + */ + if(destslot == 8) //trying to equip a shield + { + // first we will check if it is possible. Do we have a two handed weapon equipped? + UINT weapontype = EquipList[WEAPON].Index[thisclient->items[7].itemnum]->type; + switch(weapontype) + { + case TWO_HAND_SWORD: + case SPEAR: + case TWO_HAND_AXE: + case BOW: + case GUN: + case LAUNCHER: + case STAFF: + case KATAR: + case DOUBLE_SWORD: + case DUAL_GUN: + { + //yes we do. OK reject the shield then. + SendSysMsg( thisclient, "Cannot equip shield at this time" ); + return true; + } + break; + default: + break; + } + } + CItem tmpitm; + tmpitm = thisclient->items[srcslot]; + thisclient->items[srcslot] = thisclient->items[destslot]; + thisclient->items[destslot] = tmpitm; +// thisclient->UpdateInventory( srcslot, destslot ); + BEGINPACKET( pak,0x7a5 ); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, srcslot ); + ADDDWORD( pak, BuildItemShow(thisclient->items[srcslot])); // ITEM + ADDWORD ( pak, thisclient->Stats->Move_Speed); + SendToVisible( &pak, thisclient ); + + if( srcslot == 7 || destslot == 7 ) + { + // if is two hand weapon, we have to check if have shield and unequip it + UINT weapontype = EquipList[WEAPON].Index[thisclient->items[7].itemnum]->type; + switch(weapontype) + { + case TWO_HAND_SWORD: + case SPEAR: + case TWO_HAND_AXE: + case BOW: + case GUN: + case LAUNCHER: + case STAFF: + case KATAR: + case DOUBLE_SWORD: + case DUAL_GUN: + { + if(thisclient->items[8].itemnum != 0) + { // we should unequip the shield + UINT newslot = thisclient->GetNewItemSlot( thisclient->items[8] ); + if(newslot == 0xffff) + { + // we have no slot for the shield, we have to change back the items + tmpitm = thisclient->items[destslot]; + thisclient->items[destslot] = thisclient->items[srcslot]; + thisclient->items[srcslot] = tmpitm; + SendSysMsg( thisclient, "You don't have free slot" ); + return true; + } + thisclient->items[newslot] = thisclient->items[8]; + ClearItem( thisclient->items[8] ); + thisclient->UpdateInventory( newslot, 8 ); + RESETPACKET( pak, 0x7a5); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, 8 ); + ADDWORD ( pak, thisclient->items[8].itemnum ); + ADDWORD ( pak, BuildItemRefine( thisclient->items[8] ) ); + ADDWORD ( pak, thisclient->Stats->Move_Speed ); + SendToVisible( &pak, thisclient ); + + /* STARTPACKET(pak2, 0x782, 11); + SETWORD( pak2, 0x00,thisclient->clientid ); + SETBYTE( pak2, 0x02, thisclient->Status->Stance ); + SETWORD( pak2, 0x03, thisclient->Stats->Move_Speed ); + SendToVisible( &pak2, thisclient );*/ + } + } + break; + default: + break; + } + } + + STARTPACKET(pak3,0x718,21); + SETBYTE ( pak3, 0x00, 2); // ITEMS TO UPDATE + SETBYTE ( pak3, 0x01, srcslot); + SETWORD ( pak3, 0x02, BuildItemHead( thisclient->items[srcslot] ) ); + SETDWORD ( pak3, 0x04, BuildItemData( thisclient->items[srcslot] ) ); + SETBYTE ( pak3, 0x08, destslot); + SETWORD ( pak3, 0x09, BuildItemHead( thisclient->items[destslot] ) ); + SETDWORD ( pak3, 0x0b, BuildItemData( thisclient->items[destslot] ) ); + thisclient->client->SendPacket(&pak3); + + + //thisclient->UpdateInventory( srcslot, destslot ); + thisclient->SetStats( ); + return true; +} + +// Construct a cart/change parts +bool CWorldServer::pakChangeCart( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + WORD cartslot = GETWORD((*P), 0); + WORD srcslot = GETWORD((*P), 0) + 135; + WORD destslot = GETWORD((*P), 2); + if(!CheckInventorySlot( thisclient, srcslot )) + return false; + if(!CheckInventorySlot( thisclient, destslot )) + return false; + if( destslot == 0 ) destslot=thisclient->GetNewItemSlot( thisclient->items[srcslot] ); + if( destslot == 0xffff ) return true; + + CItem tmpitm = thisclient->items[srcslot]; + thisclient->items[srcslot] = thisclient->items[destslot]; + thisclient->items[destslot] = tmpitm; + + BEGINPACKET( pak, 0x7ca ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, cartslot); + ADDWORD ( pak, thisclient->items[srcslot].itemnum); + ADDWORD ( pak, BuildItemRefine( thisclient->items[srcslot] ) ); + ADDWORD ( pak, thisclient->Stats->Move_Speed ); + SendToVisible( &pak, thisclient ); + + thisclient->UpdateInventory( srcslot, destslot ); + thisclient->Stats->Move_Speed = thisclient->GetMoveSpeed( ); + return true; +} + +// Start attacking a monster or player +bool CWorldServer::pakStartAttack( CPlayer* thisclient, CPacket* P ) +{ + WORD clientid = GETWORD((*P),0x00); + if ( thisclient->Battle->target == clientid ) return true; //no targetting yourself + + int weapontype = EquipList[WEAPON].Index[thisclient->items[7].itemnum]->type; + if( weapontype == 231 && thisclient->items[132].count<1 ) //Bow and no arrows + return true; + else + if( weapontype == 232 && thisclient->items[133].count<1 ) //Gun and no bullets + return true; + else + if( weapontype == 233 && thisclient->items[134].count<1 ) //Launcher and no shells + return true; + else + if( weapontype == 271 && thisclient->items[132].count<1 ) //Crossbow and no arrows + return true; + + CMap* map = MapList.Index[thisclient->Position->Map]; + CCharacter* character = map->GetCharInMap( clientid ); + if(character == NULL) return true; + if(character->IsMonster( )) + { + if(map->allowpvp == 0 && character->IsSummon( )) return true; + if(!character->IsSummon()) + character->AddDamage((CCharacter*)thisclient, 0); + //} + //if(character->IsMonster()) + //{ + // SET MONSTER HEALTH + BEGINPACKET( pak, 0x79f ); + ADDWORD ( pak, character->clientid ); + ADDDWORD ( pak, character->Stats->HP ); + thisclient->client->SendPacket( &pak ); + } + thisclient->StartAction( character, NORMAL_ATTACK, 0 ); + thisclient->Battle->contatk = true; + return true; +} + +// Goto next map through gate +bool CWorldServer::pakGate( CPlayer* thisclient, CPacket* P ) +{ + thisclient->Session->inGame = false; + word GateID = GETWORD((*P), 0x00); + CTeleGate* thisgate = GetTeleGateByID( GateID ); + fPoint position; + UINT map = 0; + // I'm setting this at 50 distance from the point of teleport. Increase if you run into + // broken telegates. We might need to dump the scale and work from that - Drakia + if( thisgate == NULL || thisclient->Position->Map != thisgate->srcMap || distance(thisclient->Position->current, thisgate->src) > 150 ) + { + Log( MSG_HACK, "Player %s[Map: %i X: %f Y: %f] - Gate Hacking[ID: %i]", + thisclient->CharInfo->charname, thisclient->Position->Map, + thisclient->Position->current.x, thisclient->Position->current.y, GateID ); + return true; + } + + + map = thisgate->destMap; + position = thisgate->dest; + + MapList.Index[map]->TeleportPlayer( thisclient, position ); + thisclient->saveinventory(); + thisclient->quicksave(); + thisclient->Session->inGame = true; + return true; +} + +// Emotions +bool CWorldServer::pakDoEmote( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + thisclient->RestartPlayerVal( ); + BEGINPACKET( pak, 0x781 ); + ADDWORD( pak, GETWORD((*P), 0) ); + ADDWORD( pak, GETWORD((*P), 2) ); + ADDWORD( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient ); + return true; +} + +// This function is for increasing your stats +bool CWorldServer::pakAddStats( CPlayer* thisclient, CPacket* P ) +{ + BYTE statid = GETBYTE((*P), 0); + int statval = -1; + int nstatval = 0; + switch(statid) + { + case 0: nstatval=(int)floor((float)thisclient->Attr->Str/5); break; + case 1: nstatval=(int)floor((float)thisclient->Attr->Dex/5); break; + case 2: nstatval=(int)floor((float)thisclient->Attr->Int/5); break; + case 3: nstatval=(int)floor((float)thisclient->Attr->Con/5); break; + case 4: nstatval=(int)floor((float)thisclient->Attr->Cha/5); break; + case 5: nstatval=(int)floor((float)thisclient->Attr->Sen/5); break; + default: + break; + } + if (thisclient->CharInfo->StatPoints>=nstatval && nstatval < ((Config.MaxStat/5)+1)) + { + switch(statid) + { + case 0: statval=++thisclient->Attr->Str; break; + case 1: statval=++thisclient->Attr->Dex; break; + case 2: statval=++thisclient->Attr->Int; break; + case 3: statval=++thisclient->Attr->Con; break; + case 4: statval=++thisclient->Attr->Cha; break; + case 5: statval=++thisclient->Attr->Sen; break; + default: + break; + } + } + if (statval>0) + { + thisclient->CharInfo->StatPoints-=nstatval; + BEGINPACKET( pak, 0x7a9 ); + ADDBYTE( pak, statid ); + ADDWORD( pak, (unsigned short)statval ); + thisclient->client->SendPacket( &pak ); + } + thisclient->SetStats( ); + return true; +} + +// Normal Chat +bool CWorldServer::pakNormalChat( CPlayer* thisclient, CPacket* P ) +{ + if (P->Buffer[0]=='/') + { + return pakGMCommand( thisclient, P ); + //std::cout << thisclient->CharInfo->charname << ": " << P->Buffer << "\n"; + + } + else + { + BEGINPACKET( pak, 0x783 ); + ADDWORD( pak, thisclient->clientid ); + ADDSTRING( pak, P->Buffer ); + ADDBYTE( pak, 0 ); + SendToVisible( &pak, thisclient ); + } + return true; +}; + +// Exit rose +bool CWorldServer::pakExit( CPlayer* thisclient, CPacket* P ) +{ + BEGINPACKET( pak, 0x707 ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + thisclient->client->isActive = false; + return true; +} + +// move skill to function keys +bool CWorldServer::pakMoveSkill ( CPlayer* thisclient, CPacket* P ) +{ + BYTE slotid = GETBYTE((*P), 0x0 ); + WORD itemid = GETWORD((*P), 0x1 ); + if(slotid>47) return true; + thisclient->quickbar[slotid] = itemid; + BEGINPACKET( pak, 0x7aa ); + ADDBYTE( pak, slotid ); + ADDWORD( pak, itemid ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// User is checking up on the server +bool CWorldServer::pakPing ( CPlayer* thisclient, CPacket* P ) +{ + BEGINPACKET( pak, 0x0700 ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// GameGuard +bool CWorldServer::pakGameGuard ( CPlayer* thisclient, CPacket* P ) +{ + thisclient->lastGG = clock(); + return true; +} + +// User Died +bool CWorldServer::pakUserDied ( CPlayer* thisclient, CPacket* P ) +{ + thisclient->Session->inGame = false; + thisclient->Session->Respawned = 1; + //BYTE respawn = GETBYTE((*P),0); + thisclient->Session->RespawnChoice = GETBYTE((*P),0); + + //PY lets try moving all this stuff into the main loop because we are currently outside of the player mutex + //It's all in pakRespawned now. + //thisclient->Session->inGame is just used as a toggle trigger now + + /* + Log( MSG_INFO, "RespawnZones choise: %i", respawn ); + //1 - Current / 2 - save point/dungeon entrance + CMap* map = MapList.Index[thisclient->Position->Map]; + CRespawnPoint* thisrespawn = NULL; + thisclient->Stats->HP = thisclient->Stats->MaxHP * 10 / 100; + if(respawn == 1 || map->dungeon) + { + if (respawn != 1) // This is a dungeon map, spawn at start + thisrespawn = map->GetStartRespawn( ); + else + thisrespawn = map->GetNearRespawn( thisclient ); + if(thisrespawn!=NULL) + { + map->TeleportPlayer( thisclient, thisrespawn->dest, false ); + } + } + else // This is a field, spawn in save town + { + thisrespawn = GetRespawnByMap( thisclient->Position->saved ); + if(thisrespawn != NULL) + { + fPoint coord = thisrespawn->dest; + MapList.Index[thisclient->Position->saved]->TeleportPlayer( thisclient, coord, false ); + } + } + + if(thisrespawn == NULL) + { + fPoint coord; + coord.x = 5200; + coord.y = 5200; + MapList.Index[2]->TeleportPlayer( thisclient, coord, false ); + } + for(unsigned int i=0;i<30;i++) + { // Clean Buffs + thisclient->MagicStatus[i].Duration = 0; + }*/ + + return true; +} +// User Save Town +bool CWorldServer::pakSaveTown ( CPlayer* thisclient, CPacket* P ) +{ + thisclient->Position->saved = thisclient->Position->Map; + GServer->DB->QExecute("UPDATE characters SET townid=%i WHERE id=%i",thisclient->Position->saved,thisclient->CharInfo->charid ); + return true; +} +// Shouting +bool CWorldServer::pakShout ( CPlayer* thisclient, CPacket* P ) +{ + BEGINPACKET(pak, 0x0785); + ADDSTRING ( pak, thisclient->CharInfo->charname ); + ADDBYTE ( pak, 0 ); + ADDSTRING ( pak, &P->Buffer[0] ); + ADDBYTE ( pak, 0 ); + SendToMap ( &pak, thisclient->Position->Map ); + + return true; +} + +// Whispering +bool CWorldServer::pakWhisper ( CPlayer* thisclient, CPacket* P ) +{ + char msgto[17]; + memset( &msgto, '\0', 17 ); + strncpy_s( msgto, (char*)&(*P).Buffer[0],16 ); + CPlayer* otherclient = GetClientByCharName( msgto ); + if(otherclient!=NULL) + { + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, thisclient->CharInfo->charname ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, &P->Buffer[strlen(msgto)+1] ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); + return true; + } + BEGINPACKET( pak, 0x0784 ); + ADDSTRING( pak, msgto ); + ADDBYTE( pak, 0 ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// Return to Char Select +bool CWorldServer::pakCharSelect ( CPlayer* thisclient, CPacket* P ) +{ + if(!thisclient->Session->inGame) return true; + BEGINPACKET( pak, 0x707 ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + thisclient->Session->inGame=false; + RESETPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0xfa ); + ADDWORD ( pak, thisclient->Session->userid );//CharInfo->charid ); + ADDBYTE ( pak, 0x00 ); + SendISCPacket( &pak ); + + for(unsigned j=0; jVisiblePlayers.size(); j++) { + CPlayer* otherclient=thisclient->VisiblePlayers.at(j); + thisclient->ClearObject(otherclient->clientid); + } + for(unsigned j=0; jVisibleMonsters.size(); j++) { + thisclient->ClearObject(thisclient->VisibleMonsters.at(j)); + } + for(unsigned j=0; jVisibleDrops.size(); j++) { + CDrop* thisdrop = thisclient->VisibleDrops.at(j); + thisclient->ClearObject(thisdrop->clientid); + } + for(unsigned j=0; jVisibleNPCs.size(); j++) { + CNPC* thisnpc = thisclient->VisibleNPCs.at(j); + thisclient->ClearObject( thisnpc->clientid ); + } + + thisclient->savedata(); + RESETPACKET( pak, 0x505 ); + ADDWORD ( pak, thisclient->Session->userid ); + SendISCPacket( &pak ); + thisclient->Session->isLoggedIn = false; + thisclient->Saved = true; + pakClearUser( thisclient ); + ClearClientID( thisclient->clientid ); + if(thisclient->client!=NULL) thisclient->client->isActive = false; + return true; +} + +// Buy from NPC +bool CWorldServer::pakNPCBuy ( CPlayer* thisclient, CPacket* P ) +{ + //try + //{ + if(thisclient->Shop->open) + return true; + WORD NPC = GETWORD((*P), 0); + CNPC* thisnpc = GetNPCByID( NPC, thisclient->Position->Map ); + if( thisnpc == NULL ) + return true; + BYTE buycount = GETBYTE((*P), 2); + BYTE sellcount = GETBYTE((*P), 3); + + //Union Shop case... + Log(MSG_INFO,"Buying /selling from NPC %i. Buycount: %i, Sellcount: %i" ,thisnpc->npctype, buycount,sellcount); + bool is_unionshop = false; + + //if (thisnpc->npctype == 1109 || thisnpc->npctype == 1110 || thisnpc->npctype == 1111 || thisnpc->npctype == 1112 || thisnpc->npctype == 1043) + //{ + // is_unionshop = true; + // Log(MSG_INFO,"identified as a union shop"); + //} + + switch(thisnpc->npctype) + { + case 1109: + case 1110: + case 1111: + case 1112: + case 1043: + is_unionshop = true; + Log(MSG_INFO,"identified as a union shop"); + break; + } + + int UpointCost = 0; + int UnionVar = thisclient->Union_s->unionvar[0]; + int TotUpointCost = 0; + BYTE ncount = 0; + BEGINPACKET( pak, 0x717 ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x00 ); + for (int i=0; ithisnpc->tab1; + break; + case 1: + sellid = thisnpc->thisnpc->tab2; + break; + case 2: + sellid = thisnpc->thisnpc->tab3; + break; + case 3: + sellid = thisnpc->thisnpc->specialtab; + break; + default: + sellid = 0; + } + if(SellList.Index[sellid]==NULL) + { // invalid tab + //Log( MSG_WARNING, "Bad tab ID"); + continue; + } + int tmpcount = count; + while (tmpcount > 0) + { + CItem thisitem; + thisitem.itemnum = SellList.Index[sellid]->item[itemid] % 1000; + thisitem.itemtype = SellList.Index[sellid]->item[itemid] / 1000; + thisitem.count = count; + //Log( MSG_WARNING, "Item purchased. type = %i. id = %i. tempcount = %i", thisitem.itemtype,thisitem.itemnum,tmpcount); + + if(thisitem.itemtype <= 9) + { + UpointCost = GServer->EquipList[thisitem.itemtype].Index[thisitem.itemnum]->unionpoint; + thisitem.type = GServer->EquipList[thisitem.itemtype].Index[thisitem.itemnum]->type; + thisitem.durability = EquipList[thisitem.itemtype].Index[thisitem.itemnum]->durability; + } + if(thisitem.itemtype == 10) + { + UpointCost = GServer->UseList.Index[thisitem.itemnum]->unionpoint; + thisitem.type = GServer->UseList.Index[thisitem.itemnum]->type; + thisitem.durability = 0; + } + if(thisitem.itemtype == 11) + { + UpointCost = GServer->JemList.Index[thisitem.itemnum]->unionpoint; + thisitem.type = GServer->JemList.Index[thisitem.itemnum]->type; + thisitem.durability = 0; + } + if(thisitem.itemtype == 12) + { + UpointCost = GServer->NaturalList.Index[thisitem.itemnum]->unionpoint; + thisitem.type = GServer->NaturalList.Index[thisitem.itemnum]->type; + thisitem.durability = 0; + } + if(thisitem.itemtype == 14) + { + UpointCost = GServer->PatList.Index[thisitem.itemnum]->unionpoint; + thisitem.type = GServer->PatList.Index[thisitem.itemnum]->type; + thisitem.durability = GServer->PatList.Index[thisitem.itemnum]->durability; + } + if(is_unionshop && UpointCost == 0) + { + ncount++; + Log( MSG_WARNING, "Item has no price in Server"); + tmpcount = 0; + continue; //this item has no price so skip it + } + if(is_unionshop && thisclient->Union_s->unionvar[UnionVar] < UpointCost) + { + ncount++; + Log( MSG_WARNING, "You cannot afford this item"); + tmpcount = 0; + continue; //can't afford the item so don't bother adding it + } + //Log( MSG_WARNING, "Item purchased. type = %i. id = %i", thisitem.itemtype,thisitem.itemnum); + unsigned newslot = thisclient->GetNewItemSlot( thisitem ); + if ( newslot == 0xffff ) + { + ncount -= 1; + Log( MSG_WARNING, "failed to find free slot"); + break; + } + int thisslotcount = 999 - thisclient->items[newslot].count; + //Log( MSG_WARNING, "this slot count = %i", thisslotcount); + //if ( thisslotcount > tmpcount ) + //{ + // thisslotcount = tmpcount; + //} + //Log( MSG_WARNING, "this slot count = %i", thisslotcount); + tmpcount -= thisslotcount; + thisitem.count += thisclient->items[newslot].count; + thisitem.refine = 0; + thisitem.lifespan = 100; + thisitem.appraised = true; + thisitem.socketed = false; + thisitem.stats = 0; + if(is_unionshop) //make sure all union shop items come with extra stats + { + if(thisitem.itemtype <= 9) + thisitem.stats = GServer->RandNumber(1,299); + } + thisitem.gem = 0; + thisclient->items[newslot] = thisitem; + ADDBYTE ( pak, newslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[newslot] ) ); + + switch(thisitem.itemtype) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 8: + case 9: + case 14: + { + float price = 0; + UINT quality = 0; + UINT bprice = 0; + if(thisitem.itemtype<10 ) + { + quality = EquipList[thisitem.itemtype].Index[thisitem.itemnum]->quality; + bprice = EquipList[thisitem.itemtype].Index[thisitem.itemnum]->price; + } + else + { + quality = PatList.Index[thisitem.itemnum]->quality; + bprice = PatList.Index[thisitem.itemnum]->price; + } + price = quality; + price += 0x32; // town rate + price *= bprice; + price += 0.5; + price /= 100; + price = (float)round(price); + //Log( MSG_WARNING, "Item bought: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + if(is_unionshop) + { + thisclient->Union_s->unionvar[UnionVar] -= UpointCost; + if(UnionVar == 9) + { + Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + thisclient->Union_s->unionvar[UnionVar],thisclient->Session->userid, thisclient->Session->password); + Log(MSG_DEBUG,"KTPoints updated and saved to the database"); + Log( MSG_WARNING, "item Purchased successfully"); + } + + } + else + thisclient->CharInfo->Zulies -= (long int)price; + } + break; + case 10: + case 12: + { + // this values should be the same as packet 753 + //Log( MSG_WARNING, "Type %i item selected", thisitem.itemtype); + BYTE values[11] = {0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32}; + UINT type = 0; + UINT bprice = 0; + UINT pricerate = 0; + UINT pvalue = 0; + if(thisitem.itemtype == 10) + { + type = UseList.Index[thisitem.itemnum]->type; + bprice = UseList.Index[thisitem.itemnum]->price; + pricerate = UseList.Index[thisitem.itemnum]->pricerate; + pvalue = UseList.Index[thisitem.itemnum]->pricevalue; + } + else + { + type = NaturalList.Index[thisitem.itemnum]->type; + bprice = NaturalList.Index[thisitem.itemnum]->price; + pricerate = NaturalList.Index[thisitem.itemnum]->pricerate; + pvalue = NaturalList.Index[thisitem.itemnum]->pricevalue; + } + unsigned int value = 0; + float price = 0; + bool flag; + if(type<421) + if(type<311) + flag = false; + else + if(type>312) + flag = false; + else + flag = true; + else + if(type<=428) + flag = true; + else + if(type<311) + flag = false; + else + flag = true; + if(flag) + { + value = pvalue; + if(value>=11) + value ^= 0xffffffff; + else + value = values[value]; + value -= 0x32; + value *= pricerate; + price = value * 0.001; + price += 1; + price *= bprice; + price += 0.5; + price = (float)floor(price); + //Log( MSG_WARNING, "Item bought: itemnum %i, itemtype %i, itemcount %i, price %0.0f, flag %i", thisitem.itemnum, thisitem.itemtype, thisitem.count, price, flag); + if(is_unionshop) + { + thisclient->Union_s->unionvar[UnionVar] -= (UpointCost * count); + + TotUpointCost -= (UpointCost * count); + if(UnionVar == 9) + { + Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + thisclient->Union_s->unionvar[UnionVar],thisclient->Session->userid, thisclient->Session->password); + Log(MSG_DEBUG,"KTPoints updated and saved to the database"); + Log( MSG_WARNING, "item Purchased successfully"); + } + //Log( MSG_WARNING, "item Purchased successfully"); + } + else + thisclient->CharInfo->Zulies -= (long int)price * count; + } + else + { + price = pricerate; + value = 0x61 - 0x32; // misc rate - 0x32 + price *= value; + price *= 0.001; + price += 1; + price *= bprice; + price += 0.5; + price = (float)floor(price); + //Log( MSG_WARNING, "Item bought: itemnum %i, itemtype %i, itemcount %i, price %0.0f, flag %i", thisitem.itemnum, thisitem.itemtype, thisitem.count, price, flag); + if(is_unionshop) + { + thisclient->Union_s->unionvar[UnionVar] -= (UpointCost * count); + //Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + TotUpointCost -= (UpointCost * count); + if(UnionVar == 9) + { + Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + thisclient->Union_s->unionvar[UnionVar],thisclient->Session->userid, thisclient->Session->password); + Log(MSG_DEBUG,"KTPoints updated and saved to the database"); + Log( MSG_WARNING, "item Purchased successfully"); + } + //Log( MSG_WARNING, "item Purchased successfully"); + } + else + thisclient->CharInfo->Zulies -= (long int)price * count; + } + } + break; + case 7: + case 11: + case 13: + { + float price = 0; + UINT bprice = 0; + UINT pricerate; + if(thisitem.itemtype==7) + { + pricerate = EquipList[7].Index[thisitem.itemnum]->pricerate; + bprice = EquipList[7].Index[thisitem.itemnum]->price; + } + else + { + switch(thisitem.itemtype) + { + case 11: + pricerate = JemList.Index[thisitem.itemnum]->pricerate; + bprice = JemList.Index[thisitem.itemnum]->price; + break; + case 13:continue; + } + } + price = pricerate; + unsigned int value = 0x61 - 0x32; // misc rate - 0x32 + price *= value; + price *= 0.001; + price += 1; + price *= bprice; + price += 0.5; + price = (float)round(price); + if(thisitem.itemtype == 7) + price = (float)round(bprice * 1.3); + //Log( MSG_WARNING, "Item bought: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + if(is_unionshop) + { + thisclient->Union_s->unionvar[UnionVar] -= UpointCost; + //Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + TotUpointCost -= UpointCost; + if(UnionVar == 9) + { + Log( MSG_WARNING, "Union Shop Item bought: UnionID %i, itemnum %i, itemtype %i, itemcount %i, price %i", UnionVar, thisitem.itemnum, thisitem.itemtype, thisitem.count, UpointCost); + GServer->DB->QExecute("UPDATE accounts SET ktpoints=%i WHERE id=%i AND password='%s'", + thisclient->Union_s->unionvar[UnionVar],thisclient->Session->userid, thisclient->Session->password); + Log(MSG_DEBUG,"KTPoints updated and saved to the database"); + Log( MSG_WARNING, "item Purchased successfully"); + } + //Log( MSG_WARNING, "item Purchased successfully"); + } + else + thisclient->CharInfo->Zulies -= (long int)price*count; + } + break; + default: + Log( MSG_WARNING, "Invalid Item Type: %i", thisitem.itemtype ); + break; + } + ncount++; + Log( MSG_WARNING, "item count: %i", ncount ); + } + BEGINPACKET(pak1, 0x720 ); + ADDWORD(pak1, thisclient->Union_s->unionvar[0] ); + ADDDWORD(pak1, TotUpointCost); + thisclient->client->SendPacket( &pak1 ); + } + for (int i=0; iitems[slotid]; + thisitem.count = count; + switch(thisitem.itemtype) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 8: + case 9: + case 14: + { + float price = 0; + price = 7.142857E-05F * 5000; + if(thisitem.itemtype<10) + { + price *= EquipList[thisitem.itemtype].Index[thisitem.itemnum]->price; + } + else + { + price *= PatList.Index[thisitem.itemnum]->price; + } + price *= thisitem.durability + 0xc8; + price *= 40; + price *= 0xc8 - 0x62; //town rate + price *= 1.000000E-06; + price = (float)floor(price); + //Log( MSG_WARNING, "Item Sold: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + thisclient->CharInfo->Zulies += (long int)price*count; + } + break; + case 10: + case 12: + { + // this values are the same from packet 753 + BYTE values[11] = {0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32}; + UINT type = 0; + UINT bprice = 0; + UINT pricerate = 0; + UINT pvalue = 0; + if(thisitem.itemtype==10) + { + type = UseList.Index[thisitem.itemnum]->type; + bprice = UseList.Index[thisitem.itemnum]->price; + pricerate = UseList.Index[thisitem.itemnum]->pricerate; + pvalue = UseList.Index[thisitem.itemnum]->pricevalue; + } + else + { + type = NaturalList.Index[thisitem.itemnum]->type; + bprice = NaturalList.Index[thisitem.itemnum]->price; + pricerate = NaturalList.Index[thisitem.itemnum]->pricerate; + pvalue = NaturalList.Index[thisitem.itemnum]->pricevalue; + } + unsigned int value = 0; + float price = 0; + bool flag; + if(type<421) + { + if(type<311) + flag = false; + else + if(type>312) + flag = false; + else + flag = true; + } + else + { + if(type<=428) + flag = true; + else + if(type<311) + flag = false; + else + flag = true; + } + if(flag) + { + value = pvalue; + if(value>=11) + value ^= 0xffffffff; + else + value = values[value]; + value -= 0x32; + value *= pricerate; + value += 1000; + value *= bprice; + value *= (200 - 0x62); //town rate ( 100) + price = value * 5.555555555555556E-06; + price = (float)floor(price); + //Log( MSG_WARNING, "Item Sold: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + thisclient->CharInfo->Zulies += (long int)price*count; + } + else + { + price = pricerate; + value = 0x61 - 0x32; // misc rate -0x32 + price *= value; + price += 1000; + price *= bprice; + price *= (200 - 0x62); //town rate ( 100) + price *= 5.555555555555556E-06; + price = (float)floor(price); + //Log( MSG_WARNING, "Item Sold: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + thisclient->CharInfo->Zulies += (long int)price*count; + } + } + break; + case 7: + case 11: + case 13: + { + float price = 0; + UINT bprice = 0; + UINT pricerate = 0; + switch(thisitem.itemtype) + { + case 7: + pricerate = EquipList[thisitem.itemtype].Index[thisitem.itemnum]->pricerate; + bprice = EquipList[thisitem.itemtype].Index[thisitem.itemnum]->price; + break; + case 11: + pricerate = JemList.Index[thisitem.itemnum]->pricerate; + bprice = JemList.Index[thisitem.itemnum]->price; + break; + case 13:continue;//can we sell quest items? :S + + } + price = pricerate; + price *= 0x61 - 0x32; // misc rate -0x32 + price += 1000; + price *= bprice; + price *= (200 - 0x62); //town rate ( 100) + price *= 5.555555555555556E-06; + price = (float)floor(price); + if(thisitem.itemtype == 7) + price = (float)floor(bprice * 0.5555508); + //Log( MSG_WARNING, "Item Sold: itemnum %i, itemtype %i, itemcount %i, price %0.0f", thisitem.itemnum, thisitem.itemtype, thisitem.count, price); + thisclient->CharInfo->Zulies += (long int)price*count; + } + break; + default: + Log( MSG_WARNING, "Invalid Item Type: %i", thisitem.itemtype ); + break; + } + thisclient->items[slotid].count -= count; + if( thisclient->items[slotid].count <=0 ) + ClearItem( thisclient->items[slotid] ); + ADDBYTE( pak, slotid ); + ADDWORD( pak, BuildItemHead( thisclient->items[slotid] ) ); + ADDDWORD( pak, BuildItemData( thisclient->items[slotid] ) ); + ncount++; + } + SETQWORD( pak, 0, thisclient->CharInfo->Zulies ); + SETBYTE( pak, 8, ncount ); + thisclient->client->SendPacket( &pak ); + thisclient->saveinventory(); + return true; + //} + //__seh_except(info, context) + //{ + // Log( MSG_ERROR, "Error in pakNPCBuy"); + // return false; + //} + //__seh_end +} + + + +// Trade action +bool CWorldServer::pakTradeAction ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + CPacket pak; + BYTE action = GETBYTE( (*P), 0 ); + thisclient->Trade->trade_target = GETWORD( (*P), 1 ); + CPlayer* otherclient = GetClientByID( thisclient->Trade->trade_target, thisclient->Position->Map ); + if (otherclient == NULL) return true; + switch(action) + { + case 0: + // REQUEST TRADE + RESETPACKET( pak, 0x7c0 ); + ADDBYTE( pak, 0 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); // send the request packet to the other player with action zero + thisclient->Trade->trade_status = 2; // requesting trade. + otherclient->Trade->trade_status = 1; // other client needs to respond + break; + case 1: + // ACCEPT TRADE + RESETPACKET( pak, 0x7c0 ); + ADDBYTE( pak, 1 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); // send a trade accept packet + thisclient->Trade->trade_status = 3; // set both trade status bits to 3 (trade window open for both players) + otherclient->Trade->trade_status = 3; + for(int i=0; i<11; i++) thisclient->Trade->trade_count[i] = 0; + for(int i=0; i<10; i++) thisclient->Trade->trade_itemid[i] = 0; + for(int i=0; i<11; i++) otherclient->Trade->trade_count[i] = 0; + for(int i=0; i<10; i++) otherclient->Trade->trade_itemid[i] = 0; + break; + case 2: // REJECT TRADE + case 3: // CANCEL TRADE + // reject and cancel both do the same thing + RESETPACKET( pak, 0x7c0 ); + ADDBYTE( pak, 3 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); // send packet to cancel trade + thisclient->Trade->trade_target = 0; // remove trade targets from both clients. + otherclient->Trade->trade_target = 0; + thisclient->Trade->trade_status = 0; // set trade status to 0 for both clients + thisclient->Trade->trade_status = 0; + break; + case 4: + // CHECK READY + RESETPACKET( pak, 0x7c0 ); + ADDBYTE( pak, 4 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); // send trade locked packet to other client + thisclient->Trade->trade_status = 4; // set MY OWN trade status to locked + break; + case 5: + // UNCHECK READY + // seems we can't do this right now + break; + case 6: + // TRADE DONE + if (thisclient->Trade->trade_status == 6) + thisclient->Trade->trade_status = 4; + else + thisclient->Trade->trade_status = 6; + if (otherclient->Trade->trade_status == 6) + { + RESETPACKET( pak, 0x7c0 ); + // Complete the trade + ADDBYTE( pak, 6 ); + ADDWORD( pak, otherclient->clientid ); + ADDBYTE( pak, 0 ); + thisclient->client->SendPacket( &pak ); + RESETPACKET( pak, 0x7c0 ); + ADDBYTE( pak, 6 ); + ADDWORD( pak, thisclient->clientid ); + ADDBYTE( pak, 0 ); + otherclient->client->SendPacket( &pak ); + + // Update the zuly + thisclient->CharInfo->Zulies -= thisclient->Trade->trade_count[0x0a]; + otherclient->CharInfo->Zulies -= otherclient->Trade->trade_count[0x0a]; + thisclient->CharInfo->Zulies += otherclient->Trade->trade_count[0x0a]; + otherclient->CharInfo->Zulies += thisclient->Trade->trade_count[0x0a]; + + unsigned tucount = 0; + unsigned oucount = 0; + + // Begin this clients inventory update + BEGINPACKET( pakt, 0x717 ); + ADDQWORD( pakt, thisclient->CharInfo->Zulies ); + ADDBYTE( pakt, 0 ); + + // Begin the other clients inventory update + BEGINPACKET( pako, 0x717 ); + ADDQWORD( pako, otherclient->CharInfo->Zulies ); + ADDBYTE( pako, 0 ); + + for (unsigned i=0; i<10; i++) + { + if(thisclient->Trade->trade_count[i] > 0) + { + CItem thisitem = thisclient->items[thisclient->Trade->trade_itemid[i]]; + unsigned newslot = otherclient->GetNewItemSlot( thisitem ); + if(newslot==0xffff) continue; + thisclient->items[thisclient->Trade->trade_itemid[i]].count -= thisclient->Trade->trade_count[i]; + if( thisclient->items[thisclient->Trade->trade_itemid[i]].count<=0) + ClearItem(thisclient->items[thisclient->Trade->trade_itemid[i]]); + otherclient->items[newslot]=thisitem; + otherclient->items[newslot].count = thisclient->Trade->trade_count[i]; + ADDBYTE( pakt, (unsigned char)thisclient->Trade->trade_itemid[i] ); + ADDWORD( pakt, BuildItemHead( thisclient->items[thisclient->Trade->trade_itemid[i]] ) ); + ADDDWORD( pakt, BuildItemData( thisclient->items[thisclient->Trade->trade_itemid[i]] ) ); + ADDBYTE( pako, newslot ); + ADDWORD( pako, BuildItemHead( otherclient->items[newslot] ) ); + ADDDWORD( pako, BuildItemData( otherclient->items[newslot] ) ); + tucount++; + oucount++; + } + if(otherclient->Trade->trade_count[i] > 0) + { + CItem thisitem = otherclient->items[otherclient->Trade->trade_itemid[i]]; + unsigned newslot = thisclient->GetNewItemSlot( thisitem ); + if(newslot==0xffff) continue; + otherclient->items[otherclient->Trade->trade_itemid[i]].count -= otherclient->Trade->trade_count[i]; + if( otherclient->items[otherclient->Trade->trade_itemid[i]].count<=0 ) + ClearItem( otherclient->items[otherclient->Trade->trade_itemid[i]] ); + thisclient->items[newslot]=thisitem; + thisclient->items[newslot].count = otherclient->Trade->trade_count[i]; + ADDBYTE( pako, (unsigned char)otherclient->Trade->trade_itemid[i] ); + ADDWORD( pako, BuildItemHead( otherclient->items[otherclient->Trade->trade_itemid[i]] ) ); + ADDDWORD( pako, BuildItemData( otherclient->items[otherclient->Trade->trade_itemid[i]] ) ); + ADDBYTE( pakt, newslot ); + ADDWORD( pakt, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD( pakt, BuildItemData( thisclient->items[newslot] ) ); + tucount++; + oucount++; + } + } + + // Update itemcount and send + SETBYTE( pakt, 8, tucount ); + SETBYTE( pako, 8, oucount ); + thisclient->client->SendPacket( &pakt ); + otherclient->client->SendPacket( &pako ); + thisclient->Trade->trade_status=0; + otherclient->Trade->trade_status=0; + thisclient->saveinventory(); + otherclient->saveinventory(); + } + break; + case 7: + // TRADE BUSY + break; + case 8: + // TRADE TOO FAR + break; + case 9: + // NOT A VALID TARGET + break; + case 10: + // OUT OF INVENTORY + break; + case 11: + // + default: + break; + } + + return true; +} + +// Trade action +bool CWorldServer::pakTradeAdd ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + BYTE islot = GETBYTE((*P),0); + WORD slotid = GETWORD((*P),1); + DWORD count = GETDWORD((*P),3); + CPlayer* otherclient = GetClientByID( thisclient->Trade->trade_target, thisclient->Position->Map ); + if (otherclient == NULL) return true; + BEGINPACKET( pak, 0x7c1 ); + ADDBYTE( pak, islot ); + if( islot == 0x0a ) + { + ADDWORD( pak, 0xccdf );//0xccccccdf ); + ADDDWORD( pak, count ); + thisclient->Trade->trade_count[islot] = count; + } + else + { + if(islot>0x0a) + return false; + if( count != 0 ) + { + if(count>thisclient->items[slotid].count) + return false; + thisclient->Trade->trade_count[islot] = count; + thisclient->Trade->trade_itemid[islot] = slotid; + CItem tmpitem = thisclient->items[slotid]; tmpitem.count = count; + ADDWORD( pak, BuildItemHead( tmpitem ) ); + ADDDWORD( pak, BuildItemData( tmpitem ) ); + } + else + { + thisclient->Trade->trade_count[islot] = 0; + thisclient->Trade->trade_itemid[islot] = 0; + ADDDWORD( pak, 0 ); + ADDDWORD( pak, 0 ); + } + } + otherclient->client->SendPacket( &pak ); + return true; +} + + +// Attack skill +bool CWorldServer::pakStartSkill ( CPlayer* thisclient, CPacket* P ) +{ + + if( thisclient->Shop->open || thisclient->Status->Muted!=0xff ) + return true; + fPoint thispoint; + unsigned int skillid; + UINT targetid = GETWORD( (*P), 0 ); + BYTE skillnum = GETBYTE( (*P), 2 );//skill inventory number + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill went to pakStartSkill. Skill number = %i", skillnum); + } + if(skillnum >= MAX_SKILL) + { + Log( MSG_HACK, "Invalid Skill id %i for %s ", skillnum, thisclient->CharInfo->charname ); + return false; + } + if (skillnum > 29) + { + skillid = thisclient->askill[skillnum - 30] + thisclient->askilllvl[skillnum - 30]; + } + else + { + skillid = thisclient->bskills[skillnum]; + } + CMap* map = MapList.Index[thisclient->Position->Map]; + CCharacter* character = map->GetCharInMap( targetid ); + if(character == NULL) return true; + + if(character->IsMonster()) + { + BEGINPACKET( pak, 0x79f ); + ADDWORD( pak, character->clientid ); + ADDWORD( pak, character->Stats->HP ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + } + CSkills* thisskill = GetSkillByID( skillid ); + if(thisskill == NULL) + return true; + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill type = %i target = %i",thisskill->skilltype, thisskill->target); + } + if(!thisclient->canUseSkill(thisskill)) + { + SendPM(thisclient, "Sorry. You cannot use that skill"); + return true; + } + if(thisskill->target == 9 && !character->IsDead()) + { + ClearBattle( thisclient->Battle ); + return true; + } + //if( isSkillTargetFriendly( thisskill ) ) + //{ + // if(thisclient->Session->codedebug) //Debug code + // { + // SendPM(thisclient, "Target is friendly. Action = SKILL_BUFF"); + // } + // thisclient->StartAction( character, SKILL_BUFF, skillid ); + //} + //else + //{ + switch(thisskill->skilltype) + { + case 3: case 6: + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Target is UN-friendly. Action = SKILL_ATTACK"); + } + thisclient->StartAction( character, SKILL_ATTACK, skillid ); + break; + case 9: case 11: case 13: + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Target is either friendly or unfriedly. Action = SKILL_BUFF"); + SendPM(thisclient, "My id = %i target id = %i",thisclient->clientid, character->clientid); + } + thisclient->StartAction( character, SKILL_BUFF, skillid ); + break; + case 20: // resurrect + ClearBattle( thisclient->Battle ); + if(thisskill->target == 9) + { + if(character->IsDead()) + { + character->Stats->HP = (int)character->GetMaxHP()/10; + } + return true; + } + break; + default: + SendPM(thisclient, "Unhandled skill request"); + break; + } + //} + return true; +} + +// Self skills +bool CWorldServer::pakSkillSelf( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Skill went to pakSkillSelf" ); + SendPM(thisclient, buffer); + } + if( thisclient->Shop->open || thisclient->Status->Muted!=0xff || !thisclient->Status->CanCastSkill) + return true; + unsigned int skillid; + BYTE num = GETBYTE((*P),0); + if(num >= MAX_SKILL) + { + Log( MSG_HACK, "Invalid Skill id %i for %s ", num, thisclient->CharInfo->charname ); + return false; + } + if (num > 29) + { + skillid = thisclient->askill[num-30]+thisclient->askilllvl[num-30]; + } + else + { + skillid = thisclient->bskills[num]; + } + CSkills* thisskill = GetSkillByID( skillid ); + if(thisskill == NULL) + return true; + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Skill type = %i target = %i",thisskill->skilltype,thisskill->target); + SendPM(thisclient, buffer); + } + if(!thisclient->canUseSkill(thisskill)) + { + SendPM(thisclient, "Sorry. You cannot use that skill"); + return true; + } + unsigned int skilltarget = thisskill->target; + unsigned int skillrange = thisskill->aoeradius; + + if( thisskill->aoe == 0 ) + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 0. Action = BUFF_SELF"); + } + thisclient->StartAction( NULL, BUFF_SELF, skillid ); + } + else + { + if(isSkillTargetFriendly( thisskill )) + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 1 and target is friendly. Action = BUFF_AOE"); + } + thisclient->StartAction( NULL, BUFF_AOE, skillid ); + } + else + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 1 and target is UN-friendly. Action = SKILL_AOE"); + } + thisclient->StartAction( NULL, SKILL_AOE, skillid ); + } + } + return true; +} + +// Aoe Skill +bool CWorldServer::pakSkillAOE( CPlayer* thisclient, CPacket* P) +{ + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Skill went to pakSkillAOE" ); + SendPM(thisclient, buffer); + } + + if( thisclient->Shop->open || thisclient->Status->Muted != 0xff || !thisclient->Status->CanCastSkill) return true; + BYTE num = GETBYTE( (*P), 0 ); + thisclient->Position->skilltargetpos.x = P->GetFloat(1)/100; //these set an absolute aoe center. not a monster. + thisclient->Position->skilltargetpos.y = P->GetFloat(5)/100; + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "target X = %f target Y = %f",thisclient->Position->skilltargetpos.x,thisclient->Position->skilltargetpos.y ); + } + + //arnold + if(num >= MAX_SKILL) + { + Log( MSG_HACK, "Invalid Skill id %i for %s ", num, thisclient->CharInfo->charname ); + return false; + } + unsigned int skillid; + if (num > 29) + { + skillid = thisclient->askill[num-30] + thisclient->askilllvl[num-30]; + } + else + { + skillid = thisclient->bskills[num]; + } + CSkills* thisskill = GetSkillByID( skillid ); + if(thisskill == NULL) return true; + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Skill type = %i target = %i",thisskill->skilltype,thisskill->target); + SendPM(thisclient, buffer); + } + if(!thisclient->canUseSkill(thisskill)) + { + SendPM(thisclient, "Sorry. You cannot use that skill"); + return true; + } + if(thisskill->aoe == 1) + { + if(isSkillTargetFriendly( thisskill )) + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 1 and target is friendly. Action = nothing"); + } + //cout << "Friendly skill: " << thisclient->Battle->skillid << endl; + } + else + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 1 and target is UN-friendly. Action = AOE_TARGET"); + } + thisclient->StartAction( NULL , AOE_TARGET, skillid ); + } + } + else + { + if(thisclient->Session->codedebug) //Debug code + { + SendPM(thisclient, "Skill aoe is 0. Action = nothing"); + } + Log( MSG_INFO, "no aoe, skillid = %i ", thisclient->Battle->skillid); + } + return true; +} + +// Consumible Items +bool CWorldServer::pakUseItem ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + BYTE slot = GETBYTE((*P),0); + if(!CheckInventorySlot( thisclient, slot)) + return false; + if( thisclient->items[slot].count<=0 ) + return true; + CUseInfo* thisuse = GetUseItemInfo( thisclient, slot); + if(thisuse == NULL) + { + Log(MSG_WARNING,"[%i]Invalid Item, Item: %i. - Type: %i", + thisclient->clientid, thisclient->items[slot].itemnum, + thisclient->items[slot].itemtype); + return true; + } + bool flag = false; + switch(thisuse->usescript) + { + case 1: // Food + { + thisclient->UsedItem->usevalue = thisuse->usevalue; + thisclient->UsedItem->usetype = thisuse->usetype; + thisclient->UsedItem->userate = 15; + thisclient->UsedItem->used = 0; + BEGINPACKET( pak,0x7a3 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisuse->itemnum ); + SendToVisible( &pak, thisclient ); + flag = true; + Log(MSG_WARNING, "Food item used. type %i Value %i", thisuse->usetype, thisuse->usevalue ); + } + break; + case 2: // Return Scroll + { + BEGINPACKET( pak,0x7a3 ); + ADDWORD ( pak,thisclient->clientid ); + ADDWORD ( pak, thisuse->itemnum ); + ADDBYTE ( pak,slot ); + thisclient->client->SendPacket( &pak ); + //thisclient->items[slot].count -= 1; + //if( thisclient->items[slot].count == 0 ) + // ClearItem( thisclient->items[slot] ); + fPoint thispoint; + thispoint.x = floor( (double)thisuse->usevalue/10000 ); + thispoint.y = floor( (double)(thisuse->usevalue%10000) ); + TeleportTo ( thisclient, thisuse->usetype, thispoint ); + flag = true; + } + break; + case 3: // Charm Scroll + { + CSkills* thisskill = GetSkillByID( thisuse->usetype ); + if(thisskill == NULL) + { + Log(MSG_WARNING, "Char %s tried to use invalid scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + delete thisuse; + return true; + } + int skilltarget = thisskill->target; + int skillrange = thisskill->aoeradius; + if( thisskill->aoe == 0 ) + { + thisclient->StartAction( NULL,BUFF_SELF,thisuse->usetype ); + } + else + { + if(isSkillTargetFriendly( thisskill )) + { + thisclient->StartAction( NULL,BUFF_AOE,thisuse->usetype ); + } + else + { + thisclient->StartAction( NULL,SKILL_AOE,thisuse->usetype ); + } + } + flag = true; + } + break; + case 4: // Dance Scroll | FireCrackers | Emotion + { + //Start Animation + BEGINPACKET( pak, 0x7b2 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisuse->usevalue ); + SendToVisible( &pak, thisclient ); + //Finish Animation + RESETPACKET( pak, 0x7bb ); + ADDWORD ( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient ); + //???? + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, thisclient->clientid); + ADDWORD ( pak, thisuse->usevalue ); + SendToVisible( &pak, thisclient ); + flag = true; + } + break; + case 12: //Dance scrolls + { + CSkills* thisskill = GetSkillByID( thisuse->usetype ); + if(thisskill == NULL) + { + Log(MSG_WARNING, "Char %s tried to use invalid Dance scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + delete thisuse; + return true; + } + //Log(MSG_INFO, "Char %s used a Dance scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + int skilltarget = thisskill->target; + int skillrange = thisskill->aoeradius; + flag = true; + //Log(MSG_INFO, "Sending skill request. AOE range = %i", skillrange); + if(skillrange > 0 ) //AOE effect + { + thisclient->StartAction( NULL,BUFF_AOE,thisuse->usetype ); + } + else + { + thisclient->StartAction( NULL,BUFF_SELF,thisuse->usetype ); + } + flag = true; + } + break; + case 13: // Fireworks + { + CSkills* thisskill = GetSkillByID( thisuse->usetype ); + if(thisskill == NULL) + { + Log(MSG_WARNING, "Char %s tried to use invalid Dance scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + delete thisuse; + return true; + } + //Log(MSG_INFO, "Char %s used a Dance scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + int skilltarget = thisskill->target; + int skillrange = thisskill->aoeradius; + flag = true; + Log(MSG_INFO, "Sending skill request. AOE range = %i", skillrange); + if(skillrange > 0 ) //AOE effect + { + thisclient->StartAction( NULL,BUFF_AOE,thisuse->usetype ); + } + else + { + Log(MSG_INFO, "Char %s used an invalid firework type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + } + flag = true; + } + break; + case 5: // summon + case 11: // Summon ownerless + { + CNPCData* thisnpc = GetNPCDataByID( thisuse->usevalue ); + if(thisnpc == NULL) + { + Log( MSG_WARNING,"[%i]NPCDATA NOT Founded: %i.", + thisclient->clientid,thisuse->usevalue ); + delete thisuse; + return true; + } + //Start Animation + BEGINPACKET( pak, 0x7b2 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisuse->usetype ); + ADDBYTE ( pak, 6 ); + SendToVisible( &pak, thisclient ); + //Finish Animation + RESETPACKET( pak, 0x7bb ); + ADDWORD ( pak, thisclient->clientid ); + SendToVisible( &pak, thisclient ); + //???? + RESETPACKET( pak, 0x7b9); + ADDWORD ( pak, thisclient->clientid); + ADDWORD ( pak, thisuse->usetype ); + SendToVisible( &pak, thisclient ); + // Add our Mob to the mobs list + fPoint position = RandInCircle( thisclient->Position->current, 5 ); + CMap* map = MapList.Index[thisclient->Position->Map]; + int owner = 0; + if(thisuse->usescript == 5) owner = thisclient->clientid; + map->AddMonster( thisuse->usevalue, position, owner,0,1 ); + flag = true; + } + break; + case 6: // Snowball + { + WORD clientid = GETWORD((*P),2); + fPoint thispoint; + CMap* map = MapList.Index[thisclient->Position->Map]; + CCharacter* character = map->GetCharInMap( clientid ); + if(character == NULL) return true; + thisclient->StartAction( character, SKILL_BUFF, thisuse->usevalue ); + flag = true; + } + break; + case 7: // scrolls [damage,maxhp,...] + { + CSkills* thisskill = GetSkillByID( thisuse->usetype ); + if(thisskill == NULL) + { + Log(MSG_WARNING, "Char %s tried to use invalid scroll type: %i", thisclient->CharInfo->charname, thisuse->usetype ); + delete thisuse; + return true; + } + int skilltarget = thisskill->target; + int skillrange = thisskill->aoeradius; + if( thisskill->aoe == 0 ) + { + thisclient->StartAction( NULL, BUFF_SELF, thisuse->usetype ); + } + else + { + if(isSkillTargetFriendly( thisskill )) + { + thisclient->StartAction( NULL, BUFF_AOE, thisuse->usetype ); + } + else + { + thisclient->StartAction( NULL, SKILL_AOE, thisuse->usetype ); + } + } + flag = true; + } + break; + case 10: // Skill Book + { + flag = LearnSkill( thisclient, thisuse->usevalue ); + } + break; + case 14: // Fairy scroll + { + GServer->Config.ScrollFairy ++; //increase count of scroll fairies by one + int oldFairyMax = GServer->Config.FairyMax; + GServer->Config.FairyMax = (int)ceil((float)GServer->ClientList.size() / 50.0) + GServer->Config.ScrollFairy; //recalculate the maximum number of fairies + if( oldFairyMax < GServer->Config.FairyMax ) // if we increased the fairy max we need to make a dummy fairy to hold the space or we will crash the server + { + CFairy* thisfairy = new (nothrow) CFairy; + thisfairy->LastTime = clock(); + thisfairy->assigned = 0; + thisfairy->ListIndex = 0; + thisfairy->WaitTime = GServer->Config.FairyWait; + GServer->FairyList.push_back( thisfairy ); + } + for (int i=0; iassigned == false) // we found an empty fairy so let's assign it + { + FairyList.at(i)->ListIndex = 0; + FairyList.at(i)->LastTime = clock(); + FairyList.at(i)->assigned = true; + FairyList.at(i)->scroll = true; // Scroll value set to true. this IS a scroll buff fairy + thisclient->Fairy = true; + thisclient->FairyListIndex = i; // FairyList index number of our actual fairy + DoFairyStuff(thisclient, 1); // spawn fairy to target + thisclient->SetStats(); + flag = true; + break; + } + } + + break; + } + } + if(flag == true) + { + BEGINPACKET( pak,0x7a3 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisuse->itemnum ); + ADDBYTE ( pak, slot ); + thisclient->client->SendPacket( &pak ); + thisclient->items[slot].count -= 1; + if( thisclient->items[slot].count <= 0 ) + ClearItem( thisclient->items[slot] ); + thisclient->saveinventory(); + } + delete thisuse; + return true; +} + +// Level UP Skill +bool CWorldServer::pakLevelUpSkill( CPlayer *thisclient, CPacket* P ) +{ + CSkills* thisskill; + CSkills* nextskill; + char rs = 0; + char rl = 0; + int pos = GETBYTE ((*P),0); + // Check that skill is valid + if (pos > 59) + { // Passive Skill - ID + 60 + thisskill = GetSkillByID((thisclient->pskill[pos-60])+(thisclient->pskilllvl[pos-60])); + nextskill = GetSkillByID(((thisclient->pskill[pos-60])+(thisclient->pskilllvl[pos-60]))+1); + } + else if (pos > 29) + { // Active Skill - ID + 30 + thisskill = GetSkillByID((thisclient->askill[pos-30])+(thisclient->askilllvl[pos-30])); + nextskill = GetSkillByID(((thisclient->askill[pos-30])+(thisclient->askilllvl[pos-30]))+1); + } + else return true; + // Make sure skill is found + if (thisskill == NULL || nextskill == NULL) return true; + if(thisskill->skillid != nextskill->skillid) return true; //if skillid is different then we have gone off the end of the skill levels + + // Check SP requirement + if (thisclient->CharInfo->SkillPoints < nextskill->sp) { + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x02 ); + thisclient->client->SendPacket( &pak ); + return true; + } + + // Check zuly requirement + if (thisclient->CharInfo->Zulies < nextskill->zuly) { + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x01 ); + thisclient->client->SendPacket( &pak ); + return true; + } + + // Check both stat requirements. + bool fail = false; + for (char i = 0; i < 2; i++) + { + switch (nextskill->req[i]) + { + case sLevel: + if (nextskill->reqam[i] > thisclient->Stats->Level) fail = true; + break; + case sStrength: + if (nextskill->reqam[i] > thisclient->Attr->Str) fail = true; + break; + case sDexterity: + if (nextskill->reqam[i] > thisclient->Attr->Dex) fail = true; + break; + case sIntelligence: + if (nextskill->reqam[i] > thisclient->Attr->Int) fail = true; + break; + case sConcentration: + if (nextskill->reqam[i] > thisclient->Attr->Con) fail = true; + break; + case sCharm: + if (nextskill->reqam[i] > thisclient->Attr->Cha) fail = true; + break; + case sSensibility: + if (nextskill->reqam[i] > thisclient->Attr->Sen) fail = true; + break; + default: + break; + } + } + + if (fail) + { + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x03 ); + thisclient->client->SendPacket( &pak ); + return true; + } + + // Check class requirement + bool passClass = false; + int nocheck = 4; // Used to check if no lvl check + for (char i = 0; i < 4; i++) + { + if (nextskill->c_class[i]>0) + { + switch(nextskill->c_class[i]) + { + case cSoldierJob: + if((thisclient->CharInfo->Job)/100==1)passClass = true; + break; + case cMuseJob: + if((thisclient->CharInfo->Job)/100==2)passClass = true; + break; + case cHawkerJob: + if((thisclient->CharInfo->Job)/100==3)passClass = true; + break; + case cDealerJob: + if((thisclient->CharInfo->Job)/100==4)passClass = true; + break; + case cKnightJob: + if(thisclient->CharInfo->Job==121)passClass = true; + break; + case cChampionJob: + if(thisclient->CharInfo->Job==122)passClass = true; + break; + case cMageJob: + if(thisclient->CharInfo->Job==221)passClass = true; + break; + case cClericJob: + if(thisclient->CharInfo->Job==222)passClass = true; + break; + case cRaiderJob: + if(thisclient->CharInfo->Job==321)passClass = true; + break; + case cScoutJob: + if(thisclient->CharInfo->Job==322)passClass = true; + break; + case cBourgeoisJob: + if(thisclient->CharInfo->Job==421)passClass = true; + break; + case cArtisanJob: + if(thisclient->CharInfo->Job==422)passClass = true; + break; + default: + break; + } + if (passClass) break; + } else + nocheck--; + } + // Player isn't the required class/job + if (!passClass && nocheck > 0) + { + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x04 ); + thisclient->client->SendPacket( &pak ); + return true; + } + if(thisclient->Session->codedebug) //Debug code + { + char buffer[200]; + sprintf ( buffer, "Passed check for class" ); + SendPM(thisclient, buffer); + } + // Check required skills/skill levels + for (char j = 0; j < 3 ; j++) + { + if(nextskill->rskill[j]>0) + { + rs++; + for (char i = 0; i<30;i++) + { + // Subtract 1 because our characters levels are actually off by 1 + // Check passive skill + if ( ( thisclient->pskill[i] == nextskill->rskill[j] ) && + ( thisclient->pskilllvl[i] >= nextskill->lskill[j] - 1 ) ) + { + rl++; + break; // No use continueing for the rest of the for loop, right? + } + // Check Active Skill + if ( ( thisclient->askill[i] == nextskill->rskill[j] ) && + ( thisclient->askilllvl[i] >= nextskill->lskill[j] - 1 ) ) + { + rl++; + break; // No need to continue inner loop + } + } + } + } + + if(rl != rs) + { + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x05 ); + thisclient->client->SendPacket( &pak ); + return true; + } + + if (pos > 59) thisclient->pskilllvl[pos-60] += 1; + else if (pos > 29) thisclient->askilllvl[pos-30] += 1; + thisclient->CharInfo->SkillPoints -= nextskill->sp; + thisclient->CharInfo->Zulies -= nextskill->zuly; + + BEGINPACKET( pak, 0x7b1 ); + ADDBYTE( pak, 0x00 ); + ADDBYTE( pak, GETBYTE ((*P),0) ); + ADDWORD( pak, GETWORD ((*P),1) ); + ADDWORD( pak, thisclient->CharInfo->SkillPoints ); + thisclient->client->SendPacket( &pak ); + thisclient->SetStats(); + return true; +} + +// Equip bullets arrows and cannons +bool CWorldServer::pakEquipABC ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + BYTE itemslot = GETBYTE((*P),0); + int slot = ((itemslot-32)/4)+72; + int dest = 0; + int type = (itemslot-32)%4; + if(!CheckInventorySlot( thisclient, slot)) + return false; + if(itemslot>3) + { + dest = type+132; + } + else + { + slot = itemslot + 132; + dest = thisclient->GetNewItemSlot( thisclient->items[slot] ); + if(dest==0xffff) + return true; + type=300; + } + CItem tmpitm = thisclient->items[slot]; + thisclient->items[slot] =thisclient->items[dest]; + thisclient->items[dest] = tmpitm; + + thisclient->UpdateInventory( dest, slot ); + BEGINPACKET( pak, 0x7ab ); + ADDWORD ( pak, thisclient->clientid ); + unsigned int effect = ( thisclient->items[dest].itemnum * 32 ) + type; + ADDWORD ( pak, effect ); + SendToVisible( &pak, thisclient ); + thisclient->Stats->Attack_Power = thisclient->GetAttackPower( ); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Craft item (Template by Arnold) (Crafting modifications by: Killerfly) Fixed for irose by: Arnold // +// Recoded for osProse by PurpleYouko // +////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool CWorldServer::pakCraft( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open==true) + return true; + CItem item; + UINT quality = 0; + int m = 0; + + // determine the item number and type + item.itemnum = (GETWORD((*P), 2)); + item.itemtype = (GETBYTE((*P), 0x1)); + + //get material used lookup number + int materialnumber = 0; + if(item.itemtype < 10) + { + materialnumber = EquipList[item.itemtype].Index[item.itemnum]->material; + } + else + { + switch(item.itemtype) + { + case 11:materialnumber = JemList.Index[item.itemnum]->material;break; + case 14:materialnumber = PatList.Index[item.itemnum]->material;break; + default: + break; + } + } + if(thisclient->Session->codedebug) + { + SendPM(thisclient, "Attempting to craft an item. type = %i. id = %i",item.itemtype, item.itemnum); + SendPM(thisclient, "Material number = %i",materialnumber); + } + //check material slots before we start making the new item + if(ProductList.Index[materialnumber]->matType != 0) // recipe has an item type listed rather than a specific item + { + WORD material= GETWORD((*P), 4); + UINT matid = thisclient->items[material].itemnum; + quality = NaturalList.Index[matid]->quality; + if(thisclient->Session->codedebug) + { + SendPM(thisclient, "Recipe recquires an item with type = %i.",ProductList.Index[materialnumber]->matType); + SendPM(thisclient, "Type of material in slot = %i",thisclient->items[material].type); + } + if(ProductList.Index[materialnumber]->matType != thisclient->items[material].type) + { + if(thisclient->Session->codedebug) + { + SendPM(thisclient, "Rejected as mat types do not match"); + } + return true; //item in the slot is the wrong type + } + } + m=0; + for(char used=4; used != 12; used +=2) + { + WORD material= GETWORD((*P), used);//gets inventory location + if(thisclient->Session->codedebug) + { + SendPM(thisclient, "Slot number = %i", material); + SendPM(thisclient, "Recipe recquires an item with type = %i. id = %i",ProductList.Index[materialnumber]->itemtype[m],ProductList.Index[materialnumber]->itemid[m]); + SendPM(thisclient, "Type of material in slot = %i. id = %i",thisclient->items[material].itemtype,thisclient->items[material].itemnum); + SendPM(thisclient, "Material count required = %i", ProductList.Index[materialnumber]->amount[m]); + SendPM(thisclient, "Material count in slot = %i", thisclient->items[material].count); + } + if(!CheckInventorySlot( thisclient, material)) + { + SendPM(thisclient, "Crafting Error 0: Missing or incorrect material in inventory slot."); + return true; + } + if (material != 0) + { + if(ProductList.Index[materialnumber]->matType != 0 && m == 0) //only checked for the first item in the recipe + { + if(thisclient->items[material].type != ProductList.Index[materialnumber]->matType) + { + SendPM(thisclient, "Crafting Error %i 1: Server and Client crafting recipes do not match",m); + return true; // item type does not match recipe + } + } + else + { + if(thisclient->items[material].itemtype != ProductList.Index[materialnumber]->itemtype[m]) + { + SendPM(thisclient, "Crafting Error %i 2: Server and Client crafting recipes do not match",m); + return true; // item type does not match recipe + } + if(thisclient->items[material].itemnum != ProductList.Index[materialnumber]->itemid[m]) + { + SendPM(thisclient, "Crafting Error %i 3: Server and Client crafting recipes do not match",m); + return true; // item id does not match recipe + } + } + if(thisclient->items[material].count < ProductList.Index[materialnumber]->amount[m]) + { + SendPM(thisclient, "Crafting Error %i 4: Server and Client crafting recipes do not match",m); + return true; //not enough of the required item + } + } + m += 1; + } + if(thisclient->Session->codedebug) + { + SendPM(thisclient, "Item cleared the checks and will be created"); + } + + // item durability randomizer + int lowest = thisclient->Attr->Con/ 10 + 17; + int highest = thisclient->Attr->Con / 10 + 70; + int range = (highest - lowest) + 1; + item.durability = lowest+int(range*rand()/(RAND_MAX + 1.0)); + + // stats randomizer + int changeofstatslow = thisclient->Attr->Sen / 13 + 10; + int changeofstatshigh = thisclient->Attr->Sen / 13 + 50; + int changeofstatsrange = (changeofstatshigh-changeofstatslow)+1; + if (changeofstatslow+int(changeofstatsrange*rand()/(RAND_MAX + 1.0)) > 50) + { + int statslowget = 1; + int statshighget = 256; + int setstatrange=(statshighget-statslowget)+1; + item.stats = statslowget + int(setstatrange*rand()/(RAND_MAX + 1.0)); + item.appraised = 1; + } + else + { + item.stats = 0; + item.appraised = 0; + } + item.socketed = 0; + item.gem = 0; + item.lifespan = 100; //Its new so 100% + item.refine = 0; + item.count = 1; + + + unsigned newslot= thisclient->GetNewItemSlot( item ); + if (newslot !=0xffff) + { + //removes material items from the slots + item.count +=(thisclient->items[newslot].count); + thisclient->items[newslot] = item; + m=0; + for(char used=4; used != 12; used +=2) + { + WORD material= GETWORD((*P), used);//gets inventory location + if (material != 0) + { + thisclient->items[material].count -= ProductList.Index[materialnumber]->amount[m]; + m += 1; + if (thisclient->items[material].count <=0) + { + ClearItem(thisclient->items[material]); + } + } + } + + BEGINPACKET( pak, 0x07d8); + ADDWORD( pak, thisclient->clientid ); + ADDWORD( pak, 0x0100); + ADDWORD( pak, item.itemnum);// item id not shifted + thisclient->client->SendPacket(&pak); + + RESETPACKET( pak, 0x07af); + ADDBYTE( pak, 0x00); + ADDWORD( pak, newslot); + + // Make craft bars + int bar1 = item.durability * 9; + int bar2 = changeofstatsrange * 9; + int bar3 = item.durability + changeofstatsrange * 6; + int bar4 = item.durability + changeofstatsrange + bar3 / 3; + + ADDWORD( pak, bar1);//progress bar1 0 is empty 0x0400 is full bar + ADDWORD( pak, bar2);//progress bar2 0 is empty 0x0400 is full bar + if ((GETWORD((*P), 8))==0){ ADDWORD( pak, 0x99a0);}else{ ADDWORD( pak, bar3);}//progress bar3 0 is empty 0x0400 is full bar + if ((GETWORD((*P), 10))==0){ ADDWORD( pak, 0x99a0);}else{ ADDWORD( pak, bar4);}//progress bar4 0 is empty 0x0400 is full bar + ADDWORD(pak, BuildItemHead(item)); + + if (item.itemtype == 11) + { + ADDWORD( pak, item.count);// amount + ADDWORD( pak, 0x0000); + } + else + { + ADDDWORD(pak, BuildItemData(item)); + } + int crafting_exp = item.durability + changeofstatsrange * (thisclient->Stats->Level/ 5); + thisclient->CharInfo->Exp += crafting_exp;// add exp + thisclient->client->SendPacket(&pak); + if(!thisclient->CheckPlayerLevelUP( )){ + RESETPACKET( pak, 0x79b ); + ADDDWORD ( pak, thisclient->CharInfo->Exp ); + ADDWORD ( pak, thisclient->CharInfo->stamina ); + ADDWORD ( pak, 0 ); + thisclient->client->SendPacket( &pak );} + } + else + { + BEGINPACKET (pak, 0x702); + ADDSTRING(pak, "No free slot !"); + ADDBYTE(pak, 0); + thisclient->client->SendPacket(&pak); + } + thisclient->saveinventory(); + return true; +} + + +bool CWorldServer::pakModifiedItemDone( CPlayer* thisclient, CPacket* P ) +{ + DWORD result = GETDWORD((*P), 0x00 ); + BEGINPACKET( pak, 0x07d8); + ADDWORD( pak, thisclient->clientid ); + ADDDWORD( pak, result); + SendToVisible( &pak , thisclient); + return true; + +} + + + +// Identify Item +bool CWorldServer::pakidentify( CPlayer* thisclient, CPacket* P) +{ + WORD itemslot = GETWORD ((*P),0); + if(!CheckInventorySlot( thisclient, itemslot)) + return false; + thisclient->items[itemslot].appraised = true; + BEGINPACKET( pak, 0x7ba ); + ADDWORD ( pak, itemslot ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// Show Storge Items +bool CWorldServer::pakStorage( CPlayer* thisclient, CPacket* P) +{ + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 0x00: + { + UINT nstorageitems = thisclient->Getnstorageitems(thisclient); + Log( MSG_INFO, "Number of items in storage: %i ", nstorageitems); + BEGINPACKET( pak, 0x7ad ); + ADDBYTE ( pak, 0x00 ); + ADDBYTE ( pak, nstorageitems ); //numero de items + UINT counter = 0; + for(int i=0;i<160;i++) + { + if( thisclient->storageitems[i].itemtype !=0 && counter < 144) //packet can only handle 144 items + { + ADDBYTE ( pak, i ); + ADDWORD ( pak, BuildItemHead( thisclient->storageitems[i] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->storageitems[i] ) ); + counter ++; + } + } + ADDQWORD( pak, thisclient->CharInfo->Storage_Zulies ); + thisclient->client->SendPacket( &pak ); + } + break; + default: + Log( MSG_INFO, "Storage unknow action: %i ", action); + } + return true; +} + +// Change Storage (Deposit/Withdraw items) +bool CWorldServer::pakChangeStorage( CPlayer* thisclient, CPacket* P) +{ + /* Packet for this function + =========================== + [BYTE] Action + [BYTE] Item Slot + [WORD] Item Head + [DWORD] Item Body + [BYTE] Unk (0x00) + + */ + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 0x00: //Deposit + { + int itemslot = (int)GETBYTE((*P),1); + CItem clientitem = GetItemByHeadAndData( GETWORD((*P), 2), GETDWORD((*P), 4) ); + + if(!CheckInventorySlot( thisclient, itemslot )) + return false; + CItem newitem = thisclient->items[itemslot]; + //if(newitem == NULL) + // return true; + // Set the newitem's count to the count from the client for slot search + newitem.count = clientitem.count; + + Log(MSG_INFO, "New Item: %i %i", newitem.itemtype, newitem.itemnum); + // find an empty or matching slot depending on new item type + int newslot = thisclient->GetNewStorageItemSlot ( newitem ); + if(newslot == 0xffff) + return true; + if(newitem.itemtype >= 10 && newitem.itemtype <= 12) //is it a stackable type + { + int count = clientitem.count; + if(count > thisclient->items[itemslot].count) + count = thisclient->items[itemslot].count; //stops players transferring more than they actually have + newitem.count = count; + + if (thisclient->storageitems[newslot].count > 0) + thisclient->storageitems[newslot].count += newitem.count; //add the new items to the storage slot + else + thisclient->storageitems[newslot] = newitem; + + thisclient->items[itemslot].count -= count; //delete the items from inventory slot + if(thisclient->items[itemslot].count <= 0) + ClearItem(thisclient->items[itemslot]); + } + else + { + // non-stackable + ClearItem(thisclient->items[itemslot]); + thisclient->storageitems[newslot] = newitem; + } + BEGINPACKET( pak, 0x7ae ); + ADDWORD ( pak, itemslot ); + ADDWORD ( pak, newslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[itemslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[itemslot] ) ); + ADDWORD ( pak, BuildItemHead( thisclient->storageitems[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->storageitems[newslot] ) ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + //thisclient->nstorageitems++; //this is pointless and serves no purpose + } + break;//thanks to anon for post that this break was missing + case 0x01: //Withdraw + { + BYTE storageslot = GETBYTE((*P),1); + CItem clientitem = GetItemByHeadAndData( GETWORD((*P), 2), GETDWORD((*P), 4) ); + + if(storageslot >= 160) + { + Log( MSG_HACK, "Invalid storage slot %i from %s", storageslot, thisclient->Session->username ); + return false; + } + CItem newitem = thisclient->storageitems[storageslot]; + newitem.count = clientitem.count; + + int newslot= thisclient->GetNewItemSlot ( newitem ); + if (newslot == 0xffff) + return true; + + if(newitem.itemtype > 9 && newitem.itemtype < 14) + { + WORD count = clientitem.count; + if( count > thisclient->storageitems[storageslot].count ) + count = thisclient->storageitems[storageslot].count; + newitem.count = count; + + if (thisclient->items[newslot].count > 0) + thisclient->items[newslot].count += newitem.count; + else + thisclient->items[newslot] = newitem; + + thisclient->storageitems[storageslot].count -= count; + if(thisclient->storageitems[storageslot].count<=0) + ClearItem(thisclient->storageitems[storageslot]); + } + else + { + ClearItem(thisclient->storageitems[storageslot]); + thisclient->items[newslot] = newitem; + } + + BEGINPACKET( pak, 0x7ae ); + ADDWORD ( pak, newslot ); + ADDWORD ( pak, storageslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[newslot] ) ); + ADDWORD ( pak, BuildItemHead( thisclient->storageitems[storageslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->storageitems[storageslot] ) ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + //thisclient->nstorageitems--; //this is pointless and serves no purpose + } + break; + default: + Log( MSG_INFO, "Storage unknow action: %i ", action); + } + thisclient->saveinventory(); + thisclient->savestorage(); + return true; +} + +// Deposit/Widthdraw zulies +bool CWorldServer::pakStoreZuly( CPlayer* thisclient, CPacket* P) +{ + BYTE action = GETBYTE ((*P), 0 ); + QWORD zuly = GETQWORD ((*P), 1 ); + switch(action) + { + case 0x10://deposit' + { + if(zuly > thisclient->CharInfo->Zulies) + return true; + thisclient->CharInfo->Zulies -= zuly; + thisclient->CharInfo->Storage_Zulies += zuly; + BEGINPACKET( pak, 0x7da ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDQWORD ( pak, thisclient->CharInfo->Storage_Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + } + break; + case 0x11://withdraw + { + if(zuly > thisclient->CharInfo->Storage_Zulies ) + return true; + thisclient->CharInfo->Zulies += zuly; + thisclient->CharInfo->Storage_Zulies -= zuly; + BEGINPACKET( pak, 0x7da ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDQWORD ( pak, thisclient->CharInfo->Storage_Zulies ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + } + break; + default: + { + Log( MSG_INFO, "Storage unknow action: %i ", action); + break; + } + } + thisclient->saveinventory(); + thisclient->savestorage(); + return true; +} + +// Open Shop +bool CWorldServer::pakOpenShop( CPlayer* thisclient, CPacket* P ) +{ +//Log( MSG_INFO, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x ", GETBYTE((*P),0), GETBYTE((*P),1), GETBYTE((*P),2), GETBYTE((*P),3), GETBYTE((*P),4), GETBYTE((*P),5), GETBYTE((*P),6), GETBYTE((*P),7), GETBYTE((*P),8), GETBYTE((*P),9), GETBYTE((*P),10), GETBYTE((*P),11), GETBYTE((*P),12), GETBYTE((*P),13), GETBYTE((*P),14), GETBYTE((*P),15), GETBYTE((*P),16), GETBYTE((*P),17), GETBYTE((*P),18), GETBYTE((*P),19), GETBYTE((*P),20), GETBYTE((*P),21), GETBYTE((*P),22), GETBYTE((*P),23), GETBYTE((*P),24) ); + if( thisclient->Shop->open ) + return true; + BYTE nselling = GETBYTE((*P),0); + BYTE nbuying = GETBYTE((*P),1); + if(nselling>30 || nbuying>30) + return true; + int nchar = ((nselling + nbuying) * 11 )+2; + strncpy_s(thisclient->Shop->name ,(char*)&(*P).Buffer[nchar] , P->Size-nchar ); + thisclient->Shop->Selling = nselling; + thisclient->Shop->Buying = nbuying; + for(int i=0;iShop->SellingList[i].slot = slot; + if( thisclient->items[slot].itemtype>9 && thisclient->items[slot].itemtype<14 ) + thisclient->Shop->SellingList[i].count = GETWORD((*P),n+3); + else + thisclient->Shop->SellingList[i].count = 1; + thisclient->Shop->SellingList[i].price = GETDWORD((*P),n+7); + } + for(int i=0;iShop->BuyingList[slot].slot = slot; + DWORD head = GETWORD((*P),n+1); + DWORD data = GETDWORD((*P),n+3); + CItem thisitem = GetItemByHeadAndData( head, data ); + thisclient->Shop->BuyingList[slot].item = thisitem; + if( thisitem.itemtype > 9 && thisitem.itemtype < 14 ) + thisclient->Shop->BuyingList[slot].count = GETWORD((*P),n+3); + else + thisclient->Shop->BuyingList[slot].count = 1; + thisclient->Shop->BuyingList[slot].price = GETDWORD((*P),n+7); + } + BEGINPACKET( pak, 0x796 ); + ADDWORD ( pak, thisclient->clientid ); + ADDFLOAT ( pak, thisclient->Position->current.x ); + ADDFLOAT ( pak, thisclient->Position->current.y ); + ADDWORD ( pak, 0x9057 );//>??? + SendToVisible( &pak, thisclient ); + + RESETPACKET( pak, 0x7c2 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, thisclient->Shop->ShopType ); + ADDSTRING ( pak, thisclient->Shop->name ); + ADDBYTE ( pak, 0x00 ); + SendToVisible( &pak, thisclient ); + thisclient->Shop->open = true; + return true; +} + +// Show Shop to other players +bool CWorldServer::pakShowShop( CPlayer* thisclient, CPacket* P ) +{ + WORD otherclientid = GETWORD((*P),0); + CPlayer* otherclient = GetClientByID ( otherclientid, thisclient->Position->Map ); + if(otherclient == NULL) + return true; + BEGINPACKET( pak, 0x7c4 ); + ADDBYTE ( pak, otherclient->Shop->Selling ); + ADDBYTE ( pak, otherclient->Shop->Buying ); + for(unsigned int i = 0; iShop->Selling;i++) + { + CItem thisitem = otherclient->items[otherclient->Shop->SellingList[i].slot]; + thisitem.count = otherclient->Shop->SellingList[i].count; + ADDBYTE ( pak, i ); + ADDWORD ( pak, BuildItemHead( thisitem ) ); + ADDDWORD ( pak, BuildItemData( thisitem ) ); + ADDDWORD ( pak, otherclient->Shop->SellingList[i].price ); + } + for(unsigned int i = 0; i<30;i++) + { + if(otherclient->Shop->BuyingList[i].count>0) + { + CItem thisitem = otherclient->Shop->BuyingList[i].item; + ADDBYTE ( pak, i ); + ADDWORD ( pak, BuildItemHead( thisitem ) ); + ADDDWORD ( pak, BuildItemData( thisitem ) ); + ADDDWORD ( pak, otherclient->Shop->BuyingList[i].price ); + } + } + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// Buy From Shop +bool CWorldServer::pakBuyShop( CPlayer* thisclient, CPacket* P ) +{ + // Log( MSG_INFO, "Size:%04x", P->Size ); + // Log( MSG_INFO, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x ", GETBYTE((*P),0), GETBYTE((*P),1), GETBYTE((*P),2), GETBYTE((*P),3), GETBYTE((*P),4), GETBYTE((*P),5), GETBYTE((*P),6), GETBYTE((*P),7), GETBYTE((*P),8), GETBYTE((*P),9), GETBYTE((*P),10), GETBYTE((*P),11), GETBYTE((*P),12), GETBYTE((*P),13), GETBYTE((*P),14), GETBYTE((*P),15), GETBYTE((*P),16), GETBYTE((*P),17), GETBYTE((*P),18), GETBYTE((*P),19), GETBYTE((*P),20), GETBYTE((*P),21), GETBYTE((*P),22), GETBYTE((*P),23), GETBYTE((*P),24) ); + + WORD otherclientid = GETWORD((*P),0); + BYTE action = GETBYTE((*P),2); + switch(action) + { + case 0x01://check this + { + CPlayer* otherclient = GetClientByID( otherclientid, thisclient->Position->Map ); + if( otherclient == NULL ) + return true; + BYTE slot = GETBYTE((*P),3); + unsigned int count = 0; + unsigned int invslot = otherclient->Shop->SellingList[slot].slot; + CItem newitem = otherclient->items[invslot]; + if(otherclient->items[invslot].itemtype>9 && + otherclient->items[invslot].itemtype<14) + count = GETWORD((*P),6); + else + count = 1; + if( count > otherclient->Shop->SellingList[slot].count ) + return true; + newitem.count = count; + if( thisclient->CharInfo->Zulies<(otherclient->Shop->SellingList[slot].price*count) ) + return true; + unsigned int newslot = thisclient->GetNewItemSlot ( newitem ); + if(newslot==0xffff) + return true; + thisclient->CharInfo->Zulies -= (otherclient->Shop->SellingList[slot].price*count); + otherclient->CharInfo->Zulies += (otherclient->Shop->SellingList[slot].price*count); + if(otherclient->items[invslot].itemtype>9 && + otherclient->items[invslot].itemtype<14) + { + if(otherclient->items[invslot].count<=count) + { + ClearItem(otherclient->items[invslot]) + otherclient->Shop->SellingList[slot].slot = 0; + otherclient->Shop->SellingList[slot].count = 0; + otherclient->Shop->SellingList[slot].price = 0; + } + else + { + if(otherclient->Shop->SellingList[slot].count<=count) + { + otherclient->Shop->SellingList[slot].slot = 0; + otherclient->Shop->SellingList[slot].count = 0; + otherclient->Shop->SellingList[slot].price = 0; + } + else + { + otherclient->Shop->SellingList[slot].count -= count; + } + otherclient->items[invslot].count-=count; + } + } + else + { + ClearItem( otherclient->items[invslot] ) + otherclient->Shop->SellingList[slot].slot = 0; + otherclient->Shop->SellingList[slot].count = 0; + otherclient->Shop->SellingList[slot].price = 0; + } + if(thisclient->items[newslot].count!=0) + thisclient->items[newslot].count += newitem.count; + else + thisclient->items[newslot] = newitem; + //update inventory (buyer) + BEGINPACKET( pak, 0x7c7 ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x01 ); //buy action + ADDBYTE ( pak, newslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[newslot] ) ); + thisclient->client->SendPacket( &pak ); + + //update inventory (seller) + RESETPACKET( pak, 0x7c7 ); + ADDQWORD ( pak, otherclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x01 ); //buy action + ADDBYTE ( pak, invslot ); + ADDWORD ( pak, BuildItemHead( otherclient->items[invslot] ) ); + ADDDWORD ( pak, BuildItemData( otherclient->items[invslot] ) ); + otherclient->client->SendPacket( &pak ); + + //update shop + RESETPACKET( pak, 0x7c6 ); + ADDWORD ( pak, otherclient->clientid ); + ADDBYTE ( pak, 0x05 ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, slot ); + if(otherclient->Shop->SellingList[slot].count<=0) + { + ADDDWORD ( pak, 0x00000000 ); + ADDDWORD ( pak, 0x00000000 ); + } + else + { + ADDWORD ( pak, BuildItemHead( otherclient->items[invslot] )); + ADDDWORD ( pak, BuildItemData( otherclient->items[invslot] )); + } + SendToVisible( &pak, otherclient ); + } + break; + default: + Log( MSG_WARNING, "Buy unknow action: %i", action ); + } + return true; +} + +// Sell in Shop +bool CWorldServer::pakSellShop( CPlayer* thisclient, CPacket* P ) +{ + //Log( MSG_INFO, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x ", GETBYTE((*P),0), GETBYTE((*P),1), GETBYTE((*P),2), GETBYTE((*P),3), GETBYTE((*P),4), GETBYTE((*P),5), GETBYTE((*P),6), GETBYTE((*P),7), GETBYTE((*P),8), GETBYTE((*P),9), GETBYTE((*P),10), GETBYTE((*P),11), GETBYTE((*P),12), GETBYTE((*P),13), GETBYTE((*P),14), GETBYTE((*P),15), GETBYTE((*P),16), GETBYTE((*P),17), GETBYTE((*P),18), GETBYTE((*P),19), GETBYTE((*P),20), GETBYTE((*P),21), GETBYTE((*P),22), GETBYTE((*P),23), GETBYTE((*P),24) ); + + WORD otherclientid = GETWORD((*P),0); + BYTE action = GETBYTE((*P),2); + switch(action) + { + case 0x01: + { + CPlayer* otherclient = GetClientByID( otherclientid, thisclient->Position->Map ); + if( otherclient == NULL ) + return true; + unsigned int count = 0; + BYTE invslot = GETBYTE((*P),3); + BYTE slot = GETBYTE((*P),4); + if(!CheckInventorySlot( thisclient, slot)) + return false; + CItem newitem = thisclient->items[invslot]; + if(thisclient->items[invslot].itemtype>9 && thisclient->items[invslot].itemtype<14) + count = GETWORD((*P),7); + else + count = 1; + newitem.count = count; + if( otherclient->CharInfo->Zulies<(otherclient->Shop->BuyingList[slot].price*count) ) + return true; + unsigned int newslot = otherclient->GetNewItemSlot ( newitem ); + if( newslot==0xffff ) + return true; + if( thisclient->items[invslot].countCharInfo->Zulies += (otherclient->Shop->BuyingList[slot].price*count); + otherclient->CharInfo->Zulies -= (otherclient->Shop->BuyingList[slot].price*count); + if(thisclient->items[invslot].itemtype>9 && + thisclient->items[invslot].itemtype<14) + { + if(thisclient->items[invslot].count<=count) + { + ClearItem( thisclient->items[invslot] ); + if(count>=otherclient->Shop->BuyingList[slot].count) + { + ClearItem( otherclient->Shop->BuyingList[slot].item ); + otherclient->Shop->BuyingList[slot].slot = 0; + otherclient->Shop->BuyingList[slot].count = 0; + otherclient->Shop->BuyingList[slot].price = 0; + } + else + { + otherclient->Shop->BuyingList[slot].count -= count; + } + } + else + { + if(otherclient->Shop->BuyingList[slot].count<=count) + { + ClearItem( otherclient->Shop->BuyingList[slot].item ); + otherclient->Shop->BuyingList[slot].slot = 0; + otherclient->Shop->BuyingList[slot].count = 0; + otherclient->Shop->BuyingList[slot].price = 0; + } + else + { + otherclient->Shop->BuyingList[slot].count -= count; + } + thisclient->items[invslot].count-=count; + } + } + else + { + ClearItem( thisclient->items[invslot] ) + ClearItem( otherclient->Shop->BuyingList[slot].item ); + otherclient->Shop->BuyingList[slot].slot = 0; + otherclient->Shop->BuyingList[slot].count = 0; + otherclient->Shop->BuyingList[slot].price = 0; + } + if(otherclient->items[newslot].count!=0) + otherclient->items[newslot].count += newitem.count; + else + otherclient->items[newslot] = newitem; + + //update inventory (seller) + BEGINPACKET( pak, 0x7c7 ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, invslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[invslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[invslot] ) ); + thisclient->client->SendPacket( &pak ); + + //update inventory (buyer) + RESETPACKET( pak, 0x7c7 ); + ADDQWORD ( pak, otherclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, newslot ); + ADDWORD ( pak, BuildItemHead( otherclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( otherclient->items[newslot] ) ); + otherclient->client->SendPacket( &pak ); + + //update shop + CItem thisitem = otherclient->Shop->BuyingList[slot].item; + RESETPACKET( pak, 0x7c6 ); + ADDWORD ( pak, otherclient->clientid ); + ADDBYTE ( pak, 0x07 ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, slot ); + ADDWORD ( pak, BuildItemHead( thisitem ) ); + ADDDWORD ( pak, BuildItemData( thisitem ) ); + SendToVisible( &pak, otherclient ); + } + break; + default: + Log( MSG_WARNING, "Sell unknow action: %i", action ); + } + return true; +} + +// Close Shop +bool CWorldServer::pakCloseShop( CPlayer* thisclient, CPacket* P ) +{ + BEGINPACKET( pak, 0x7c3 ); + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, 0x00 ); + SendToVisible( &pak, thisclient ); + thisclient->Shop->open = false; + for(unsigned int j=0;j<30;j++) + { + thisclient->Shop->SellingList[j].slot = 0; + thisclient->Shop->SellingList[j].count = 0; + thisclient->Shop->SellingList[j].price = 0; + thisclient->Shop->BuyingList[j].slot = 0; + thisclient->Shop->BuyingList[j].count = 0; + thisclient->Shop->BuyingList[j].price = 0; + ClearItem( thisclient->Shop->BuyingList[j].item ); + + } + return true; +} + +// Modified item (put gem, refine, Drill ) +bool CWorldServer::pakModifiedItem( CPlayer* thisclient, CPacket* P ) +{ + printf("[DEBUG]: "); + for (int i = 0;i<(P->Size-6);i++) + { + printf("%i ",GETBYTE((*P),i)); + } + printf("\n"); + if(thisclient->Shop->open==true) + return true; + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 0x01://Gemming + { + BYTE destslot = GETBYTE((*P),1); + BYTE srcslot = GETBYTE((*P),2); + if(!CheckInventorySlot( thisclient, destslot)) + return false; + if(!CheckInventorySlot( thisclient, srcslot)) + return false; + if( thisclient->items[srcslot].count<=0 ) + { + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x12 ); // dont have requerid material + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; + } + if( thisclient->items[destslot].gem!=0 ) + { + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x02 ); //no socket available + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; + } + thisclient->items[destslot].gem = thisclient->items[srcslot].itemnum; + thisclient->items[destslot].stats = thisclient->items[srcslot].itemnum; + thisclient->items[srcslot].count--; + if(thisclient->items[srcslot].count<1) + ClearItem( thisclient->items[srcslot] ); + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x01 );//gemming success + ADDBYTE ( pak, 0x02 ); + ADDBYTE ( pak, destslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[destslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[destslot] ) ); + ADDBYTE ( pak, srcslot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[srcslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[srcslot] ) ); + thisclient->client->SendPacket( &pak ); + thisclient->SetStats( ); + thisclient->saveinventory(); + } + break; //case 0x02 ( Surprise gift box ) + case 0x02: //itemdivide by user + case 0x03: //itemdivide by npc + { + BYTE slot = GETBYTE((*P),3); + if(!CheckInventorySlot( thisclient, slot)) + { + SendPM(thisclient, "Sorry! Checkinventory returned false for slot %i", slot); + return true; + } + BEGINPACKET( pak, 0x7bc ) + ADDBYTE ( pak, 0x07 );//successful + ADDBYTE ( pak, 0x01 ); + CItem splititem; + char updateteller = 0; + UINT material_lookup_number = STB_ITEM[(thisclient->items[slot].itemtype)-1].rows[thisclient->items[slot].itemnum][14]; + UINT CraftLevel = STB_ITEM[(thisclient->items[slot].itemtype)-1].rows[thisclient->items[slot].itemnum][13]; + SendPM(thisclient, "Material lookup number = %i Craft Level = %i", material_lookup_number,CraftLevel); + + //first item + int matType = ProductList.Index[material_lookup_number]->matType; + int item1 = ProductList.Index[material_lookup_number]->itemid[0]; + int itemtype1 = ProductList.Index[material_lookup_number]->itemtype[0]; + int itemcount1 = ProductList.Index[material_lookup_number]->amount[0]; + int itemcount2 = ProductList.Index[material_lookup_number]->amount[1]; + int itemcount3 = ProductList.Index[material_lookup_number]->amount[2]; + int itemcount4 = ProductList.Index[material_lookup_number]->amount[3]; + unsigned slot1 = 0; + unsigned slot2 = 0; + unsigned slot3 = 0; + unsigned slot4 = 0; + if(matType != 0) + { + // we have to find one of the correct item types rather than just a specific item. Seems to be related to craft skill level + SendPM(thisclient, "item matType = %i", matType); + splititem.itemtype = 12; // always seems to be a natural item here + // Search the natural list and return the nth item in the list where n is the craft level of the item + splititem.itemnum = GetMatIDfromRecipe (CraftLevel, matType); + if(splititem.itemnum == 0) + { + SendPM(thisclient, "Item number returned as zero"); + return true; + } + SendPM(thisclient, "Item number = %i type = %i", splititem.itemnum, splititem.itemtype); + } + else + { + splititem.itemtype = itemtype1; + splititem.itemnum = item1; + //need to adjust the count according to some rules + } + splititem.count = itemcount1; + SendPM(thisclient, "Item count = %i", splititem.count); + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + slot1 = thisclient->GetNewItemSlot( splititem ); + if (slot1 != 0xffff) + { + splititem.count += (thisclient->items[slot1].count); + thisclient->items[slot1] = splititem; + ADDBYTE ( pak, slot1); + ADDWORD ( pak, BuildItemHead( thisclient->items[slot1] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot1] ) ); + updateteller += 1; + } + else + { + thisclient->items[slot1].count -= itemcount1; + if(thisclient->items[slot1].count < 1) + ClearItem( thisclient->items[slot1] ); + SendPM(thisclient, "Not enough free slots"); + return true; + } + + //second item + SendPM(thisclient, "Second item now"); + splititem.itemnum = ProductList.Index[material_lookup_number]->itemid[1]; + splititem.itemtype = ProductList.Index[material_lookup_number]->itemtype[1]; + splititem.count = ProductList.Index[material_lookup_number]->amount[1]; + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + SendPM(thisclient, "Item number = %i type = %i", splititem.itemnum, splititem.itemtype); + slot2 = thisclient->GetNewItemSlot( splititem ); + if (slot2 != 0xffff) + { + splititem.count +=(thisclient->items[slot2].count); + thisclient->items[slot2] = splititem; + ADDBYTE ( pak, slot2); + ADDWORD ( pak, BuildItemHead( thisclient->items[slot2] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot2] ) ); + updateteller += 1; + } + else + { + thisclient->items[slot1].count -= itemcount1; + if(thisclient->items[slot1].count < 1) + ClearItem( thisclient->items[slot1] ); + thisclient->items[slot2].count -= itemcount2; + if(thisclient->items[slot2].count < 1) + ClearItem( thisclient->items[slot2] ); + SendPM(thisclient, "Not enough free slots"); + return true; + } + + //third item (if it exists) + if(ProductList.Index[material_lookup_number]->amount[2] !=0 ) + { + SendPM(thisclient, "Third item now"); + splititem.itemnum = ProductList.Index[material_lookup_number]->itemid[2]; + splititem.itemtype = ProductList.Index[material_lookup_number]->itemtype[2]; + splititem.count = ProductList.Index[material_lookup_number]->amount[2]; + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + SendPM(thisclient, "Item number = %i type = %i", splititem.itemnum, splititem.itemtype); + slot3 = thisclient->GetNewItemSlot( splititem ); + if (slot3 != 0xffff) + { + splititem.count +=(thisclient->items[slot3].count); + thisclient->items[slot3] = splititem; + ADDBYTE ( pak, slot3); + ADDWORD ( pak, BuildItemHead( thisclient->items[slot3] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot3] ) ); + updateteller += 1; + } + else + { + thisclient->items[slot1].count -= itemcount1; + if(thisclient->items[slot1].count < 1) + ClearItem( thisclient->items[slot1] ); + thisclient->items[slot2].count -= itemcount2; + if(thisclient->items[slot2].count < 1) + ClearItem( thisclient->items[slot2] ); + thisclient->items[slot3].count -= itemcount3; + if(thisclient->items[slot3].count < 1) + ClearItem( thisclient->items[slot3] ); + SendPM(thisclient, "Not enough free slots"); + return true; + } + } + + //fourth item + if(ProductList.Index[material_lookup_number]->amount[3] !=0 ) + { + splititem.itemnum = ProductList.Index[material_lookup_number]->itemid[3]; + splititem.itemtype = ProductList.Index[material_lookup_number]->itemtype[3]; + splititem.count = ProductList.Index[material_lookup_number]->amount[3]; + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + slot4 = thisclient->GetNewItemSlot( splititem ); + if (slot4 !=0xffff) + { + splititem.count +=(thisclient->items[slot4].count); + thisclient->items[slot4] = splititem; + ADDBYTE ( pak, slot4); + ADDWORD ( pak, BuildItemHead( thisclient->items[slot4] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot4] ) ); + updateteller += 1; + } + else + { + thisclient->items[slot1].count -= itemcount1; + if(thisclient->items[slot1].count < 1) + ClearItem( thisclient->items[slot1] ); + thisclient->items[slot2].count -= itemcount2; + if(thisclient->items[slot2].count < 1) + ClearItem( thisclient->items[slot2] ); + thisclient->items[slot3].count -= itemcount3; + if(thisclient->items[slot3].count < 1) + ClearItem( thisclient->items[slot3] ); + thisclient->items[slot4].count -= itemcount4; + if(thisclient->items[slot4].count < 1) + ClearItem( thisclient->items[slot4] ); + SendPM(thisclient, "Not enough free slots"); + return true; + } + } + + /* + for(unsigned int i=0;iitem[0]) + { + splititem.itemtype = 12; + splititem.itemnum = i; + splititem.count = ProductList.Index[material_lookup_number]->amount[0]; + if(splititem.count > 1) + { + splititem.count/=2; + } + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + unsigned newslot = thisclient->GetNewItemSlot( splititem ); + if (newslot !=0xffff) + { + splititem.count +=(thisclient->items[newslot].count); + thisclient->items[newslot] = splititem; + ADDBYTE ( pak,newslot); + ADDWORD ( pak, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[newslot] ) ); + updateteller+=1; + } + break; + } + } + for (char teller = 1; teller<4;teller++) + { + if (ProductList.Index[material_lookup_number]->amount[teller]>0) + { + splititem.count = ProductList.Index[material_lookup_number]->amount[teller]; + if(splititem.count > 1) + { + splititem.count /= 2; + } + splititem.itemtype = (ProductList.Index[material_lookup_number]->item[teller])/1000; + splititem.itemnum = (ProductList.Index[material_lookup_number]->item[teller])-(1000*splititem.itemtype); + splititem.appraised = false; + splititem.durability = 0; + splititem.gem = 0; + splititem.lifespan = 0; + splititem.refine = 0; + splititem.socketed = 0; + splititem.stats = 0; + unsigned newslot = thisclient->GetNewItemSlot( splititem ); + if (newslot !=0xffff) + { + splititem.count += (thisclient->items[newslot].count); + thisclient->items[newslot] = splititem; + ADDBYTE ( pak,newslot); + ADDWORD ( pak, BuildItemHead( thisclient->items[newslot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[newslot] ) ); + updateteller += 1; + } + } + } + */ + thisclient->items[slot].count -= 1; + ADDBYTE ( pak,slot); + if (thisclient->items[slot].count <=0) + { + ClearItem(thisclient->items[slot]); + ADDWORD ( pak,0); + ADDDWORD ( pak,0); + } + else + { + ADDWORD ( pak, BuildItemHead( thisclient->items[slot] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot] ) ); + } + updateteller += 1; + SETBYTE(pak,1,updateteller); + thisclient->client->SendPacket( &pak ); + thisclient->saveinventory(); + } + break; + case 0x04://NPC Refine + case 0x05://Player Refine + { + BYTE item = GETBYTE((*P),3); //slot number + BYTE material1 = GETBYTE((*P),4); //slot number + int itemtype = thisclient->items[item].itemtype; + int itemnum = thisclient->items[item].itemnum; + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Item: %i Material: %i",item, material1 ); + } + if(!CheckInventorySlot( thisclient, item)) + return false; + if(!CheckInventorySlot( thisclient, material1)) + return false; + //compare material1 with refinetype from equiplist + int refinetype = GServer->EquipList[itemtype].Index[itemnum]->refinetype; + int refineitem = GServer->ProductList.Index[refinetype]->item[0]; + int refinecount = GServer->ProductList.Index[refinetype]->amount[0]; + if( thisclient->Session->codedebug ) + { + SendPM(thisclient, "Refineitem needed for this item: %i",refineitem ); + } + //if(refineitem != material1) + // return false; + if(thisclient->items[item].count<1 || thisclient->items[material1].count < refinecount) + { + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x12 ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; + } + unsigned int nextlevel = ( thisclient->items[item].refine / 16 ) + 1; + if( nextlevel > 9 ) + return true;//if a client were able to try and upgrade an lvl 9 grade item this would probebly lockup this client + + thisclient->items[material1].count -= refinecount; + if ((thisclient->items[material1].count) < 1) + ClearItem( thisclient->items[material1] ); + + + bool success = true; + srand( time(NULL)); + unsigned int prefine = rand()%100; + + //PY ToDo. This whole function needs to be recoded so that we know if it's a bindrune or a talisman + //right now the success chance is Bindrune chance (which is the same as talisman chance in the STB anyway for now) + int refinechance = upgrade[nextlevel].BindRune_Success; + + if( GServer->EquipList[itemtype].Index[itemnum]->raretype != 0 ) + refinechance += GServer->Config.Rare_Refine; + if( prefine > refinechance) + success = false; + + BEGINPACKET( pak, 0x7bc ) + if( success ) + { + thisclient->items[item].refine = nextlevel*16; + thisclient->items[item].durability += nextlevel; + ADDBYTE ( pak, 0x10 );//successful + } + else + { + if(GServer->Config.KillOnFail || nextlevel > 5) + { + ClearItem( thisclient->items[item] ); + } + else + { + thisclient->items[item].refine = (nextlevel-2)*16; + thisclient->items[item].durability -= 2; + } + ADDBYTE ( pak, 0x11 );//Fail + } + {ADDBYTE ( pak, 0x04 );} + ADDBYTE ( pak, material1 ); + ADDWORD ( pak, BuildItemHead( thisclient->items[material1] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[material1] ) ); + ADDBYTE ( pak, item ); + ADDWORD ( pak, BuildItemHead( thisclient->items[item] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[item] ) ); + ADDBYTE ( pak, 0x00 ); + ADDDWORD ( pak, 0x002f0000 ); + ADDDWORD ( pak, 0x00000017 ); + thisclient->client->SendPacket( &pak ); + thisclient->saveinventory(); + } + break; + case 0x06: // Drill + { + BYTE material = GETBYTE((*P), 1); + BYTE item = GETBYTE((*P), 3); + int itemnum = 0; + if(!CheckInventorySlot( thisclient, item)) + return false; + if(!CheckInventorySlot( thisclient, material)) + return false; + //Log(MSG_INFO,"client has %i drills in slot %i",thisclient->items[material].count,material); + if( thisclient->items[material].count <= 0 ) + { + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x12 ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; + } + else + { + //find the item number + itemnum = thisclient->items[material].itemnum; + //Log(MSG_INFO,"Item number = %i",itemnum); + } + //already drilled + if(thisclient->items[item].socketed) + { + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x23 ); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + return true; + } + //if( thisclient->items[item].socketed ) return true;//Show message + thisclient->items[material].count -= 1; + //thisclient->items[item].socketed = true; + if( thisclient->items[material].count <= 0 ) + ClearItem(thisclient->items[material]); + + //LMA: Adding some probability there :) + int success = RandNumber(0,100); + //Log(MSG_INFO,"Drill succes? %i / %i",success,UseList.Index[itemnum]->useeffect[1]); + if(success > UseList.Index[itemnum]->useeffect[1]) + { + //fail + thisclient->items[item].socketed = false; + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x21 ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, material); + ADDDWORD ( pak, BuildItemHead( thisclient->items[material] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[material] ) ); + thisclient->client->SendPacket(&pak); + } + else + { + // success + thisclient->items[item].socketed = true; + BEGINPACKET( pak, 0x7bc ); + ADDBYTE ( pak, 0x20 ); + ADDBYTE ( pak, 0x02 ); + ADDBYTE ( pak, material); + ADDWORD ( pak, BuildItemHead( thisclient->items[material] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[material] ) ); + ADDBYTE ( pak, item ); + ADDWORD ( pak, BuildItemHead( thisclient->items[item] ) ); + ADDDWORD ( pak, BuildItemData( thisclient->items[item] ) ); + thisclient->client->SendPacket(&pak); + } + //Log(MSG_INFO,"client now has %i drills in slot %i",thisclient->items[material].count,material); + } + thisclient->saveinventory(); + break; + default: + Log( MSG_WARNING,"Modified Item unknown action: %i", action); + } + return true; +} + +// Repair hammer +bool CWorldServer::pakRepairHammer( CPlayer* thisclient, CPacket* P ) +{ + Log( MSG_INFO, "Repairhammer. %02x %02x %02x %02x", GETBYTE((*P),0), GETBYTE((*P),1), GETBYTE((*P),2),GETBYTE((*P),3) ); + UINT repairhammer = GETBYTE((*P),0); + UINT repairitem = GETBYTE((*P),2); + if (thisclient->items[repairhammer].count < 1) + return true; + thisclient->items[repairhammer].count -= 1; + if (thisclient->items[repairhammer].count < 1) + ClearItem( thisclient->items[repairhammer] ); + thisclient->items[repairitem].durability -= 2; + thisclient->items[repairitem].lifespan=100; + BEGINPACKET ( pak, 0x7cb ); + ADDBYTE ( pak, 0x02 ); + ADDBYTE ( pak, repairhammer); + ADDWORD ( pak, BuildItemHead( thisclient->items[repairhammer] )); + ADDDWORD ( pak, BuildItemData( thisclient->items[repairhammer] )); + ADDBYTE ( pak, repairitem); + ADDWORD ( pak, BuildItemHead( thisclient->items[repairitem] )); + ADDDWORD ( pak, BuildItemData( thisclient->items[repairitem] )); + thisclient->client->SendPacket( &pak ); + thisclient->saveinventory(); + return true; +} +// Repair npc +bool CWorldServer::pakRepairItem( CPlayer* thisclient, CPacket* P ) +{ + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 0x61: //Ronk + case 0x3d: + case 0x39: + case 0x1c: + case 0x08: + case 0x74: + case 0x89: + case 0x12: // Raffle + case 0x43: //NPC Repair Item / Town + { + BYTE slot = GETBYTE((*P),2); + if(!CheckInventorySlot( thisclient, slot)) return false; + if(thisclient->items[slot].count<1) return true; + //Log( MSG_INFO,"Item socketed?: %i", thisclient->items[slot].socketed); + thisclient->items[slot].lifespan = 100; + //Log( MSG_INFO,"Item socketed?: %i", thisclient->items[slot].socketed); + //Still TODO: find where prices of storage and repair are and add it to the code. + BEGINPACKET( pak, 0x7cd ); + ADDQWORD ( pak, thisclient->CharInfo->Zulies ); + ADDBYTE ( pak, 0x01 ); + ADDBYTE ( pak, slot ); + ADDWORD ( pak, BuildItemHead( thisclient->items[slot] )); + ADDDWORD ( pak, BuildItemData( thisclient->items[slot] )); + ADDBYTE ( pak, 0x00 ); + thisclient->client->SendPacket( &pak ); + thisclient->SetStats( ); + thisclient->saveinventory(); + } + break; + default: + Log( MSG_WARNING,"Repair Item unknown action: %i", action); + break; + } + return true; +} + +// Ride request +bool CWorldServer::pakRideRequest( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Shop->open) + return true; + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 0x01://Ride Request + { + WORD tclientid = GETWORD((*P),1); + WORD oclientid = GETWORD((*P),3); + if( tclientid != thisclient->clientid ) + return true; + CPlayer* otherclient = GetClientByID( oclientid, thisclient->Position->Map ); + if(otherclient == NULL) + return true; + BEGINPACKET( pak, 0x7dd ); + ADDBYTE ( pak, 0x01 ); + ADDWORD ( pak, thisclient->clientid ); + ADDWORD ( pak, oclientid ); + ADDFLOAT ( pak, thisclient->Position->current.x*100 ); + ADDFLOAT ( pak, thisclient->Position->current.y*100 ); + otherclient->client->SendPacket( &pak ); + } + break; + case 0x02://Accept + { + WORD oclientid = GETWORD((*P),1); + WORD tclientid = GETWORD((*P),3); + if( tclientid != thisclient->clientid ) + return true; + CPlayer* otherclient = GetClientByID( oclientid, thisclient->Position->Map ); + if(otherclient == NULL) + return true; + BEGINPACKET( pak, 0x796 ); + ADDWORD ( pak, tclientid ); + ADDFLOAT ( pak, thisclient->Position->current.x*100 ); + ADDFLOAT ( pak, thisclient->Position->current.y*100 ); + ADDWORD ( pak, 0x0000 ); + SendToVisible( &pak, otherclient ); + + RESETPACKET( pak, 0x7dd ); + ADDBYTE ( pak, 0x02 ); + ADDWORD ( pak, oclientid ); + ADDWORD ( pak, tclientid ); + SendToVisible( &pak, otherclient ); + thisclient->Ride->Ride = true; + thisclient->Ride->Drive = false; + thisclient->Ride->charid = otherclient->CharInfo->charid; + otherclient->Ride->Ride = true; + otherclient->Ride->Drive = true; + otherclient->Ride->charid = thisclient->CharInfo->charid; + } + break; + case 0x03://Denied + { + WORD oclientid = GETWORD((*P),1); + WORD tclientid = GETWORD((*P),3); + if( tclientid != thisclient->clientid ) + return true; + CPlayer* otherclient = GetClientByID( oclientid, thisclient->Position->Map ); + if(otherclient == NULL) + return true; + BEGINPACKET( pak, 0x7dd ); + ADDBYTE ( pak, 0x03 ); + ADDWORD ( pak, oclientid ); + ADDWORD ( pak, tclientid ); + otherclient->client->SendPacket( &pak ); + } + break; + } + return true; +} + +bool CWorldServer::pakWeight( CPlayer* thisclient, CPacket* P ) +{ + BYTE weight = GETBYTE((*P),0) & 0xff; + if(weight>110) + thisclient->Status->CanAttack = false; + else + thisclient->Status->CanAttack = true; + if(weight>100) + thisclient->Status->CanRun = false; + else + thisclient->Status->CanRun = true; + thisclient->Stats->Move_Speed = thisclient->GetMoveSpeed( ); + BEGINPACKET( pak, 0x762 ); + ADDWORD ( pak, thisclient->clientid ); + ADDBYTE ( pak, weight ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// Show Monster HP or Player Regeneration +bool CWorldServer::pakShowHeal( CPlayer* thisclient, CPacket* P ) +{ + WORD clientid = GETWORD((*P),0); + CCharacter* character = MapList.Index[thisclient->Position->Map]->GetCharInMap( clientid ); + if(character == NULL) return true; + if(character->IsMonster()) + { + // SET MONSTER HEALTH + BEGINPACKET( pak, 0x79f ); + ADDWORD( pak, character->clientid ); + ADDWORD( pak, character->Stats->HP ); + ADDWORD( pak, 0 ); + thisclient->client->SendPacket( &pak ); + //thisclient->Battle->contatk = false; + } + //thisclient->Battle->target = clientid; + return true; +} + +// Disconnect char +bool CWorldServer::pakCharDSClient( CPlayer* thisclient, CPacket* P ) +{ + if(!thisclient->Session->isLoggedIn) return true; + BYTE action = GETBYTE((*P),0); + switch(action) + { + case 1: + { + unsigned int userid = GETDWORD((*P), 1 ); + CPlayer* otherclient = GetClientByUserID( userid ); + if(otherclient==NULL) + { + Log(MSG_WARNING, "userid '%s' not found online", userid ); + return true; + } + otherclient->client->isActive = false; + + } + break; + default: + break; + } + return true; +} + +// Pack Printscreen +bool CWorldServer::pakPrintscreen( CPlayer* thisclient, CPacket* P ) +{ + BEGINPACKET( pak, 0x7eb ); + ADDWORD ( pak, thisclient->Session->userid ); + ADDWORD ( pak, 0x0302 ); + ADDWORD ( pak, 0x2d17 ); + thisclient->client->SendPacket( &pak ); + return true; +} + +// CharServer Connected +bool CWorldServer::pakCSReady ( CPlayer* thisclient, CPacket* P ) +{ + if(thisclient->Session->isLoggedIn) return false; + if(GETDWORD((*P),0) != Config.WorldPass ) + { + Log( MSG_HACK, "CharServer Tried to connect WorldServer with wrong password "); + return true; + } + + thisclient->Session->isLoggedIn=true; + return true; +} + +// CharServer - Disconnect User +bool CWorldServer::pakCSCharSelect ( CPlayer* thisclient, CPacket* P ) +{ + if(!thisclient->Session->isLoggedIn) return false; + Log( MSG_INFO, "Char server requested client kill" ); + DWORD userid = GETDWORD( (*P), 0 ); + CPlayer* otherclient = GetClientByUserID( userid ); + if(otherclient != NULL) + { + if(otherclient->client==NULL) return true; + otherclient->client->isActive = false; + } + Log( MSG_INFO, "Client killed" ); + return true; +} + +bool CWorldServer::pakItemMall( CPlayer* thisclient, CPacket* P ) +{ + return true; +} diff --git a/Src/World Server/worldserver.cpp b/Src/World Server/worldserver.cpp new file mode 100644 index 0000000..670edd7 --- /dev/null +++ b/Src/World Server/worldserver.cpp @@ -0,0 +1,1075 @@ +/* + Rose Online Server Emulator + Copyright (C) 2006,2007 OSRose Team http://www.osrose.net + + This program 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 (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + developed with Main erose/hrose source server + some change from the original eich source +*/ +#include "worldserver.h" + +// Constructor +CWorldServer::CWorldServer( string fn ) +{ + filename = fn; + LoadConfigurations( (const char*)fn.c_str() ); + for(unsigned i=0; i<0xffff; i++) + ClientIDList[i] = 0; + ClientIDIndex = 1; + //Clear or Vector list + PartyList.clear(); + MDropList.clear(); + SkillList.clear(); + FairyList.clear(); + TeleGateList.clear(); + // + CEquip* nullequip = new CEquip; + nullequip->id = 0; + nullequip->itemtype = 0; + nullequip->equiptype = 0; + nullequip->type = 0; + nullequip->price = 0; + nullequip->pricerate = 0; + nullequip->weight = 0; + nullequip->quality = 0; + nullequip->level = 0; + nullequip->material = 0; + nullequip->defense = 0; + nullequip->magicresistence = 0; + nullequip->attackdistance = 0; + nullequip->movespeed = 0; + nullequip->attackpower = 0; + nullequip->attackspeed =0; + nullequip->itemgrade = 0; + for(int i=0;i<3;i++) + { + nullequip->occupation[i] = 0; + nullequip->condition1[i] = 0; + nullequip->condition2[i] = 0; + } + for(int i=0;i<2;i++) + { + nullequip->stat1[i] = 0; + nullequip->stat2[i] = 0; + } + for(int i=0;i<10;i++) + { + EquipList[i].nullequip = nullequip; + for(UINT j=0;j<5000;j++) + EquipList[i].Index[j] = nullequip; + } + CNaturalData* nullnatural = new CNaturalData; + nullnatural->id = 0; + nullnatural->itemtype = 0; + nullnatural->type = 0; + nullnatural->price = 0; + nullnatural->pricerate = 0; + nullnatural->weight = 0; + nullnatural->quality = 0; + nullnatural->pricevalue = 0; + NaturalList.nullnatural = nullnatural; + + CPatData* nullpat = new CPatData; + nullpat->id = 0; + nullpat->itemtype = 0; + nullpat->type = 0; + nullpat->price = 0; + nullpat->pricerate = 0; + nullpat->weight = 0; + nullpat->quality = 0; + nullpat->material = 0; + nullpat->partversion = 0; + nullpat->maxfuel = 0; + nullpat->fuelcons = 0; + nullpat->speed = 0; + nullpat->attackdistance = 0; + nullpat->attackpower = 0; + nullpat->attackspeed = 0; + PatList.nullpat = nullpat; + CCSellData* nullsell = new CCSellData; + nullsell->id = 0; + for(UINT i=0;i<50;i++) + nullsell->item[i] = 0; + SellList.nullsell = nullsell ; + for(UINT i=0;i<1000;i++) + { + NaturalList.Index[i] = nullnatural; + PatList.Index[i] = nullpat; + SellList.Index[i] = nullsell; + } + CMap* nullzone = new CMap( ); + nullzone->id =0; + nullzone->dayperiod = 1; + nullzone->morningtime = 0; + nullzone->daytime = 0; + nullzone->eveningtime = 0; + nullzone->nighttime = 0; + nullzone->allowpvp = 0; + nullzone->allowpat = 0; + nullzone->MapTime = 0; + nullzone->LastUpdate = 0; + nullzone->CurrentTime = 0; + for(UINT i=0;i<300;i++) + MapList.Index[i] = nullzone; + MapList.nullzone = nullzone; + CUseData* nulluse = new CUseData; + nulluse->id = 0; + nulluse->itemtype = 0; + nulluse->restriction = 0; + nulluse->type = 0; + nulluse->price = 0; + nulluse->pricerate = 0; + nulluse->weight = 0; + nulluse->quality = 0; + nulluse->pricevalue = 0; + for(int i=0;i<2;i++) + { + nulluse->usecondition[i] = 0; + nulluse->useeffect[i] = 0; + } + UseList.nulluse = nulluse; + CProductData* nullproduct = new CProductData; + nullproduct->id = 0; + for(UINT i=0;i<50;i++) + { + nullproduct->item[i]; + nullproduct->amount[i]; + } + ProductList.nullproduct = nullproduct; + for(UINT i=0;i<2000;i++) + { + UseList.Index[i] = nulluse; + ProductList.Index[i] = nullproduct; + } + CJemData* nulljem = new CJemData; + nulljem->id = 0; + nulljem->itemtype = 0; + nulljem->type = 0; + nulljem->price = 0; + nulljem->pricerate = 0; + nulljem->weight = 0; + nulljem->quality = 0; + nulljem->material = 0; + for(int i=0;i<2;i++) + { + nulljem->stat1[i] = 0; + nulljem->stat2[i] = 0; + } + JemList.nulljem = nulljem ; + for(UINT i=0;i<4000;i++) + JemList.Index[i] = nulljem; + for(UINT i=0;i<500;i++) + { + StatsList[i].stat[0] = 0; + StatsList[i].stat[1] = 0; + StatsList[i].value[0] = 0; + StatsList[i].value[1] = 0; + } + MapMutex = PTHREAD_MUTEX_INITIALIZER; //fast mutex + SQLMutex = PTHREAD_MUTEX_INITIALIZER; + PlayerMutex = PTHREAD_MUTEX_INITIALIZER; +} + +// Destructor +CWorldServer::~CWorldServer( ) +{ + // free memory + for(UINT i=0;iplayer; + delete ClientList.at(i); + } + delete EquipList[0].nullequip; + delete JemList.nulljem; + delete NaturalList.nullnatural; + delete PatList.nullpat; + delete ProductList.nullproduct; + delete SellList.nullsell; + delete UseList.nulluse; + delete MapList.nullzone; + delete [] GradeList; + delete DB; +} + +// This cleans up our clients mess :P +void CWorldServer::OnClientDisconnect( CClientSocket* thisclient ) +{ + if(thisclient->player == NULL) return; + CPlayer* player = (CPlayer*)thisclient->player; + if(player == NULL) return; + if(!player->Session->isLoggedIn) return; + if(!player->Saved) + { + player->savedata(); + player->SaveBackupStorage( 2 ); // make a backup of the character's storage on exit + player->Session->isLoggedIn = false; + //send packet to change messenger status (offline) + BEGINPACKET( pak, 0x7e1 ); + ADDBYTE ( pak, 0xfa ); + ADDWORD ( pak, player->Session->userid );//CharInfo->charid );CharInfo->charid ); + ADDBYTE ( pak, 0x00 ); + // cryptPacket( (char*)&pak, NULL ); + // send( csock, (char*)&pak, pak.Size, 0 ); + SendISCPacket (&pak); + } + if ( player->Fairy ) + { + GServer->FairyList.at(player->FairyListIndex)->assigned = false; + GServer->FairyList.at(player->FairyListIndex)->LastTime = clock(); + GServer->FairyList.at(player->FairyListIndex)->ListIndex = 0; + GServer->FairyList.at(player->FairyListIndex)->WaitTime = GServer->Config.FairyWait * (rand()% GServer->GetFairyRange(1)+ GServer->GetFairyRange(0)); + player->Fairy = false; + player->FairyListIndex = 0; + GServer->DoFairyStuff(player, 0); + // recalculate FairyMax + int oldFairyMax = GServer->Config.FairyMax; + GServer->Config.FairyMax = (int)ceil((float)GServer->ClientList.size() / 50.0); //(1 fairy more every 50 player) + if( oldFairyMax > GServer->Config.FairyMax ){ + GServer->FairyList.erase( GServer->FairyList.begin() + GServer->FairyList.size() ); + } + } + if(player->Party->party!=NULL) + { + CParty* party = player->Party->party; + BEGINPACKET( pak, 0x7d2 ); + ADDWORD ( pak, 0xff00 ); + ADDDWORD ( pak, player->CharInfo->charid ); + bool pflag = false; + party->RemovePlayer( player ); + if(party->Members.size()>1) + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* othermember = party->Members.at(i); + if(!pflag) + { + ADDDWORD( pak, othermember->CharInfo->charid ); + if(player->Party->IsMaster) + othermember->Party->IsMaster = true; + pflag = true; + } + othermember->client->SendPacket( &pak ); + } + } + else + { + for(UINT i=0;iMembers.size();i++) + { + CPlayer* othermember = party->Members.at(i); + RESETPACKET( pak, 0x7d1 ); + ADDBYTE ( pak, 0x05 ); + ADDWORD ( pak, 0x0000 ); + ADDWORD ( pak, 0x0000 ); + othermember->client->SendPacket( &pak ); + othermember->Party->party = NULL; + othermember->Party->IsMaster = true; + } + RemoveParty( party ); + delete party; + } + } + DB->QExecute("UPDATE accounts SET online=false where id=%u", player->Session->userid ); +} + +// Server is started, lets run our loop :D +void CWorldServer::ServerLoop( ) +{ + fd_set fds; + int activity; + maxfd = 0; + sockaddr_in ClientInfo; + SOCKET NewSocket; + timeval timeout; + maxfd = sock; + OnServerStep(); + + //MySQL Ping (every 10 minutes) + UINT time_last_ping = clock(); + UINT delay_ping = 600000; //10 minutes + UINT time_last_update = clock(); + UINT ServerTimeUpdateDelay = 3600000; //1 hour + do + { + //MySQL Ping + UINT etime = (UINT)round((clock( ) - time_last_ping)); + if(etime >= delay_ping) + { + time_last_ping = clock(); + Ping(); + } + UINT stime = (UINT)round((clock( ) - time_last_update)); + if(stime >= ServerTimeUpdateDelay) + { + ServerRunTime ++; + time_last_update = clock(); + DB->QExecute("UPDATE runtime SET hours = %i WHERE id = %i", ServerRunTime, ThisSession); + } + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + NewSocket = INVALID_SOCKET; + FD_ZERO( &fds ); + pthread_mutex_lock( &PlayerMutex ); + if(!Config.usethreads) + FillFDS( &fds ); + FD_SET( sock, &fds ); + activity = select( maxfd+1, &fds, NULL, NULL, &timeout ); + if ( activity == 0 ) + { + FD_SET( sckISCII, &fds ); + activity = select( maxfd+1, &fds, NULL, NULL, &timeout ); + if ( activity == 0 ) + { + pthread_mutex_unlock( &PlayerMutex ); + #ifdef _WIN32 + Sleep(1); + #else + usleep(1); + #endif + continue; + } + if ( activity < 0 && errno != EINTR ) + { + #ifdef _WIN32 + Log( MSG_ERROR, "Select command failed. Error #%i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Select command failed. Error #%i", errno ); + #endif + isActive = false; + } + if ( FD_ISSET( sckISCII, &fds ) ) + { + int clientinfolen = sizeof( sockaddr_in ); + #ifdef _WIN32 + NewSocket = accept( sckISCII, (sockaddr*)&ClientInfo, (int*)&clientinfolen ); + #else + NewSocket = accept( sckISCII, (sockaddr*)&ClientInfo, (socklen_t*)&clientinfolen ); + #endif + // TODO: check if server is full + if (NewSocket != INVALID_SOCKET) + AddUser( NewSocket, &ClientInfo, true); + else + { + #ifdef _WIN32 + Log( MSG_ERROR, "Error accepting socket: %i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Error accepting socket: %i", errno ); + #endif + } + } + } + if ( activity < 0 && errno != EINTR ) + { + #ifdef _WIN32 + Log( MSG_ERROR, "Select command failed. Error #%i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Select command failed. Error #%i", errno ); + #endif + isActive = false; + } + if ( FD_ISSET( sock, &fds ) ) + { + int clientinfolen = sizeof( sockaddr_in ); + #ifdef _WIN32 + NewSocket = accept( sock, (sockaddr*)&ClientInfo, (int*)&clientinfolen ); + #else + NewSocket = accept( sock, (sockaddr*)&ClientInfo, (socklen_t*)&clientinfolen ); + #endif + // TODO: check if server is full + if (NewSocket != INVALID_SOCKET) + AddUser( NewSocket, &ClientInfo, false); + else + { + #ifdef _WIN32 + Log( MSG_ERROR, "Error accepting socket: %i", WSAGetLastError() ); + #else + Log( MSG_ERROR, "Error accepting socket: %i", errno ); + #endif + } + } + if(!Config.usethreads) + HandleClients( &fds ); + pthread_mutex_unlock( &PlayerMutex ); + #ifdef _WIN32 + Sleep(1); + #else + usleep(1); + #endif + } while( isActive ); +} + +// load the servers encryption +void CWorldServer::LoadEncryption() +{ + GenerateCryptTables( CryptTable, 0x87654321 ); + srand ( (unsigned)time(NULL) ); +} + +// Return a new clientsocket structure +CClientSocket* CWorldServer::CreateClientSocket( ) +{ + //DB->QExecute( "UPDATE channels SET connected=%i where id=%i and type=2", ConnectedClients, Config.ServerID ); + CClientSocket* client = new CClientSocket; + CPlayer* player = new CPlayer( client ); + client->player = (void*) player; + return client; +} + +// Deletes an old clientsocket structure +void CWorldServer::DeleteClientSocket( CClientSocket* thisclient ) +{ + //DB->QExecute( "UPDATE channels SET connected=%i where id=%i and type=2", ConnectedClients, Config.ServerID ); + if(thisclient->player!=NULL) + { + CPlayer* player = (CPlayer*) thisclient->player; + CMap* map = MapList.Index[player->Position->Map]; + pthread_mutex_lock( &MapMutex ); + map->RemovePlayer( player ); + pthread_mutex_unlock( &MapMutex ); + delete player; + } + else delete thisclient; + Log( MSG_INFO, "User Disconnected" ); +} + +void CWorldServer::SendISCServerInfo( ) +{ + BEGINPACKET( pak, 0x400 ); + ADDBYTE( pak,2 ); + ADDDWORD( pak, Config.ServerID ); + ADDDWORD( pak, Config.ParentID ); + ADDDWORD( pak, Config.WorldPort ); + ADDSTRING( pak, Config.WorldIP ); + ADDBYTE( pak, 0 ); + ADDSTRING( pak, Config.ServerName ); + ADDBYTE( pak,0 ); + SendISCPacket( &pak ); +} + +// This function is called just before the server starts +bool CWorldServer::OnServerReady( ) +{ + ServerOnline = true; + GServer = this; + clock_t timer = clock(); + LastUpdateTime = clock(); + ATTK_SPEED_MOD = 120; + HIT_DELAY_MOD = 0; + MOVE_SPEED_MOD = 100000; + + //SendISCServerInfo( ); + + //Load our Server Info + LoadConfig( ); + LoadLTB( ); + LoadSTBData( ); + LoadZoneData( ); + LoadConsItem( ); + LoadSellData( ); + LoadProductItem( ); + LoadPatItem( ); + LoadNaturalItem( ); + LoadJemItem( ); + LoadEquip( ); + LoadItemStats( ); + LoadSkillData( ); + LoadStatusData( ); + LoadBreak( ); + LoadNewDrops( ); + //LoadPYDropsData( ); + LoadSkillBookDropsData( ); + + + LoadNPCData( ); + LoadTeleGateData( ); + LoadRespawnData( ); + if(Config.SpawnType == 1)LoadMonsterSpawn( ); + if(Config.SpawnType == 0)LoadMobGroups( ); + LoadAipData( ); + LoadNPCs( ); + LoadMonsters( ); + LoadUpgrade( ); + LoadQuestData( ); + LoadGrades(); //LMA: loading refine (grades) bonuses. + LoadWayPoints( ); + + //LoadZones(); + CleanConnectedList( ); + Log(MSG_INFO, "Database Loaded " ); + pthread_create( &WorldThread[WORLD_THREAD], &at, WorldProcess, NULL); + pthread_create( &WorldThread[VISUALITY_THREAD], &at, VisibilityProcess, NULL); + pthread_create( &MapThread[0], &at, MapProcess, NULL); + Log( MSG_INFO, "Process Loaded. WorldDelay %i | MapDelay %i | VisualDelay %i",Config.WorldDelay,Config.MapDelay,Config.VisualDelay); + //DB->QExecute( "DELETE FROM channels WHERE id=%u and type=%i", Config.ServerID, Config.ServerType ); +// if(!DB->QExecute("INSERT INTO channels (id,type,name,host,port,lanip,lansubmask,connected,maxconnections,owner) VALUES (%i,%i,'%s','%s',%u,'%s','%s',0,%i,%i)", +// Config.ServerID, Config.ServerType, Config.ServerName, Config.WorldIP, Config.WorldPort, Config.LanIP, Config.LanSubnet, Config.MaxConnections, Config.ParentID)) +// { +// Log(MSG_WARNING, "Error accessing to database, the other server will not connect to WorldServer" ); +// } + DB->QExecute( "DELETE FROM channels WHERE id=%u", Config.ServerID); + if(!DB->QExecute("INSERT INTO channels (id,name,host,port,connected,maxconnections,owner) VALUES (%i,'%s','%s',%u,0,%i,%i)", + Config.ServerID, Config.ServerName, Config.WorldIP, Config.WorldPort, Config.MaxConnections, Config.ParentID)) + { + Log(MSG_WARNING, "Error accessing to database, the other server will not connect to WorldServer" ); + } + //DB->QFree( ); + + // set timestamp for startup + time_t rtime; + time(&rtime); + char *timestamp = ctime(&rtime); + timestamp[ strlen(timestamp)-1 ] = ' '; + int a1 = 0; + DB->QExecute("INSERT INTO runtime (starttime,hours) VALUES ('%s',%i)",timestamp, a1 ); + MYSQL_ROW row; + MYSQL_RES *result = DB->QStore("SELECT id FROM runtime"); + while(row = mysql_fetch_row(result)) + { + ThisSession = atoi(row[0]); + } + DB->QFree( ); + ServerStartTime = clock(); + ServerRunTime = 0; + + /* + MYSQL_ROW row; + bool pflag = false; + //Get IP and Port from Charserver + + MYSQL_RES *result = DB->QStore( "SELECT host,port,lanip FROM channels WHERE id=%u", Config.ParentID ); + if(result==NULL) return false; + if (mysql_num_rows( result ) == 1) + { + row = mysql_fetch_row( result ); + switch(Config.Connection) + { + case 0://wanip + Config.CharIP = row[0]; + break; + case 1://lanip + Config.CharIP = row[2]; + break; + default://localhost + Config.CharIP = "127.0.0.1"; + break; + } + Config.CharPort = atoi(row[1]); + pflag = true; + } + DB->QFree( ); + if(pflag) + {*/ + Log( MSG_INFO, "Initializing Charserver connection" ); + // Connect To CharServer + csock = socket( AF_INET, SOCK_STREAM, 0 ); + if (csock == INVALID_SOCKET) + { + Log( MSG_WARNING, "Could not access login server" ); + } + struct sockaddr_in ain; + ain.sin_family = AF_INET; + ain.sin_addr.s_addr = inet_addr( Config.CharIP ); + ain.sin_port = htons( Config.CharPort ); + if ( connect( csock, (SOCKADDR*) &ain, sizeof(ain) ) == SOCKET_ERROR ) + Log( MSG_WARNING, "Could not access charserver" ); + else + Log( MSG_INFO, "Connected to charserver" ); + + //say hello to char server + BEGINPACKET( pak, 0x500 ); + ADDDWORD ( pak, Config.CharPass ); + ADDDWORD ( pak, Config.ServerID ); + ADDWORD ( pak, Config.WorldsPort ); + SendISCPacket( &pak ); +/* CPacket *P=&pak; + unsigned char* Buffer = (unsigned char*)P; + unsigned Size = P->Size; + EncryptBuffer( CryptTable, Buffer ); + send( csock, (char*)Buffer, Size, 0 ); + //EncryptBuffer(CryptTable,pak); + //cryptPacket( (char*)&pak, NULL ); + //send( csock, (char*)&pak, pak.Size, 0 ); + } + */ +/////////////////////////////////////////////////////////////////////////////////// + float loadtime = (float)( clock() - timer ) / CLOCKS_PER_SEC; + Log( MSG_INFO, "Server took %.4f seconds to load", loadtime ); + + return true; +} + +// Send Levelup To charserver +bool CWorldServer::SendLevelUPtoChar(CPlayer *thisclient) +{ + + BEGINPACKET( pak, 0x79e ); + ADDWORD( pak, thisclient->CharInfo->charid );//Session->userid );//CharInfo->charid ); + ADDWORD( pak, thisclient->Stats->Level ); + //cryptPacket( (char*)&pak, NULL ); + //send( csock, (char*)&pak, pak.Size, 0 ); + SendISCPacket( &pak ); + + RESETPACKET( pak, 0x79e ); + ADDWORD( pak, thisclient->CharInfo->charid ); + ADDWORD( pak, thisclient->Stats->Level ); + thisclient->client->SendPacket(&pak); + return true; +} + +bool CWorldServer::pakGiveItem( CPlayer* thisclient, unsigned char itemtype, unsigned short itemid, unsigned short count ) +{ + char itemtypes[15] = {4,0,0,0,0,0,0,0,0,0,1,2,2,4,3}; + CPacket pakout; + CItem thisitem; + + thisitem.Clear( ); + thisitem.itemtype = itemtype; + thisitem.itemnum = itemid; + thisitem.count = count; + if( itemtypes[thisitem.itemtype] == 0 ) + { + thisitem.appraised = true; + thisitem.lifespan = 100; + thisitem.gem = 0; + thisitem.durability = STB_ITEM[thisitem.itemtype-1].rows[thisitem.itemnum][29]; + } + + pakout.StartPacket( 0x71f ); + pakout.AddByte( 0 ); + while( thisitem.count > 0 ) + { + unsigned char thisslot = thisclient->AddItem( thisitem ); + if( thisslot == 0xff ) break; + pakout.AddByte( thisslot ); + pakout.AddWord( thisclient->items[thisslot].GetPakHeader( ) ); + pakout.AddDWord( thisclient->items[thisslot].GetPakData( ) ); + pakout.Buffer[0]++; + } + thisclient->client->SendPacket( &pakout ); + + return true; +} + +bool CWorldServer::pakGiveExp( CPlayer* thisclient, unsigned exp ) +{ + CPacket pakout; + thisclient->CharInfo->Exp+= exp; + if(!thisclient->CheckPlayerLevelUP( )) + { + pakout.StartPacket( 0x79b ); + pakout.AddDWord( thisclient->CharInfo->Exp ); + pakout.AddWord( thisclient->CharInfo->stamina ); + pakout.AddWord( 0 ); + thisclient->client->SendPacket( &pakout ); + } + return true; +} + +bool CWorldServer::pakGiveMoney( CPlayer* thisclient, unsigned amount ) +{ + CPacket pakout; + thisclient->CharInfo->Zulies += amount; + pakout.StartPacket( 0x71e ); + pakout.AddQWord( thisclient->CharInfo->Zulies ); + thisclient->client->SendPacket( &pakout ); + return true; +} + +void CWorldServer::DePopulateZone( CPlayer* thisclient, int zonex, int zoney )//compile warning fix by Fluorescent abs -> fabs +{ + CPacket pakout, pakout2; + if( !thisclient->Session->inGame || fabs( thisclient->Position->current.x - zonex ) > 1 || fabs( thisclient->Position->current.y - zoney ) > 1 ) + { + pakout.StartPacket( 0x794 ); + + for( unsigned i=0; iSession->inGame || thisclient==otherclient ) continue; + if( otherclient->Position->current.x == zonex && otherclient->Position->current.y == zoney && otherclient->Position->Map == thisclient->Position->Map ) + { + pakout2.StartPacket( 0x794 ); + pakout2.AddWord( thisclient->clientid ); + otherclient->client->SendPacket( &pakout2 ); + + pakout.AddWord( otherclient->clientid ); + } + } + + for( unsigned i=0; iMonsterSpawnList.size();j++) + { + CMonster* thismonster = thismap->MonsterList.at(j); + if( thismonster->Position->current.x == zonex && thismonster->Position->current.y == zoney && thismonster->Position->Map == thisclient->Position->Map ) + pakout.AddWord( thismonster->clientid ); + + for( unsigned k=0; kNPCList.size( ); k++ ) + { + CNPC* thisnpc = thismap->NPCList[k]; + if( thisnpc->pos.x == zonex && thisnpc->pos.y == zoney && thisnpc->posMap == thisclient->Position->Map ) + pakout.AddWord( thisnpc->clientid ); + } + + for( unsigned k=0; kDropsList.size( ); k++ ) + { + CDrop* thisdrop = thismap->DropsList[k]; + if( thisdrop->pos.x == zonex && thisdrop->pos.y == zoney && thisdrop->posMap == thisclient->Position->Map ) + pakout.AddWord( thisdrop->clientid ); + } + } + } + + if( pakout.Size > 6 ) thisclient->client->SendPacket( &pakout ); + } +} + +bool CWorldServer::pakTeleport( CPlayer* thisclient, unsigned short map, float x, float y ) +{ + + fPoint coord; + coord.x = x/100; + coord.y = y/100; + MapList.Index[map]->TeleportPlayer( thisclient, coord, false ); + /* + CPacket pakout; + + thisclient->Session->inGame = false; + + GServer->MapList.Index[thisclient->Position->Map]->RemovePlayer( thisclient, false ); + + ClearClientID( thisclient->clientid ); + thisclient->Position->Map = map; + thisclient->Position->destiny.x = x; + thisclient->Position->destiny.y = y; + thisclient->Position->current = thisclient->Position->destiny; + thisclient->Status->Stance= 0; + + pakout.StartPacket( 0x7a8 ); + pakout.AddWord( thisclient->clientid ); + pakout.AddWord( thisclient->Position->Map ); + pakout.AddFloat( thisclient->Position->current.x ); + pakout.AddFloat( thisclient->Position->current.y ); + pakout.AddWord( 1 ); + thisclient->client->SendPacket( &pakout );*/ + return true; +} + +// disconect all the clients +void CWorldServer::DisconnectAll() +{ + for(UINT i=0;iplayer; + if(otherclient->Session->isLoggedIn) + { + otherclient->savedata( ); + otherclient->Saved = true; + otherclient->client->isActive = false; + } + } +} + +// Load Server configuration +void CWorldServer::LoadConfigurations( const char* file ) +{ + //Database + Config.SQLServer.pcServer = ConfigGetString ( file, "mysql_host", "localhost" ); + Config.SQLServer.pcDatabase = ConfigGetString ( file, "mysql_database", "roseon_beta" ); + Config.SQLServer.pcUserName = ConfigGetString ( file, "mysql_user", "root" ); + Config.SQLServer.pcPassword = ConfigGetString ( file, "mysql_pass", "" ); + Config.SQLServer.pcPort = ConfigGetInt ( file, "mysql_port", 3306 ); + //Server + Config.ServerID = ConfigGetInt ( file, "serverid", 1 ); + Config.ServerType = 2; // World always = 2 + Config.WorldPort = ConfigGetInt ( file, "serverport", 29200 ); + Config.WorldIP = ConfigGetString ( file, "serverip", "127.0.0.1" ); + Config.ParentID = ConfigGetInt ( file, "parentid", 1 ); + Config.ServerName = ConfigGetString ( file, "servername", "Channel" ); + Config.MaxConnections = ConfigGetInt ( file, "maxconnections", 100 ); + //Config.Connection = ConfigGetInt ( file, "connection", 0 ); + //Config.LanIP = ConfigGetString ( file, "lanip", "192.168.0.1" ); + //Config.LanSubnet = ConfigGetString ( file, "lansubmask", "192.168.0" ); + Config.CharIP = ConfigGetString ( file, "charip", "127.0.0.1" ); + Config.CharPort = ConfigGetInt ( file, "charport", 29110 ); + Config.WorldsPort = ConfigGetInt ( file, "WorldsPort", 29210 ); + //World + Config.MinimumAccessLevel = ConfigGetInt ( file, "minimal_access_level", 100 ); + Config.usethreads = ConfigGetInt ( file, "usethreads", 0 )==0?false:true; + //Config.EXP_RATE = ConfigGetInt ( file, "exp_rate", 10 ); + //Config.DROP_RATE = ConfigGetInt ( file, "drop_rate", 1 ); + Config.DROP_TYPE = ConfigGetInt ( file, "drop_type", 2 ); + //Config.ZULY_RATE = ConfigGetInt ( file, "zuly_rate", 1 ); + Config.WELCOME_MSG = ConfigGetString ( file, "welcome_msg", "Welcome to Rose Online" ); + Config.AUTOSAVE = ConfigGetInt ( file, "autosave", 0 ); + Config.SAVETIME = ConfigGetInt ( file, "savetime", 3600 ); + Config.MapDelay = ConfigGetInt ( file, "mapdelay", 10 ); + Config.WorldDelay = ConfigGetInt ( file, "worlddelay", 200 ); + Config.VisualDelay = ConfigGetInt ( file, "visualdelay", 500 ); + Config.Partygap = ConfigGetInt ( file, "partygap", 10 ); + Config.MaxStat = ConfigGetInt ( file, "maxstat", 254 ); + Config.FairyMode = ConfigGetInt ( file, "fairy", 1 ); + Config.FairyStay = ConfigGetInt ( file, "fairystay", 20 ); + Config.FairyWait = ConfigGetInt ( file, "fairywait", 15 ); + Config.FairyMax = ConfigGetInt ( file, "fairymax", 0); + Config.FairyTestMode = ConfigGetInt ( file, "fairytestmode", 1); + //Config.PlayerDmg = ConfigGetInt ( file, "playerdmg", 120); + //Config.MonsterDmg = ConfigGetInt ( file, "monsterdmg", 100); + Config.Cfmode = ConfigGetInt ( file, "cfmode", 0); + + //Password + Config.LoginPass = ConfigGetInt ( file, "loginpass", 123456 ); + Config.CharPass = ConfigGetInt ( file, "charpass", 123456 ); + Config.WorldPass = ConfigGetInt ( file, "worldpass", 123456 ); + if(Config.AUTOSAVE==1) + Log( MSG_INFO, "Autosaving Every %i minutes", Config.SAVETIME/60 ); + Config.DeathDelay = 1000; //delay time in miliseconds. + + LoadCommandLevels(); +} + +// Load commands from commands.ini [by Paul_T] +void CWorldServer::LoadCommandLevels( void ) +{ + Config.Command_Ani = ConfigGetInt ( "commands.ini", "ani", 299 ); + Config.Command_Ann = ConfigGetInt ( "commands.ini", "ann", 299 ); + Config.Command_Ban = ConfigGetInt ( "commands.ini", "ban", 299 ); + Config.Command_Cha = ConfigGetInt ( "commands.ini", "cha", 299 ); + Config.Command_ChangeFairyWait = ConfigGetInt ( "commands.ini", "changefairywait", 299 ); + Config.Command_ChangeFairyStay = ConfigGetInt ( "commands.ini", "changefairystay", 299 ); + Config.Command_ChangeFairyTestMode = ConfigGetInt ( "commands.ini", "changefairytestmode", 299 ); + Config.Command_Class = ConfigGetInt ( "commands.ini", "class", 299 ); + Config.Command_Convert = ConfigGetInt ( "commands.ini", "convert", 299 ); + Config.Command_Cfmode = ConfigGetInt ( "commands.ini", "cfmode", 299 ); + Config.Command_DelSpawn = ConfigGetInt ( "commands.ini", "delspawn", 299 ); + Config.Command_DQuest = ConfigGetInt ( "commands.ini", "dquest", 299 ); + Config.Command_Drop = ConfigGetInt ( "commands.ini", "drop", 299 ); + Config.Command_DSpawn = ConfigGetInt ( "commands.ini", "dspawn", 299 ); + Config.Command_ESpawn = ConfigGetInt ( "commands.ini", "espawn", 299 ); + Config.Command_Exp = ConfigGetInt ( "commands.ini", "exp", 299 ); + Config.Command_Face = ConfigGetInt ( "commands.ini", "face", 299 ); + Config.Command_Give2 = ConfigGetInt ( "commands.ini", "give2", 299 ); + Config.Command_GiveFairy = ConfigGetInt ( "commands.ini", "givefairy", 299 ); + Config.Command_GiveZuly = ConfigGetInt ( "commands.ini", "givezuly", 299 ); + Config.Command_Go = ConfigGetInt ( "commands.ini", "go", 299 ); + Config.Command_Goto = ConfigGetInt ( "commands.ini", "goto", 299 ); + Config.Command_GoToMap = ConfigGetInt ( "commands.ini", "gotomap", 299 ); + Config.Command_Hair = ConfigGetInt ( "commands.ini", "hair", 299 ); + Config.Command_Heal = ConfigGetInt ( "commands.ini", "heal", 299 ); + Config.Command_Here = ConfigGetInt ( "commands.ini", "here", 299 ); + Config.Command_Hide = ConfigGetInt ( "commands.ini", "hide", 299 ); + Config.Command_Info = ConfigGetInt ( "commands.ini", "info", 299 ); + Config.Command_IQuest = ConfigGetInt ( "commands.ini", "iquest", 299 ); + Config.Command_Item = ConfigGetInt ( "commands.ini", "item", 299 ); + Config.Command_Job = ConfigGetInt ( "commands.ini", "job", 299 ); + Config.Command_Kick = ConfigGetInt ( "commands.ini", "kick", 299 ); + Config.Command_KillInRange = ConfigGetInt ( "commands.ini", "killinrange", 299 ); + Config.Command_Level = ConfigGetInt ( "commands.ini", "level", 299 ); + Config.Command_LevelUp = ConfigGetInt ( "commands.ini", "levelup", 299 ); + Config.Command_ManageFairy = ConfigGetInt ( "commands.ini", "managefairy", 299 ); + Config.Command_Mdmg = ConfigGetInt ( "commands.ini", "mdmg", 299 ); + Config.Command_Mon = ConfigGetInt ( "commands.ini", "mon", 299 ); + Config.Command_Monster = ConfigGetInt ( "commands.ini", "monster", 299 ); + Config.Command_Move = ConfigGetInt ( "commands.ini", "move", 299 ); + Config.Command_Moveto = ConfigGetInt ( "commands.ini", "moveto", 299 ); + Config.Command_Mute = ConfigGetInt ( "commands.ini", "mute", 299 ); + Config.Command_Npc = ConfigGetInt ( "commands.ini", "npc", 299 ); + Config.Command_Pak = ConfigGetInt ( "commands.ini", "pak", 299 ); + Config.Command_Pak2 = ConfigGetInt ( "commands.ini", "pak2", 299 ); + Config.Command_Pakm = ConfigGetInt ( "commands.ini", "pakm", 299 ); + Config.Command_Partylvl = ConfigGetInt ( "commands.ini", "partylvl", 299); + Config.Command_PlayerInfo = ConfigGetInt ( "commands.ini", "playerinfo", 299 ); + Config.Command_Pdmg = ConfigGetInt ( "commands.ini", "pdmg", 299 ); + Config.Command_Pvp = ConfigGetInt ( "commands.ini", "pvp", 299 ); + Config.Command_Rate = ConfigGetInt ( "commands.ini", "rate", 299 ); + Config.Command_Reload = ConfigGetInt ( "commands.ini", "reload", 299 ); + Config.Command_ReloadQuest = ConfigGetInt ( "commands.ini", "reloadquest", 299 ); + Config.Command_Rules = ConfigGetInt ( "commands.ini", "rules", 99 ); + Config.Command_Save = ConfigGetInt ( "commands.ini", "save", 299 ); + Config.Command_ServerInfo = ConfigGetInt ( "commands.ini", "serverinfo", 299 ); + Config.Command_Set = ConfigGetInt ( "commands.ini", "set", 299 ); + Config.Command_Settime = ConfigGetInt ( "commands.ini", "settime", 299 ); + Config.Command_ShopType = ConfigGetInt ( "commands.ini", "shoptype", 299 ); + Config.Command_Shutdown = ConfigGetInt ( "commands.ini", "shutdown", 299 ); + Config.Command_SSpawn = ConfigGetInt ( "commands.ini", "sspawn", 299 ); + Config.Command_Stat = ConfigGetInt ( "commands.ini", "stat", 299 ); + Config.Command_Summon = ConfigGetInt ( "commands.ini", "summon", 299 ); + Config.Command_TargetInfo = ConfigGetInt ( "commands.ini", "targetinfo", 299 ); + Config.Command_Tele = ConfigGetInt ( "commands.ini", "tele", 299 ); + Config.Command_TeleToMe = ConfigGetInt ( "commands.ini", "teletome", 299 ); + Config.Command_Transx = ConfigGetInt ( "commands.ini", "transx", 299 ); + Config.Command_Who = ConfigGetInt ( "commands.ini", "who", 299 ); + Config.Command_Who2 = ConfigGetInt ( "commands.ini", "who2", 299 ); + Config.Command_Broadcast = ConfigGetInt ( "commands.ini", "broadcast", 299 ); + Config.Command_GlobalTime = ConfigGetInt ( "commands.ini", "globaldelay", 30 ); + Config.Command_GlobalPrefix = ConfigGetString ( "commands.ini", "globalprefix", "[Broadcast]" ); + +} +bool CWorldServer::Ping( ) +{ + return DB->Ping( ); +} + +void CWorldServer::ReceivedISCPacket( CPacket* pak ) +{ + switch( pak->Command ) + { + case 0x100: + break; + case 0x101: + SendISCServerInfo( ); + break; + case 0x300: + break; + case 0x301: + break; + case 0x400: + break; + case 0x401: + break; + case 0x500: + break; + case 0x501: + break; + case 0x502: + { + for( unsigned i=0; iSession->inGame ) + { + BEGINPACKET( pakout, 0x702 ); + ADDSTRING( pakout, (char*)&pak->Buffer[0] ); + ADDBYTE( pakout, 0 ); + otherclient->client->SendPacket( &pakout ); + } + } + break; + } + case 0x503: + exit( 0 ); + break; + case 0x580: + break; + case 0x581: + break; + default: + Log( MSG_DEBUG, "UNKNOWN ISC PACKET - 0x%x %04x %04x", pak->Command, pak->Size, pak->Unused ); + } +} + +// Incoming packet +bool CWorldServer::OnReceivePacket( CClientSocket* thisclient, CPacket *P ) +{//Log( MSG_INFO, "(SID:%i) Received packet. Command:%04x Size:%04x", thisclient->sock, P->Command, P->Size ); + switch( P->Command ) + { + case 0x0500: return pakCSReady ( (CPlayer*)thisclient->player, P ); + case 0x0502: return pakCharDSClient ( (CPlayer*)thisclient->player, P ); + case 0x0505: return pakCSCharSelect ( (CPlayer*)thisclient->player, P ); + case 0x0700: return pakPing ( (CPlayer*)thisclient->player, P ); + case 0x0707: return pakExit ( (CPlayer*)thisclient->player, P ); + case 0x070b: return pakDoIdentify ( (CPlayer*)thisclient->player, P ); + case 0x071c: return pakCharSelect ( (CPlayer*)thisclient->player, P ); + case 0x0730: return pakQuestTrigger ( (CPlayer*)thisclient->player, P ); + //case 0x0753: return pakRespawned ( (CPlayer*)thisclient->player, P ); + case 0x0753: return pakDoID ( (CPlayer*)thisclient->player, P ); + case 0x0755: return pakUserDied ( (CPlayer*)thisclient->player, P ); + case 0x0756: return pakSaveTown ( (CPlayer*)thisclient->player, P ); + case 0x0762: return pakWeight ( (CPlayer*)thisclient->player, P ); + case 0x0771: return pakStopChar ( (CPlayer*)thisclient->player, P ); + case 0x0781: return pakDoEmote ( (CPlayer*)thisclient->player, P ); + case 0x0782: return pakChangeStance ( (CPlayer*)thisclient->player, P ); + case 0x0783: return pakNormalChat ( (CPlayer*)thisclient->player, P ); + case 0x0784: return pakWhisper ( (CPlayer*)thisclient->player, P ); + case 0x0785: return pakShout ( (CPlayer*)thisclient->player, P ); + case 0x0786: return pakPartyChat ( (CPlayer*)thisclient->player, P ); + case 0x0798: return pakStartAttack ( (CPlayer*)thisclient->player, P ); + case 0x079f: return pakShowHeal ( (CPlayer*)thisclient->player, P ); + case 0x079a: return pakMoveChar ( (CPlayer*)thisclient->player, P ); + case 0x07a1: return pakNPCBuy ( (CPlayer*)thisclient->player, P ); + case 0x07a3: return pakUseItem ( (CPlayer*)thisclient->player, P ); + case 0x07a4: return pakDoDrop ( (CPlayer*)thisclient->player, P ); + case 0x07a5: return pakChangeEquip ( (CPlayer*)thisclient->player, P ); + case 0x07a7: return pakPickDrop ( (CPlayer*)thisclient->player, P ); + case 0x07a8: return pakGate ( (CPlayer*)thisclient->player, P ); + case 0x07a9: return pakAddStats ( (CPlayer*)thisclient->player, P ); + case 0x07aa: return pakMoveSkill ( (CPlayer*)thisclient->player, P ); + case 0x07ab: return pakEquipABC ( (CPlayer*)thisclient->player, P ); + case 0x07af: return pakCraft ( (CPlayer*)thisclient->player, P ); + case 0x07b1: return pakLevelUpSkill ( (CPlayer*)thisclient->player, P ); + case 0x07b2: return pakSkillSelf ( (CPlayer*)thisclient->player, P ); + case 0x07b3: return pakStartSkill ( (CPlayer*)thisclient->player, P ); + case 0x07b4: return pakSkillAOE ( (CPlayer*)thisclient->player, P ); + case 0x07c0: return pakTradeAction ( (CPlayer*)thisclient->player, P ); + case 0x07c1: return pakTradeAdd ( (CPlayer*)thisclient->player, P ); + case 0x07ad: return pakStorage ( (CPlayer*)thisclient->player, P ); + case 0x07ae: return pakChangeStorage ( (CPlayer*)thisclient->player, P ); + case 0x07ba: return pakidentify ( (CPlayer*)thisclient->player, P ); + case 0x07bc: return pakModifiedItem ( (CPlayer*)thisclient->player, P ); + case 0x07bf: return true;//add to wishlist + case 0x07c2: return pakOpenShop ( (CPlayer*)thisclient->player, P ); + case 0x07c3: return pakCloseShop ( (CPlayer*)thisclient->player, P ); + case 0x07c4: return pakShowShop ( (CPlayer*)thisclient->player, P ); + case 0x07c5: return pakBuyShop ( (CPlayer*)thisclient->player, P ); + case 0x07c6: return pakSellShop ( (CPlayer*)thisclient->player, P ); + case 0x07ca: return pakChangeCart ( (CPlayer*)thisclient->player, P ); + case 0x07cb: return pakRepairHammer ( (CPlayer*)thisclient->player, P ); + case 0x07cd: return pakRepairItem ( (CPlayer*)thisclient->player, P ); + case 0x07d0: return pakPartyActions ( (CPlayer*)thisclient->player, P ); + case 0x07d1: return pakPartyManager ( (CPlayer*)thisclient->player, P ); + case 0x07d7: return pakPartyOption ( (CPlayer*)thisclient->player, P ); + case 0x07d8: return pakModifiedItemDone ( (CPlayer*)thisclient->player, P ); + case 0x07d9: return pakItemMall ( (CPlayer*)thisclient->player, P ); + case 0x07da: return pakStoreZuly ( (CPlayer*)thisclient->player, P ); + case 0x07dd: return pakRideRequest ( (CPlayer*)thisclient->player, P ); + case 0x07e0: return pakCreateClan ( (CPlayer*)thisclient->player, P ); + case 0x07e1: return pakClanManager ( (CPlayer*)thisclient->player, P ); + case 0x07eb: return pakPrintscreen ( (CPlayer*)thisclient->player, P ); + case 0x0808: return pakGameGuard ( (CPlayer*)thisclient->player, P ); + default: + Log( MSG_WARNING, "(SID:%i) Received unknown packet. Command:%04x Size:%04x", thisclient->sock, P->Command, P->Size ); + break; + } + return true; +} +// -----------------------------------------------------------------------------------------

    ouN4 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 Ab^rhX literal 0 HcmV?d00001 diff --git a/Src/3rd_party/mysql/lib/libmysql.lib b/Src/3rd_party/mysql/lib/libmysql.lib new file mode 100644 index 0000000000000000000000000000000000000000..640c9a3eae53e79efc0258595a3564d3500bc168 GIT binary patch literal 34876 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 u9vTDR);RK8H4Y3<^pZiu@#|1M`3B;C2)M literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x64/pthreadGC2.dll b/Src/3rd_party/pthread/bin/x64/pthreadGC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..841d4a21699017e835f330b3262e5b46e988a753 GIT binary patch literal 185976 zcmeFad3;pW`9D4ZB2ktbiUUCqV~rYIFmX);bpi?8XwWFAh+Etccf=dPiWoXG!tHfz zY}IO|EwPGFn;O;uw<3z7SjE=mUI!GlBA_zA_xm~L&fJ-V#c$isU%$MRx%cen zInQ>U=Q-z&8rK@?914ZH;Qw?w6q<`){zc^9o&VEBw+29^M_A1j&H`awh zm)^E_=#!@xoffL?6gp&&P-s)=pwe!gI)y{qQRp!IzgH;qL~r~Svyyo+|CQ%XYTSG+ z4B>@;#h4A+s4_asyh5Q;Nmg~GK;FAI`9w(s!|=gGNa9>KrTP-D~JOE0o6LgEw5C@u)e zw7cLp@D~X+whf-73Su|_fiAa4yK<4i7vFG$R0WJf6}kC-lP#g9w!xRF_5?kUzrj8y%E8?l=|;ea&($y4=Sdl&eznn8@+ZMmgfcmi%%N%(vo5 zq26z^y-G=;Q@w-Py<&IQKA-E3OW&cClm&>;P|3CdV zAuxJg2#ah+H}89t4j#M`VxOy`7?h8KjMXHMlhW4}mfo{QenZ<+R5mOSP_RkGQ23V(#h=$1}K zY%i3aw;yV_Gqyp1v91j5f(y^DId4gHTY)4sr!F&{E(caH*O^dM*~az`Ujr6VnP%VB zX7#ejvjtWq#;{;mL8z|s!&JBNtw=Ru=*b^QgS}oV&O$|0Ykt2n?G( zQEzXW&l0Bnc6wE`@RWgQlGBq(ovY@kW^bd#)H9~j!#x=7U-GdufCaVck@harPGg$A z+>Cx!WLAGQxwq-;!9G4`LV6XFZrq4&+(wQzS@;mD@DGo_(2vQ&d-anV>^*qPABJ-_7L7$WnDNzicq!zZX`feCYT8rFD(u%y`|7eP)3(aW zP5bW+_N8T!276iaMNZ+l`(lMQW0d7s5%)KrghKVUYrkXKTiw=1c403!Vt+(u(Qi9j z*T>gc*Vvnqg%_cD^qDptcHyvD2mP21dTDUbI!0|SG4=~3hNq)j3T`ZMhoj}_w*@uE zT}zvP;~tm-Ebl?SdkUV|iS=-%-;(dsX(lzlnEGWVv9lyvW8#~Q*iX=NCSxx&U@!N^ zBMt4<#XdA*r>p$R)ttqPr^%Gm%X8mJP4%+s(@@Ae5M?Vjdk0JPh`*Y!(j%Ai95 zNi8nPtg!$sBvbLNx1LD!F8ufz<}%5&V`U{6NjiM(Nu+Vp(RCf& z;PjXDuuC82}4tGBRz1ho5dlQ>Hf}~n( z+Fm-Gg5r>RLg;C}8YvPlz}QmjG2aPTpvU%M)$=g2gr>1Z!Wf{qx@@ebuqG2IQfh*h zu*y{c9w@!Gj0{p9%H?53G04@b0;ue9MLuAni%ayNeBkVX{yEyjRmGK?In8ESLCbWy zK2LGMv3GYGORCQR*<~JNRxhz0G^0yo#SX=I ztOL}F_4O`CF=M|L0Z4R8Q9-}W(T_WO)ktj(c6OfMnaj=&;G7STzJ?$p^|$PLN#f&} zig!KQkR_C)`?;>K5Q8)F6v%)j1>_f_WV*o?vDHhXy&WOAlH z=}q%lFhM|5BWzv}^G!XVz0BmW5&>{>Sh4=4Di|_nV79BR<2dw5xfm!aK>!2a zRWmAzL zA1{*Mg=NL^`)FASejDru%S!p~aM6v}x!}5HVk*i1J+O;ueF{SJsG)ylK4d25lyT!h z&Mv>nSeZ*EjXx95>jTeg{b$Gm#VL+gtCS?Rf|r6%2tPbtV=PGcSi&`NZEdZ!=<#p`~50pc|`* z7Y$%G@s$?&-f`-^T)&?x?|L~P1!XxLy&R5S4o5G?^rc+AzA?=E7YLN1nA3!gMmELR;89XUO95|_|IU>BlRRevh^#d86Rl_M7u5L%0D@vOT(?+&%DffIZ$epQ7Cw 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 literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x64/pthreadVC2.dll b/Src/3rd_party/pthread/bin/x64/pthreadVC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..165b4d26ec41c4e563782f116b4a2e96936b0fef GIT binary patch literal 82944 zcmeF43t&{m)%a&gHd#W#24pe37Y!O;7ztJ)QnQf24QxtKP!y0LpdhFbb`^^V7*brX zsnkcc*3Z&vtG2d9Ygdyn#M!zkYuLbMM@F z&pBt#oH=u5w(P<+Dn%)kinDE{*7KIXBK@C|O zp1bU+3nR1mUNUV;q(X;pjSSJ>=SMEq?~C2|*^&AB`y4m?T=zX$zt5RJd!Cfpwg=@U zO3nFNx;pNWjT61NCUs1>TbB%_>Iu2R3C(|+ceeJg@X1U3U6k_6gFo-B)(|VeATwxeEKg;>xM3xGb=Vrsj+7g)P$49{s!0LuZUI;b&{#q z(uv3i3<9Pc-Yx!$l&T$C=_CwDodT|&y!*CGS2T3KTcE%K7^&{OPi~j4cIcwTi)It0 zVFwIVfR8iVr7Ie`Xzs!*N%#Yz6)@H98{ID5Xo>#+>2HVpHT0gQR6~zhJZ|pA<9MLd z@XRqhE*D?U2|NmNcswdj-kJgPx^!@9vd8W-?LdyvQP8DI_5Z|Cu9 z5szmi$*&mq4L5@OhLk)WPaVtSTm5+K9>n8!U*$3WP9DDojSb;2k5A6wu_eLdDu!l5 zrsTMWfzz-^Vn6#5kGsTo-?=ZJiaTB zo3G^YKq-%5C-WF^J&z?){9o_q@!oYjK01v@?sOiv%;&LPiXYO4$C`2;CrP0{5Rj~t zZ0`sRet8OyVNiC%3<2j|(%)k*=J9zAk3L`F5tFdrUBhF%q0uGU0M}7mB$)^WIrjs_hKI3 zn9Czm0Ji@w9*a)oae03pcgbUuVBz_1@%Tt8irve@mb|au%_GFnX=plw#{@xl?;bpQ zjOX$2w|V^X93I1l@%SI<){-#JnomzTcmGi-pa$jf=AQ&JbrW;kKPhC zSTOU>XdXvj&f`rq$A>jH(+*vy8{oEZX?AlHr+G~ruRYV9 zCeA!a6O(NfPwV!+W|&>gSEUK(254w}pkZzjI2Rft>tlIF&4e85gpX}InS^e*UC4I3 z@MKgVcKEF*sMR1E4TweqqR|13R%3sCzuKz3+3kB_RBwwNWtf7;ShtlU-m_~r$5RSw zH?8+6rP?QN(LcldctbvkpoN<8FWRAx1zu1?_WDd6nrw5yE+av6mIRDMd7fcT#BbzC zy{Sebl4m5w5l|jB%n4J808>sx*hq|-s(VhCAdPr)9%P7O}GuF8&OX^!+5vB5z_sxxQ^*>nbnCAx~v z;IK%?b^wP-I)auJ;So42uYipNS1n5sqMsNrYNl)j8GDVYjbL&{J#MFwDBJ3^yhm^q zav2GyJecfL$56YdbEv&y2ync%wSd~sbp&m@A5TY6bNKl661&X~{eXAF#?Nh7=(ubL zlQRQmx2QQgV24%`X?A0jnoV|SAs~zpcI~E{i>&XI7_*iMizUq?VoQ}MJx!t)vlj6w$qLrEb>c)lGHg4Gi~1q?MMtq>7h6_rqz zFxn$yUrEb0wLr$pI)XNB#?#!Uoeh8ZM9TYT#?%e1>TEHlW_fj%x2khpk`p-ua3Znh zvYZkIN%cic-cd7_TM;!E=FW?nb8;&U^T}LRztH<*xoh?No?Mn&GFvQ(%$(7|l-g#- zIP&@Lgn*W3iwx60CI~4OY&H_bNPM%I?bq)a{)<;{_-(&2#>!{H-%VG>{y&QVy77~q z(6h18I@?I87s06}nj1Fe*IF_wq}1D?H;ZMYPR|Voe_5QHS2ll-#6(l-OGoax{`AO6 zd#XOM<2mc$#n5q-x(aq!<5+e>A4StPQL+~}EE$LZLoL zi`Eq978x~@b4O^Vdo@9bKtrVd5s3I3L@G3@UyP+h>{=sbSNyNV38Y1l&!%l!-_0TO zgUa*C`h;w@@;;6>NZFx#$AAO`m2QpY(=I`m|&oIm) zz|~>plKyBneb7yxj+Bfn*0FhXw~y#k}xEwFmqweHwXWW5uR;pD|Q2~7Vh zgf@qP@6yCH)-Vfl&I9d51^XE0AYAeqiPI8S1xjls)Pri=;wGGh+iR@OdR7vEa)G^| zn&q0eV4m-xdg4gI(U#6pU2ccQ=rji$p|BOI2kRJ6ovl6B<(3;}=$;9#u3G1nZ+=Z3*JF zKZ#)BC3c#bB#0~688aY=3K3yo2Pn=q7hTj0-G3Czo#%7#6#!r0Y;jI$Vre)sSrBKK zrxm;l;tJB~zed>t(8aLSs9PW?%aN$69U5gbTA&OPpXH(KoljuT7i-T&*@k(2PQk9o`gQPk0BF^-d^Zw=C$7)2$L1JyV;w1EY#tj~ zr0{P`71SQ^#nRB@gd3SPPydMo$sor6L+yXAL#&I3n0J{G5lQEJU4~uhoUmI+ziD8mOtvY*0uwDIvUi5VHpb5crTOkQ!Kf;40DMAOKNQoMI|V`_0?cA%Lk zqKVnr=zq`98N*UPYujgQD*M}^Qz@;~{0)j64exw&i$EMvIEGLR#0`c^_+JZgS6*>c zrpPOQvqO&vx@lG$dBr#^f#sm`%0x>3Ps=N?UnoS2709(qx>bGLWqDd+Qfg=NAn8MH zdRLAvB`xA%$8xl=_oO`Ql<%YbgUYj%ua^OyoE|ucfYwqzYO#KS@Z)}Hvk@xXZuR{= zI{lqcl^x1F-G`>O9(1M&@vuq~@&ECgctloy(gj`#Om1$QYugFKT9#lDkaSQB2E-JS-|o3s)`?#)s-E zp2-tVosfV{rKT5=lk^(!awoP$%R2T7XsIk}7Qsczsc;f%E>=TYH7{Cua}v5TCjb^R zI;XVoMyWU6O%(B^i52qk*ABa)D}*%AT*8A=Yex6pR5&THj3P_(){{$V;KckJ1mfm1 z9hc$K%NIwF(*L4BvU1>IuDDA8>%dFH$NL~i#;+YpD{z+LBV;L_ub1MD!(=J0`xS$? z5nQm1{Q;x;nOGNlqmfb@|Iidd>R`{VC1!L1$=2-HVTSIuWrcg}iz8nQ-d@`x#>v&e zdaB3p4*Anzu_xU>fq94Psh`sA{{{T1-THa|>-}nntdt?uj_F8Was`RZRsG%db1xz2 zAW~nKtGm{9A)83G?Q**KCx2)9g8m2LLmp0DSnB#a(iem~uUZ!&`8%sWd^q*X{;bFc z*|JHSAIyf_-xuLP)M5p@O3nJ<39MbLiZ^Tl%rx`H8yO5R zbjJI1nPq_{RGdH40a)8WuiKrzW&FSdugO@CF$m%r)!9?eRP>W%Q1-JB~P6!(v+i;)`O)v2XtD!4+$hO z29abjl8BU*U_xSP;*|M!bW8U4&vUl-=LzSl-WFVS861G^{eY}OSSqovua!;F!dW@N zRTqF&e1YJqiTKzu;c<|s9 zwBA*1m96&*+G+J@QBZB0f*#NXIl!vkqqnskV42UwQhJ-d5V+&n&jXLE+5D{E;o{U>FD~~T>X;YYGI{bf}@K63qK#f0F!u6ISc}Hlt$OFYjF!S~u*HFbct>_0J6e(=h8g(~K?E(H&{VF3>SDPiRlpYwt^yANgXvWxZiu z5N3U%aq_IeTor%U)la_QX}W-#w9M05)21`qs=J22B;3x+;V3CFGH^E7w+)eo1HQzl zS3aO>CZi${Z~R*b9Qs`O&R&g2X2#bOt&vz7q*7HkU`~A zW`}y}=A)Z;zq`oy%FR;dVJO?(`LMmxtz7H55j>btiW75C{paY485Z-P)`gG=vzp&9 zKW{v3gXl1|$Z3aWYa||khXq%ORzULRiCPPiVoh!~p*mwd>HjuZc9dj>T^p&H~I4v*|3m9(LPzd5H1?5Xz6?FxuFaOu++#G#0iiHKP9$G7* zf6=5+LZ4gjDxY0Nv?!rN-vBFb5?|J(G@eaupU00fYC_@nWVAhRUG_YZ zV}0XsM0}?0D&{7019YyeWrT`_(q7$=ZLz_ief}ddWS^C6tZus4 zJDHxQkj@VMlrU$NvvRY5;0xfuB0n274_YF`t$ZgN$+>FO@7~dHzC?@BOJGi;ULi!o z`7Bqoro96_IBy)3g!4YUI^8~6>+zi;Bk@>n9vP#F`*TO|7I+s~cRg#{1^c2kGf}dM z2@exC^KA?{gGXMJ`88a+x|_HE)M}b-xdGKrs%*7q*lGi+myYge(0@P&^q~K}_ArR* z#^rHxhCpxkuJtV1ZmaJ-deEHMVMfVKqbe!2>S^J}l;{8;wqT86u9l#v87MU;<&|L| zU7EO-X<$4nk8PrsMI|f3k~u!x3lQlEwd@+nCu&)Aw2oRpzrhQO< z`yXdOI+XrX9dJ)>Sf~Ul&*$yYuaMRZ$E@9pVpeV$>V0+24E`6!2q^O0{gLp>LT$ef zDtE<>DmJ@BYKnqY2L@h~UYv;Z?^qGovAhwP64ATshIv9s!Mml%n-R0#{2*c;C^7$l zUh|r?NKMK6jX^u~8$j-;)*|MXk@dlwzXX+3ect7obgh7sB#*aAa?4SYR!2#)a+cB9GeOLFB z)mx_%@d~05d>pZp0q?HgSY%zKWAKz|Pg%fxmGpHq4&beO4ATB+i zJ?UCpVh0Qf$LhI7%Pv{wZ`+F~Nw$jg)J8;U$6IfuoL0P*mM6_9j@R|F7XRX{DBSUh zeq98?oG8+Jc#0mLH4o?Rb!JV6-4!xYSwOz^2VtYkCd~s!uN|GyvpTeicp1=9^Y;<+ zZ-O6h7SJR%?6$GFDSJyKYl8wgh4!Ivii zWjrCGq~88VeW^FRiX!G4){M=zU2M*DcJd-spQObCiPa)BHFho3?_f!jo=B1d%SevG zA`#2=C@d0%(okU}Q>~{du{p6bT`~%p>(K2^J{rsRR#sTva7~t~4Y1mc?fETB%}bW* zEM-6Els{%y^H`b95u<+2!P%jY2#f@?Q**T(lAH{++*j#NhL>^zR$5(=Oju#9-lS*4 zb;9~sXa104WHwyle&5Y^w8okIOtx*Og-gxFd8Ovk{4#U)h*I+{VIlwPwM3%duNw8n zo6tloV?F4Go3Mu?!yvz4Po2M(7zwR8H;kd9j0psfp8u?ijH4b223ZWEEmvt2Q zec=f)O}0Z5NryTnG3slS0$#>Ar4)davvd*lvQqNbEYU@LON!8XWr}$AX{U%xDPoV2 z7%)MKuw#B}(?&Rs|71R8Jr(wpMs4;r4wvJ$nUBwpOpZ)JFkT^mFh@QAds(Ev8$2N< zi{$rzzq>>M)V)LfU9eSs-fG z8RkojE@>tfItv9=zoD7Hm?LATGA;=&r&{YLwL;j%xl^Nwp^faCl(A@el5~doa-!Oq zZL@aoRm!^VX(yp!B!&j_fPv8a3aT>_W066H;ujzcZGrIfh2WVn5A)dI;i9Kqx}tNn zq{P{e_Y1a_vY&~{2>W$sLup>*B6ORqq8IflWnFy<{S;G>VD&L<$cTRBVYl^H3dy>i>GOel|9$(@2ZHD7!duC8wmW z$!Yi-{fU~F=9wFtB4S?~s5;Pvt+1)f8uk#Qi6qJxFSi<=vTZr!YvxDIC4u6a`CU$W zE?CXV71`<60)g1&jEh0QE=}xBs8#wgKM>1$Lfge0z%J$u7`&*!82QCsi_b_l_~IS{ zuwZpJDn@o}ZqCD!#10K3UYFG{k`!#FWv={0N()xA|K!z+C17?mu_Oo0bK%#PNEcX4 z1*>I2E32r6kMOu4MxX==ja!m{HUM!rKCrkDb+17BIS10uk;Q@ZCdvHnCHgNoXcb?{ zdJom#BCo$^C;Pi7TNB8jR()OP^mQz8y04ArXo_i^;Pf>|DS&cdS8SW@Bol&8+{dxqo8+8AN0*pj7 zH*oHh^EDS)Qi3^iRpDob`R9oFISSuJLF=?9VHyX7FdJbShS^WF84rSaVG)()V)iuT z=oy2gf)i;sqG*C5R}vq28ZW$JWQho1caT|VAVqk^jeGV!n31vh!R#@P z>dWCULCs;h78foJH1u(K3bn){$tI$>Y}3R?9#1y$zAU$i!RpIcP_}61(_-;ioLCCS zS)9{wv#=nh36`re#!H0b1Xn#RppYraT0yTP=zEJ#`lDf%Gl`)Ox;=Lt@xf|Fg$ve* zS?7RqlrHZ6+_fYq*mudAQZp5EpdAUi(SMMi9%+r3z6Yv{kS zKH0UcnaQr*g2dmV;c@uw1bEXoNujH0_%Th?7-{%EzLmDaq3*=*w9M&>HpzokVUmYt z=NHcKy($}7DRC9r9X(7NF4mcn1G%($AbTl_NFh)wx5k4A2g{m6d-KT1_z2Pe2yg@e z9&jAaQQ4MD?k1V$lE253q>dz8)Duw2$Ungr$D}sM=I(ku0X>P&tEB_@_wwK$Sx-VF z?~((J0gVO@v~ev&KMxa}>zlwF__CG5ek_FdSmWG?S=)k0)&P8jsel93cu7ZHGOC^u zXfir6q)g*^C~j!hwLClO4sAH3=)IRjE}Hp;pV0X4Vy127W7Z4IAbpVPG3BZH;Y&&N+rb-ez*A7`NT>p$lj0{y z_^Rhz&V!}Xf>dhi;Oc%g%yhe&b|jK_O=*mk(f<>Y=k zo5~wzOHJSkj^7mA7|{Z1hPJhC81FWsLZ)P4E>Yeqcv~Z>4QbVBx62VDC+%<~zmwvH ztl&A7upreiODLQJ|5|LU%!w9i?h!>LAFO^@mv}AecZVNu<6ERc-J?qD)t@DM)FG`& zj~rUl{L&;hODhoq1u7Jn!09 zM#4yMb&)U)SNrR+@Cq;=ShqbQbc)#oHZKid9A<73qebhrpTZjCl*Efh)t42veiArm z9fDlwtK@bTAy1gB^R7>`w#P7#L7?q~|PHU+G0B=R&p2fdo8H9i4N9|4UN@yDO&deC<` zHN4qHvvdp`$71^_e~n&k(~`9THrMX4lTIBDKJroziH}km*8(5iG@y4A*tpe99|c;L zFUz%S6X0X{V3v&8h*FW7atKXx0wptS0&T-mP_O6n^upJfJ-sqN(_Z5g?B`Dw-G z#+uaABKzwT=_ODV^XkOx;*nd-S0cf$?4&z(sE^x~6V7PUl^xVUSGv-bY)zSV=w*=C zA&Dc&{ZpsLr2nv3Gy^V?ejs$p{INhr8C{?4s_?P=Wg*BW*&IxRZh9CfSWt09s8~P{ zA8LhYwjDZ&mNwhMGZs;(kfjytfTGPo)*mP&G0^;VBi8DF0~XCmn)B-s+!5Y73u|fr z7b`cMgA+gqMQvB>ZUJm<9K)1u8@fO^=yTRH$I3yT9wVQL{nhl1d?ubW8mu~y!WlV+ ztiw;g`6JtQMWBP;(9pVN0?ISp{nKGsx!D{figeke&saHv8FfP+y;&w+-(9NAlqwsx zQd`G6s;$d!)!!*@f6Ltd{tyV$-!X=H)Z@}$ud^BU*4SWNPkSqTsAjy|L&55L5lVE2 zH+jaBe^;l4o>=8^o)bZBvSYZ6pm=)hSy@D35dV=e^7+`Yn9T09;`Grlnao9`!ekbj zpP*sVuuvo)HZ@MDojor(t-;O;~YWB}9 zMK00hiJ<3*iSO#2D6|wAx?C)vwZg3M8jN_~q`%}1u6j*6UNaF>vMGD5vwuj_Y9_En zJY}6VjE|@}ZE6`4VXzeaL9BO~IW3#dIJTUxGUHm;Kg57&!uWWdy90!&an-Y8nW-(A z*SQ-;W$QcxA*&|a^g1J<|}1zBwhdgD`6 ziTmOqGgxZwwnOjn`M6AMXlaVy7YRrJWNwMpAkL%UW;@5xY)}7B2}3SFW;4Bo zV9hi(|H^(DP0TnO_e(OpfMcB%e;PX+jjcr4L>aG_V;n%dcYOZ>DkvG}8js%miBL%i z*-B9D>dj?77-U>u<6Tl|hps18lw$>nlHY+4HVbFe#WAWY`<+%@Pn9zGK<}k}CD_hA zUsseTtjMXlgjrjfP_kc)Udx`Qx0;qtCfj~VBGhu4bVtbG6g)3jMGwko(tFTO29Fdd z=2W-!&ws3IrarC~+gNEWoFr=^k&6aV@}KM}z_XsJ0pnByaQ=l>`Ds<-Q4yPwSMsWy zMW1SLMVC2M+ZLSyLCGeG;7D%@WL@dq`;%#}6f!Gvy zo9!toT|mDP0>jDx zDw8HhC4bTehJNV{K1xqsR1zUib7F_~lLi&9?3a->G25~@ou6KKdLO zD_>+i?u*mpGu>PI2{@2^7KfQmtnaMV{hPs=6+m~g*-Z-hIqV+t$x7L1ev}x4#GFWJ zyxgwUibB3?A~pfj%`vHkDZ#I6C5v1|yubm(G1%>BOh0etSbHg6hMCj|PTa+(Ie|ME zU}pXkx+TV>Qd62ak`mMHZcY_q`}MqazvPt@h({x;Wt*>B6%Ihz-v7SPm%d-4b-`q7 zQ&+=9l#oyAFoF8`D?`!!T{o?PC$2nRb%{59Bmosse z1sdm4d5QU_hNTQ)qvqz^71Y2EjB5mSvB4Jw=j$@_t*_lH9k^KwGcuK81oeGNmmp2U zPSC9q1e3r5i%D_19IHadC~%GP7LW$>9Bc_)V?RO&mcW9@$V_cA@}H88gy@RS6LDi6 z=U_dFqd*WIRi2__4!CEWP+MZpg?`ohh-UZqf)(ZfF!P2$r!1ADL(!>^D)pD*h>Rq)w)fdaQC#HI9`=Xg* zHk23$=j2{r;TabKP+11Vy2-H}k#V$P(egZu`+YOMC?><Xq zQh;rBPaZ}J%qu#Ly9h@034;a`q&vq#$+$=#RDJCq! zY&&st*WJP^JlPa#k*0doaw&D_EAfz>Tu}V$DdX@;l=X$S@nenQTwOSR1FMRf30qk^ zw7R~K!=I;0lwM0r3q;IMg$K%VVyn9rri;ntglJG&-&)veIkAlBj%|0R=@@Kx$7|1k z1&@;wikPP*l0OI{;6arlgDgQm$IMf`Q3B=M`VYh-@`|*s;eX_-W0n=}?`vE=$c5S3%qe@l;>qwLUPp$kegM_5tPH;yJ5 z10wB&6t4G5H4Ef;9?`{#W37{Qd5nR9gk+nqSQY`K z2N7q9tSac5^i~!(?m=}~+#vf@v>-V^-R>&Jnbgvp!y&ok3g<%(sqFuqA-}?aE(3f1 ze}iq};ZUHfpM;VIIZ`LLXK4(2+QpSibKm^&!@*cLkC|{>+Z@y1l|d(<$-6Fh1h78V z3VqW-bNn=WOjx6qofE6y-y{@71UL(_H8G~YRYN+4rg^}s!UqANpmF3&N&+^Ax(_7~-h9ePy`j}Swu6Rk@mR)(n^x(4VadkOH{ zY#`Q^kcwl;=>j-wDEUN-@u*vvb@m->#vjOzan~y|-~`G79zXvFK_LBQYF*f3Q>KV> z7fk$b&}a8j54|%JTr~#DftcV1jOUZsIiP0jhSDM{OqP zEzC1AL6@7GTAYhQC z3TGruc2zi)cqKl!Q|(Q6O0a&ihG}e_%q$d|)D0b?aV=P#47G@9>6XZs2d~9P+8gE zkSQ~c#&Dq9!Zw3WIG{~O^^Mw%h`wHYa-R5y@Pj-gzc5wg>~aQI73)#bLoa|vh`;I? z(X<9QmB^*B+*_FNu8m!Qw)Nb45e742Sv4gnx);d4S&@j+?p++v&PX&vh3UZ?ZWSPj z#&NM_ZC$M%c+M*9Q_{@ES7!1N-Hgy zNzX@W(c9_o0mS*48 zXUv2z#li zEx#ZI@2e=T8OS;4N(xREZj(y8S|6EVx z{er3Q@26dTzICzINJq{?qo;qU3k?f+=VEx2Q~S|RLBYXQXQc@I7CjmtDYR|-kZIFPzX{O2suLR)>wjNEf!m*Q;jf5 z#eEcPX4jTF7xI*Cl>saFd+9|p(py=rx0zjUeU%K!gl*P1*}D3B^q#fsUEigs7#esi zcP&0hpaKQkMOvfJ;!Wx=Ou0VKdh90MQnbRi{7zb`+c}TG$dkgmip{15lXo=nW$WIA zmvoMoG}}rzfn7P@M_{puYMhQ9PXWc|UVxaXzYA$N-$vnpZ~aiSznCU^VJ&klq?%!ByN|B&1Mp71Nj`)Ec(x zV1KF9R8zJW0y%(d!dVcC+%So$GH;K4m0|@@h{cPp!F|Uzh5nLBvQ@?dn7q$ zmn1pztb2b`d1>M-VgF%A&NrN{t7usrkVzGf3Z8wyn%e- z?f<`B3ktov69Hw;$)RKCTe^fm%y!wniDdPp^2TN=JWY~j-W z%h@3e7mmqUj@{0LKw@dGZy`C_|TMJ%rC;-{t{*=#hzW*7qfuUcZW>2fYzIx5d?V5#mLVs*o8~ zxL+%gsikuB0Zzjno|{71;K zU8O>wE%j-#$TZB;r`(6wZHr!KUp&>^-`EfR@XJir+VHRF6?|APG})mz-ny~cCaLPP zF0md}pZQ{$k3%15aaID79s9m^J72z7U-Q=(ICjOrF~CncHl~=@YbLTxBWTon-vd3! zB}I1Vt=$x^SJ=F8~cr0##kpTH-K z=_IC{T>SV>1@@96>zRMH#@!&qpW|9fvSL9Yem){0&Forb`-0WuwR|`R%6}|(1rWhl zPUcma@e}a_xF(#1xsYuAt|5V3(;?$v0g?r2JT*@8nwzzN#Ue@Umk68ix^(8!A{f|)Y1kkri2Q5Z{7_eQGXGLHKPEWq zq?%5t>l|`hA6)NImo`+kLk|On9byuiY`bs|k)rns7vg@ImLk;f6`gl-J5BNqZOM+c zP=aU=l0PF^5t)HajI*F`8~U#x@qcR>osXyefViXa#8`?2gd+dtc*@o(T8$?eH_mtxU*XSWR0XU1^OT;n98Y_89&$X*BgtWpr)|qRC$*Ur z#CTZ3PMy#@<0<}vH=Y&{=Z>efcsm?VFKB>`DsuBPo=#8ZKgrF{c&dZheFvl5!)jpY)SU^@__=2m_7$h>;*r7^-+f6Tr&2wwN+Kxt&1_9^knnzuEH&r6=VF{A7?tVeMtP&8`A?cZ9dhn*+2(sRjE) zEdAMb4{;AYn?&4i@wP#nBN1c?F3eHaJ7tKxxj31BzMCJE-HW$#logTN+Iw{yl-&)W z580h|3EKDX^*dYj@1<>C|F%g>9kKg!ydC!M6pigs_c~=r|87g>Pq_K%-&1%y?_UME zE!DPv8-Pi><{vTudxMqlD8E46zw-`W1^j&Ps@x`IM!Bn9F0RAWu-vDz`DUTEkmg2f z2KX_+bZyL@jbfgb15>a==MyQS(alc&f*sapgD7X^W~t!tRLwJfWo4dWc>-rYliV|JHCSY9>KO)d-qruuv*nAMegDcTlGcqnXoYY~qx;Ncd~{ znS8PSaUCaeJuZDy7-TC=z;NeY%3}HKUOE*8ui@myc`a_eTz2`YKe0P z!r$<=836rlTa`h2y9-)uG<7IPMD3^;0$6#=ZBKEF~dP;Bv|(8VNa?H4$=f4Wy}2X zp!D0_1ds)dj03g>R&HK_M@Gycna2O21bV-z_3zi#_|Lu-WF@bkZ*`w)E>VYhpXxU@ z3!Jso)zJx}n{;$XTCsUL24;J{_O!ZB^;eamN^Sh7?^7LpRgzZGtU0n@a@+9LP115S zf6jrPsiG76f)rvR>3ZjLPG7!8>itNp#ZQ!pHy=CNXCO|k)w+yExm@Ktx_x4&f%+>T z66(u9N~^W_by&mNjqbS<8f~7>MS{)|`<$?+J7V^zl_`ZEqSD7EtW+5h_?nE%h6rnBJijqFowdVRRobpS=Ioma*w#3{4N0D}&BF*0_Ta^3YIX1tA`IKxV z=--=k*iiBN&eeKm*;an1f#h0IwTaZt(titWlsD-(zrx#@z~5WS=~Up)*0CK3{D0Xf z*=Y7}@xXmtwo`S0IR%66_EU*$QMcTjYOh&&{fZ==mATVfQp&H?)s0GgT{^9oyI-rN zUaefbPFmmyQ;*Xa@I{Y6y^x4VSIGkXNS{l|fV|r;D ze$?WFKy7TpsH*6X`en;6Ce|F!26yC?r+IQtxjZ?2G&>o zw%-^TpcOJU%U5nZdvs9kZ*YISaHHMRtlMY}N;wcQ)%;qE*)!#UH4Dro2Pj7<7(H<= z2_d`t+hi5GbZ7O-r2k8wkobfmxky~m1W}dhqc=4ZSZYs}EfB0%tScA5W@P{nQn1f_ zUJrp!RVPCrD)nxQ^AqqedRM*3F&WvL(DoXJ^^%n5-gk{nZM*XlbDxrCjk?}U4uz$^ z@`l1y#JNM^2YA~Ih3;CMdYP_x>s`R3>7DWLrDP!|yM-_wW=UP0j)xR-TldXxGahD8 zz8(*5SMxgPs^P zfiYdK^LlYE>3FpG-|*J!t^8DhfWP8KUe0<;&T?6I19iQrwL$S|*4O!ON;tPG+o9c0 z2(89W=S+}DtUfZTl^%}QkTbW12FYhww;ycTtf2~&)e z-Ot|1GsN$2eV=kLppBhYm9-sjmqzF%Ys;m&90}mCY85X#^ajmD$f)yIN)G;H@hHGB zSUlGD;JtFQKF(#Gf8V{%q5%@NCU;pBQpdj!|+$aZ`xNkn-N&tdD`*>w+}Feb;k*$t0z z8j`C?_!9P%FQ!t-6A%(J$BStZ{`ZocOmCjN`>}gAs?w4qtC`QAT68yAstcgHF=5gw z0n(+U)438Qvf^7n$G!}JHGhYi%41k54m;a#2fp!D$J#U0bbb+cViX?nPV zua4{Y8ppPboVG;5Ix}a5Xx7ozxn5DNO0mxJ;OZR|Y(JQq zL+ZxJuA9v8;V**8yX(E=c_gv$k#jKS`_?6bzT~BN_x+;9nHJ_axyc_cP(i~u*`Uvg zY_0%~_3#gUDV_wuqbAOR*M#PhyX7S(g@kg+$s)r%{n3?z5dX1q(O>6sT9YH$H}sHIHS7eubC3SL?j`S`W^EbHK#vN*mmpQ>^^=Y0p-(-g=KfF}eB@ zO)02tIP+gSU+CYT(e^K3!u{Qu1T+Nd8g(X|#>G}%m#u0M23o94vxp##F#|O!6X-uQz zxGI6)iOJ>;&$69l-f4YEEsb6E`lYdpxyid2EDSlY!vD|z){D>!KTzbH0Vywdx6HKk zwQ{d9q$^pp23Lt8kt25nS82lsNA}t~-5aklKk`@n4EX_U#tmslGIn#UeIx)hK|5W4 zUavo8k;i5Bo?{D zdd^S0VSV9GWms!&c?({KwIOBcn-@&YEe&hWg+eSltRGI3;Z2P)s%dn8nv62$W$S*Y z)!a36-#Us5aQlCy)+1VXlZ9)TDjN!5PD$e)S*0)|xIO7mg8A15qDO2P@p-eph0z-K z^E-0AH#H1y5uB`)`7IBlk%dzdTEPFeK^HrPk8s4El`W`3wk{bu|8U_O$Yqx+# zzV7EMSxnMX)Z(_SSX)8$MDix(li`|<&R)5^&G}@RJ+Vt(eSxG6R%b~vc)t*4xy&gQ z)PAyg%BTjey@A=ZGica_Yu~`IbvoDERB&$II2e z(DhUyqIvwzYSbJu{Hs!(|6)2PRK*klK{1;3?3-(cdOge@jG37$S84=)oTR8xc*(u* zT8E+-&O4m>H!)Ri(4ctl_Zn)NPmHbk)agguUpOv&-5(0J(X2Z*TIIS!G)pGO#LOIf z9O@1F-Ku{{fPcZB=KC?+3r*S~b&5umyZUH@)GTKph)5(CUYM^4!s>N~V0FO5toe#Y zfb}#H8n0|{yZL5_aA1vp4$TdGr}{xFHu3Nb~l)JQ#L<`Rw68cJ-5)OS6ko=vuh zdrwNikCI58hc{}TGFa!<*9ko5Aoa2H1gXLhWz$1A4OjHY&6el$dlOr%^&h)<%@n-u zWyTVn6zJU}=)LNE2s?>cO&iTU`~tCQ56OXc?I9UFxu-@>X@*N=tK#*jxh+!l)`7@A zl^Qi)j2#u>hM}_A<*EDYqbV;Mv$sZ%eX(@*rtxO4Y_>46W!pSuLSSDcB^b*L{vwi6 zwYSN*E1I%3TCly;+y<_=9Q$-SEV^C!g;FL*B3`sOcG7~Q?68L{0lrR$FTwa`6ld*AIcEd{aI^;T&T=tNY^>d(pX6t7c{rnJA+C%=R zpY{5=T|b}X$+dc^L-MJF9ZRSGkBikS=dOs9YxZPN-}WH>gny2nXZ$7d0$T_JzQ$vX zs$-SaH;tg0tO>lVePRevlU2aW`ZEu$VASgpgWnxaOoeOBL-1O{zEMS3q%KaV0rmpB z%-qW@Q``zvEr()F|LIq2)$-SmmG*2cHvf|FA45>G#oPxOXv{W7Zi@ANTr}BEJDqK~ z0e3A0U~e_1IoH*j{v~{K-EA!R_#irK{fd@0d{+jWnN>p+zq|N=5cXbla9oHeKsU3d z>xi!tQD%M`2bfHnWOvT=}LsK6jX<>FQ+}D!Z4t?UleYf!6g#Qn` z5~F^~Q+_F{`YmbXYW>~tDt;!pmw9ogVX(RmPZCb%U)n=zK2^Jrx`G0Mlwbc0m+7C# zC-YIJ1E)JcVB?ojR9au6wwkPs!&zYXk0%YVQ`TifGnuX;!JP#zlDUUut%A>S=&i!OAX0u`wL?3-2tK@DH6tF zXnnRYh^aED^w~ST*`A~=`2mxm}wV-6?j8!-}7twboYh{|@wdlrUXT}-z# z3ic4(@Vo;zgtU*lAf%c88_uAiG>W?k&3c*g#PA(tKp|VqS4A4i=ggNI1;Ny`jQR%W za-1m4dgG@9pVP>uZ6~ZaamkN&nCs9Jhp0@h`R(RKMC_N4_x)9NO8ksug-?@se|^7g z$3APmhJ+Os{l5Qo!3yon_L6z+ZA_zQNUe4L30DD0{+yW-Afg%6Km>mI&abaFuZQ$V zEHw{cwPd~zCB^;L1B@(w^-g~Dj%(~>znBsnZB@f+M|#q~_k;ULYT;v6>?eHR4WS#kzgbfwk{Muh=hgI2%rd&71KbX^m4RFe6mQb2XW#L zN6F7GZBe3ZELCtJ*Aoy><1vQ0%CYuK`0u9yMvePfRSU!MpC&=3|0a1Q{GR|5NzYcf ze11yVb^cuhc1=~ab^aarguKb%tCSSQa(9mZvnw5cr5(EPK8B+IEb=*m?w0_PY{-o# zT=*cYo_iGds2bIqL=c*jt~=@M(5FOfkgD_s!f}#k4r?$N3Jmy=aOinzbU2^m9(Q(?2TMimsc|K*+p+k4dzC0>HP^@*w2mDWL-Q;)>7MKdryS^W6Dn2SZeN+ zkQ)VT1O?5aTsCO;@2uME<1UL@2ZHtxMUU(u9{_)Q$lLn)ihgd@&ka1G9pIJYz{~WH zyBv5`&B@JI#?!;T_q$WRbrRnr>$odp|4wK=pXkP`WuprNk!fp*6S!lK{9QS2TwhwS z#}0i+u;(w5B%~hU1)M=ZlsqG%+!42)*7HZmvGvCK0=K&8$lpcLPDB#}HY`L3vi~J~ z>+w00f|DKM$Uq64KpAYKi5B0OZesgRaStI#d+KMZe(t+h!$Ch^*UugL`4mr&l8(HOK;Q@jjzHiD1dc%9 z2n3Em;0OecK;Q@jjzHiD1dc%92n3Em;0Of%FF>G3?ivjnCYL+EXe7$=uq?rK)r&Bf8Pn=L?hR>-;k=K#9bknfxr<69D%?Q2poaHKL`PJw+JT+`lbLZo5>aXHGxo%EgPPUvuT61&b?Z&7M2Ge6n#)Nu>DP zp~dqT#}>`MI3Aljy)rg$(cD>c7AHMJhk4%f#ta);Ip^XIsyq^O1Oi7Oa0CKJAn;E> zz?bS%siTz|Od0vTm70kA@KdM2!Tv7FA4_W&?n~>U(uQ@(_hqE2jNxf2V^|tTmzXCH z_1&VbiattRi;I2c*3~yt^?f^2_1>MOdT#EnGGieXnA1z84nIn@P3y}_QCV-MHKmq2 zdHaND&!R5*_iUgKPLN8r(Q9klx7(I>ZFbWMJZg_qY8!6y=WZKi_TSCnC)(FO&PjXr z@k$N%(rfr-=`vb{pO&UhqnrTcq|V7~SC+49it2iUzdY6LjI?E4o>C{Cz=^9?WoNVt z_hqN4?8%ubdw4gMy(qInTJGzUq58zKR3GZ}OItcNZm32P6g9;`e24yLK#oUST3JfMQ-baQCOOY6Pc(o)+p zy>xBfgM+DxA6o|rT~!muzj|`^YqKpqrLTgI4#It$kLx!H)N3YRi>XM*#d&~$dYvV55-D)SkCg-6rA zeyOS-V^7AJw7o5z>h_GHJ-FRo-K3N6_2RrR-RF$<;R4@SK>6o%Q$=mc(LCV>>J&Qk zou!->@ZC2<^^FBp-#Oh?-^p33?{LQKt-WidebSz$BCZ~bD7D1rlOP=?-`~YmB*T!T z!B4wzJytphPwm1O$!J%$uSc5dK^uC|h8~NuYQ6fxW0X1r_ja;A=X(NSJG?L{bNH8a zx_05QvsBwMd>LI-hHsSa&x&HDzJt3??j+}Lu&fMXV^#ZbC*9kmt!SBc^EjQqRd{x) z$|gK8r=)#cPO8coUaV3(sKeJYMfLQJR!N!}n5hPWpMl_KAg~z-Yz8jsyEkWNGmgUJ zp%GlY*Cv5QEJL*o?~sq=WvRT)6I5PoyvmzXs`7>#D$iG}`s_V=XGo76XoR!)1+i4s zpRtq5ydrgr zQQU`K9eo3;?}wQxv^z^>!Jh+jLhaJ{y8Bi4H#7DID$~o;@}&p#u~$I#nv<=1O%AGF z!@H|qk2d$Me7aI!#jS!@$)D6K-z77Yn&X8D4ZNLAeMh&C3&V?d_fWky=cvH&-l}bR zzD&Q$^z~H!rj$yDgV7$3Uv|W6bz5mKJiHfp>NU)1Yg-=E5g&B=*+E{-8@j?9fFI@d zrQE*5n(1!sY^9#XRr);`FqdvlXL3zb?c#(t5$Eq9&S{f7ceuPc*;lvE0^g>&N-e#F zIozvH`10d554+`lJ84#w|iky9(=yTaClcy zn=*WXR82?7Iht!}++Cy8&v7|joBJ={1zftrlpmK&a&k3FJ8EJF=GNoR? z-Q%@S;IKKPUAR+57JM!f8tA5aEpqvs$PVzQj5gzA-EyT~#bsuAuwZT{UAu6puY+)h zHzCu)|60n8YgRBma5pE*orxRK&4UL#d-qYmq$f9h2K-k!Oho_xCytwP31$t3O6Fy23Bpt zJB1Zjflc@k@{N~Y_XRnTd84UqoNg;}Vh3@aOepZ*OL^lTW=)tZ6TVKKcHx4T4#GQ< zL)4GC@Q}s{5^aKG5?`Oa14XUvL=` zZeh>n_T~H%9OH(!2$Sy;Tz@Z2+PpbSr)w7u4BM9he$d4!VB4-v!4Gl%4&pR^7Io3< zfQt1>ZNXVy8-VY-e1{%K_v3|0S-ZRIIlo<;z>_w&E643i+wiusW0F=p{TuZs!vl9o)lfM|lr-Jt*Y~M(tW|s@tek#;cN>Ft6@Hr^*i~?ySnMTa_*$O?YMl(k++j4kX&myr5|}7fx!O_2zd4w>-+iiIFhBIxWLgq zW${a*Tl@OdRXq{^abLlmjytV~QoFL*PvU(PZYAz9zMm!T0{lP4{}iq#-(2pk_7c{B z-^PDCZ$Iz;yl3FX6Zco%h{h^|@IUbVd)~jnJxW*(-)G=I3wIIz2=Ds{pUL|{-g&&A zA^dgx%kY1M|6BMs;WzQG$1TBsEN%z>Z}UEm_#1d5imO}jpNUH)F2MV7!Y<`KjCTs} zU*Z$YAzUqP7p@VPKFFsA z;0kdD?gHFA+|{@$+}*f!xXrlNaQksR!ag++cN(r3HwkwcZUyc(+z)V1;dbL1a4Ca* zDvTS2y8yQscN6Xb+-BTf+{d_H$N1D>+z4D0cQI}m?k?OTxb3*TxP7=D$NJQ%xN*3- zxJ9@G?mM_&;CA5l;{Jw9&-JN3xS_bwxGA{BxZ7|)!|lSokINq7Q^Rn@xbtyW;Hq(V z;eLesC2k|`Io#{Gw{Y*^{)S6G&Zk1S!MKxgGDCL3!YP%X{!LTqDxkWm4Ao6#sw~xA z1y#1{p?a#L6q-_g^WkWfqxz`6s-Nnw2B?8*kP558>KJvb%2h+uaq4*X!%k2qs*}{o z>J&9poyxxKX=<1%P{Y;f>PzYjH9{4tGu23SmKvqLtj<O6J6nyM~P7piG$x|*RbQZrSBnx!sQv(+3mS6!km zRrAz*b(vbA7OKnD73xY=sjgCs)M6D=adox2M!6P8VqG+6;X?0o%&aSB&s{j{;)Qc3 z&%b=`obzX4Mdqe#j%pb?b5<<&|Jpkf*f^>?j6Yvsst85pXlSEmMZ?u{m?Y4YawPSU zq&UHfLrBZ9Uhj_A6YtJWW_PidQg8tQqEK8EP|9hJLc&qpk|K_v8mJ%wN=!KfRjNh^ zDk?>dp%F#U(*FK4Z+71vrV^^!ita|Azi@xTpfA}^=nH8x_l3ln`ch1)(HB3hsxK0)-Ip+>tS|jB&$F%S*$Lxi-3)`n z#*{~Ou;v__X-u`79gKO#Oq$8H4&>KKvn$KR{S{c8sd&aoMEku=ED?`oOOxV;{R&aXEsj8N z4i(>88}deHtPx|S(i{#S&wHNb7$*ne8cR^3t`)Zj?0B(mVtL2jkhjd^6C7T+An+#+ z#+*cI##B3DmRp~(onp;HRv{WM@2G_6tY!3l$Sz%pqG^1QBQjfAY?78!n)@K3a`J;{ z*{BDhR3<)DY)26CXTMh5E7XYQo$6rCb>UYYE{q|Yg=Sz_g)FH7_?{dr@lm!;(j z#`Ud~Qr+!vwawg&8s}cALK0_e25Du@aPsM>>t*6rHkQCM$Bn^8tOL>JE%6Ns^B`T&}AH8PYnyVow5H5$x88=dEbUmEC%2HV? zHBem3%tLUoEdP8hhRY^NqBX7!glk+IBwBfG$hx*uHF0qJ?^$K5H>f_i z@Xc7ePBts&Y8mCC8MQ{@mYcIPP87HP`8MYVSF?ni5R${qxY2mZiaB|?)ccWqcCZ-o zXT`6^_3RAalM2yvY(&)xbD1rMeUVRDeAAQfRUA2(Un5kotyDr~tu)8VWG!EX7gLGS z^z&n>e30PJtwi}Q!eJ0@hHFzOxJajRwwnqvmp)V_ohkP}PDL}*wlm}x^JAZl+XD4o z)JLE`0`(F2za9a(b0V*hE493Dlpp#vyDivX7SSBWLk|D@gx{PS`R%|f^5@lpSD0Ss z|FID`SgCnnl06gV{t4MfF$#7P7rq>9M~}lteL<CMw|N&WPifEW3V3)7d`<@qDSFvp!pxP6>dD1@kF;B z$Gv@UAbRY0rCtVe(UAo_w+HjlE_??#8ogu@a{(5ir{KSWLdKrH(6KY7k(bRgLY5mxeHiFo+EhSF__w(V~K_6Q9X|NnE-1IHR5-of- zn2Q$P0$S0g|6aWOp+{eM7;GhvFgJ_UI9m8va5Z`q4*mJUq0e4;x|6Jpv=bicV&2ih zd%zsDaLY34MGGGQc1!)sSsN>;pSbCh7a#q{V3N4->8CSB=yCYnm6Sz~t)h=-Fh@;j z_{|>H9@_M#i|;-3vkQ-`;dv5ygl`7x(870t4z%f67w`Iw-(h_b7rqN@Lr;8{Hk{3P zHaC>{#ZBM2_|LCAkM5C2_ztiLE&MYOk+M;xE&`j-WAGt;j5&GFBc(HE-qr`=GfE3#FREy{O1h9>`@M^Fb?ZT&KSfgmuLoHtF6J7d`$65GVa5P%@ zZg3!4_+g;Xrtew2(R)4GLtOY^e9I}c={Xj^bLdMp{mbHSUIsoOzwiin3oZNxm_iG` z4Yr|8f3fL77LW3c;6i-L!aQzKgW_3+TfrK%=@&Nr#^NoWQ{cGd5k4MFp@lC3+oYZg zl)3>NiT`&J{?qq(_KRL}A~yl{OFJAFIsp9n1wcdrlucN z{HlH6F7gQPcRinjXyJL_LbT~E6%XpZn-mY#d6o$u1a^~W0*?NK^OyJ-eA?KxNs|L2JJK2m3nStu7G$!g;$L;Cd7qr0=v-D@P4-k zeox_rU=jI+zYQYjK6uZa%stxlZ;FreEHFh}Slz?;qlLc&Hll^)|MMI{?}886LL1Pg z_fkBVtM27kcpims0<+Mjhtl_1-pAMy-vr+a`q07?pc`#^B*jB{1$cqD@C4Y37JeLz zqfLLK_#-!i`S=)x9|Nsu;YqNU{H6y{JdfW86XX$o8f-)h{{f7kh2I7VwD8AZIlAXT zc!KXs=rQ=2pR*p&kzXkF`-eC#{zB6yC_coOz((T2;zJxon|?&`B_@B#IYM0c25>T3 z_yKSvddb6Fm%t?2^zw<1P<($qc=1gCpXnVGuiyk|CXetgFikze?}43Y;Sa!ewCTxv z=8?efH$_>~+b90NOTo>=g`WjiqJ?*W9D4C%tOxJ`TD;Yd0(AiA7rY0wqWL>L>IN_i zy$OEz2|jnwd*Ne##dlJ48=M9gqFwm0Nv?#du?dp!@x$gc<@$(o#^_jJ_7X-sE@#Zas(Fl zm##bgNxt7$^1D$fcEJ$8Tg6N7E8F(kV35{FK6&+MK2h_bnp-vRT_5DzrTHn%cWb^* z^Cg-+&F5(D*1SOTp_=#C{GlDxzgzR`nzw7-s`*yUS7{#6oY35%`_-fQJk2@HmubFE z^SI{CnxE4AN6mXRe@@qbu;z&7HqEDM?$KO1-v2A_PtyHapn1OLFKT|DwIr`ynqSoX zu;$I0H)*~~^F^9n&1*HU(%hkWf#xGM&(XZU=4Qc*}riS=^y&j1Gvk;lxGJEdKVEv`&AZC2gfKSa7er zA!>P^!(FQ_5{tV9ezzm$Bm#SgHNriDCNX2IPrK@YhG@T)w4GAgEiI{xla#dOExw~q zz1f1H)``b*9CBsv+V<7Qx3&2`UA1o$Mi6Nre!85xyfG>kjhyAB)fFWjwbXlZeDRy$ zLd#>TyR{)2OJ|d~QsfUqez_qUZI6Z~pU_;xod$`!?z*^}>#|c+tGbAAB&0l-^q?Slj+$&LW8PSG;Ow@^`vAEDL%M-t)I=fkXAL71SPHs%b8OJ~P`;AdQdP`GO zN0QCGR<84WG5O(fZWmk1a^Gpl-O{u0qjqJyUfW5gtTX!8Tk)K_M78@~LchpTrCw`Z zlgd)XTHm+ws=QkkzME|BO<9(!RyGpq32bUh2L@!+3M*sTA%0VCAf?@RO@-gN z>b24_cQE6z$`2-%&8Mxf)X?uoQm%F6blv-MhZEx<)v(lV| zu6mzvPFHI4P%x|X{nVyzjJ|qoINteETW zl7$|=f3bNDr-SV5Gqh#t1+E^Fe$1HM*t8=QqMXSVGq+F z`7BZ~qVis9_TaPpqb7Ja?8mz)wM=#U^dZB%dGyfI?*?e&FlA(}BE+SRG5Qtj-Ni|CcJp7OGCf}_dUN%?+`nWH|L>j>i%?4{!w zh&sI3HyI7d&({e5XzUkZf8BBX%NqR(dvmN`TawiB@yoJXZ`dF0g&gNN>goUuoM|!& z;pmsn2A=KsXVMx{Wo)y|ag0`!*2aF#6LPB6U-c3AY>vRR_^)^_b?4MiUzg{#Z4K<1 Pg~k_;L-p5Zd<6apgZj`N literal 0 HcmV?d00001 diff --git a/Src/3rd_party/pthread/bin/x86/pthreadGC2.dll b/Src/3rd_party/pthread/bin/x86/pthreadGC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..67b9289dfd9f6f1ad12c1d137f4e57ad1a54271c GIT binary patch literal 119888 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

TQ1QhAAy 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%0A#@4d!p*|%!fnEx!Xn{KVR#z&S@1N3WRZj< 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