diff options
Diffstat (limited to 'src/map/pc.cpp')
-rw-r--r-- | src/map/pc.cpp | 9048 |
1 files changed, 9048 insertions, 0 deletions
diff --git a/src/map/pc.cpp b/src/map/pc.cpp new file mode 100644 index 0000000..256244a --- /dev/null +++ b/src/map/pc.cpp @@ -0,0 +1,9048 @@ +// $Id: pc.c 101 2004-09-25 17:57:22Z Valaris $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#include "../common/socket.hpp" // [Valaris] +#include "../common/timer.hpp" +#include "../common/db.hpp" + +#include "../common/nullpo.hpp" +#include "../common/mt_rand.hpp" + +#include "atcommand.hpp" +#include "battle.hpp" +#include "chat.hpp" +#include "chrif.hpp" +#include "clif.hpp" +#include "guild.hpp" +#include "intif.hpp" +#include "itemdb.hpp" +#include "map.hpp" +#include "mob.hpp" +#include "npc.hpp" +#include "party.hpp" +#include "pc.hpp" +#include "script.hpp" +#include "skill.hpp" +#include "storage.hpp" +#include "trade.hpp" + +#ifdef MEMWATCH +#include "memwatch.hpp" +#endif + +#define PVP_CALCRANK_INTERVAL 1000 // PVP順位計算の間隔 + +//define it here, since the ifdef only occurs in this file +#define USE_ASTRAL_SOUL_SKILL + +#define STATE_BLIND 0x10 + +#ifdef USE_ASTRAL_SOUL_SKILL +#define MAGIC_SKILL_THRESHOLD 200 // [fate] At this threshold, the Astral Soul skill kicks in +#endif + +#define MAP_LOG_STATS(sd, suffix) \ + MAP_LOG_PC(sd, "STAT %d %d %d %d %d %d " suffix, \ + sd->status.str, sd->status.agi, sd->status.vit, sd->status.int_, sd->status.dex, sd->status.luk) + +#define MAP_LOG_XP(sd, suffix) \ + MAP_LOG_PC(sd, "XP %d %d JOB %d %d %d ZENY %d + %d " suffix, \ + sd->status.base_level, sd->status.base_exp, sd->status.job_level, sd->status.job_exp, sd->status.skill_point, sd->status.zeny, pc_readaccountreg(sd, "BankAccount")) + +#define MAP_LOG_MAGIC(sd, suffix) \ + MAP_LOG_PC(sd, "MAGIC %d %d %d %d %d %d EXP %d %d " suffix, \ + sd->status.skill[TMW_MAGIC].lv, \ + sd->status.skill[TMW_MAGIC_LIFE].lv, \ + sd->status.skill[TMW_MAGIC_WAR].lv, \ + sd->status.skill[TMW_MAGIC_TRANSMUTE].lv, \ + sd->status.skill[TMW_MAGIC_NATURE].lv, \ + sd->status.skill[TMW_MAGIC_ETHER].lv, \ + pc_readglobalreg(sd, "MAGIC_EXPERIENCE") & 0xffff, \ + (pc_readglobalreg(sd, "MAGIC_EXPERIENCE") >> 24) & 0xff) + +timer_id day_timer_tid; +timer_id night_timer_tid; + +static int max_weight_base[MAX_PC_CLASS]; +static int hp_coefficient[MAX_PC_CLASS]; +static int hp_coefficient2[MAX_PC_CLASS]; +static int hp_sigma_val[MAX_PC_CLASS][MAX_LEVEL]; +static int sp_coefficient[MAX_PC_CLASS]; +static int aspd_base[MAX_PC_CLASS][20]; +static char job_bonus[3][MAX_PC_CLASS][MAX_LEVEL]; +static int exp_table[14][MAX_LEVEL]; +static char statp[255][7]; +static struct +{ + int id; + int max; + struct + { + short id, lv; + } need[6]; +} skill_tree[3][MAX_PC_CLASS][100]; + +static int atkmods[3][20]; // 武器ATKサイズ修正(size_fix.txt) +static int refinebonus[5][3]; // 精錬ボーナステーブル(refine_db.txt) +static int percentrefinery[5][10]; // 精錬成功率(refine_db.txt) + +static int dirx[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; +static int diry[8] = { 1, 1, 0, -1, -1, -1, 0, 1 }; + +static unsigned int equip_pos[11] = + { 0x0080, 0x0008, 0x0040, 0x0004, 0x0001, 0x0200, 0x0100, 0x0010, 0x0020, + 0x0002, 0x8000 +}; + +//static struct dbt *gm_account_db; +static struct gm_account *gm_account = NULL; +static int GM_num = 0; + +int pc_isGM (struct map_session_data *sd) +{ +// struct gm_account *p; + int i; + + nullpo_retr (0, sd); + +/* p = numdb_search(gm_account_db, sd->status.account_id); + if (p == NULL) + return 0; + return p->level;*/ + + for (i = 0; i < GM_num; i++) + if (gm_account[i].account_id == sd->status.account_id) + return gm_account[i].level; + return 0; + +} + +int pc_iskiller (struct map_session_data *src, + struct map_session_data *target) +{ + nullpo_retr (0, src); + + if (src->bl.type != BL_PC) + return 0; + if (src->special_state.killer) + return 1; + + if (target->bl.type != BL_PC) + return 0; + if (target->special_state.killable) + return 1; + + return 0; +} + +int pc_set_gm_level (int account_id, int level) +{ + int i; + for (i = 0; i < GM_num; i++) + { + if (account_id == gm_account[i].account_id) + { + gm_account[i].level = level; + return 0; + } + } + + GM_num++; + RECREATE (gm_account, struct gm_account, GM_num); + gm_account[GM_num - 1].account_id = account_id; + gm_account[GM_num - 1].level = level; + return 0; +} + +int pc_getrefinebonus (int lv, int type) +{ + if (lv >= 0 && lv < 5 && type >= 0 && type < 3) + return refinebonus[lv][type]; + return 0; +} + +static int distance (int x0, int y0, int x1, int y1) +{ + int dx, dy; + + dx = abs (x0 - x1); + dy = abs (y0 - y1); + return dx > dy ? dx : dy; +} + +static void pc_invincible_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd; + + if ((sd = (struct map_session_data *) map_id2sd (id)) == NULL + || sd->bl.type != BL_PC) + return; + + if (sd->invincible_timer != tid) + { + if (battle_config.error_log) + printf ("invincible_timer %d != %d\n", sd->invincible_timer, tid); + return; + } + sd->invincible_timer = -1; +} + +int pc_setinvincibletimer (struct map_session_data *sd, int val) +{ + nullpo_retr (0, sd); + + if (sd->invincible_timer != -1) + delete_timer (sd->invincible_timer, pc_invincible_timer); + sd->invincible_timer = + add_timer (gettick () + val, pc_invincible_timer, sd->bl.id, 0); + return 0; +} + +int pc_delinvincibletimer (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->invincible_timer != -1) + { + delete_timer (sd->invincible_timer, pc_invincible_timer); + sd->invincible_timer = -1; + } + return 0; +} + +static void pc_spiritball_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd; + int i; + + if ((sd = (struct map_session_data *) map_id2sd (id)) == NULL + || sd->bl.type != BL_PC) + return; + + if (sd->spirit_timer[0] != tid) + { + if (battle_config.error_log) + printf ("spirit_timer %d != %d\n", sd->spirit_timer[0], tid); + return; + } + sd->spirit_timer[0] = -1; + for (i = 1; i < sd->spiritball; i++) + { + sd->spirit_timer[i - 1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + sd->spiritball--; + if (sd->spiritball < 0) + sd->spiritball = 0; + clif_spiritball (sd); +} + +int pc_addspiritball (struct map_session_data *sd, int interval, int max) +{ + int i; + + nullpo_retr (0, sd); + + if (max > MAX_SKILL_LEVEL) + max = MAX_SKILL_LEVEL; + if (sd->spiritball < 0) + sd->spiritball = 0; + + if (sd->spiritball >= max) + { + if (sd->spirit_timer[0] != -1) + { + delete_timer (sd->spirit_timer[0], pc_spiritball_timer); + sd->spirit_timer[0] = -1; + } + for (i = 1; i < max; i++) + { + sd->spirit_timer[i - 1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + } + else + sd->spiritball++; + + sd->spirit_timer[sd->spiritball - 1] = + add_timer (gettick () + interval, pc_spiritball_timer, sd->bl.id, 0); + clif_spiritball (sd); + + return 0; +} + +int pc_delspiritball (struct map_session_data *sd, int count, int type) +{ + int i; + + nullpo_retr (0, sd); + + if (sd->spiritball <= 0) + { + sd->spiritball = 0; + return 0; + } + + if (count > sd->spiritball) + count = sd->spiritball; + sd->spiritball -= count; + if (count > MAX_SKILL_LEVEL) + count = MAX_SKILL_LEVEL; + + for (i = 0; i < count; i++) + { + if (sd->spirit_timer[i] != -1) + { + delete_timer (sd->spirit_timer[i], pc_spiritball_timer); + sd->spirit_timer[i] = -1; + } + } + for (i = count; i < MAX_SKILL_LEVEL; i++) + { + sd->spirit_timer[i - count] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + + if (!type) + clif_spiritball (sd); + + return 0; +} + +int pc_setrestartvalue (struct map_session_data *sd, int type) +{ + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + s_class = pc_calc_base_job (sd->status.pc_class); + + //----------------------- + // 死亡した + if (sd->special_state.restart_full_recover) + { // オシリスカード + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + } + else + { + if (s_class.job == 0 && battle_config.restart_hp_rate < 50) + { //ノビは半分回復 + sd->status.hp = (sd->status.max_hp) / 2; + } + else + { + if (battle_config.restart_hp_rate <= 0) + sd->status.hp = 1; + else + { + sd->status.hp = + sd->status.max_hp * battle_config.restart_hp_rate / 100; + if (sd->status.hp <= 0) + sd->status.hp = 1; + } + } + if (battle_config.restart_sp_rate > 0) + { + int sp = sd->status.max_sp * battle_config.restart_sp_rate / 100; + if (sd->status.sp < sp) + sd->status.sp = sp; + } + } + if (type & 1) + clif_updatestatus (sd, SP_HP); + if (type & 1) + clif_updatestatus (sd, SP_SP); + + /* removed exp penalty on spawn [Valaris] */ + + if (type & 2 && sd->status.pc_class != 0 && battle_config.zeny_penalty > 0 + && !map[sd->bl.m].flag.nozenypenalty) + { + int zeny = + (int) ((double) sd->status.zeny * + (double) battle_config.zeny_penalty / 10000.); + if (zeny < 1) + zeny = 1; + sd->status.zeny -= zeny; + if (sd->status.zeny < 0) + sd->status.zeny = 0; + clif_updatestatus (sd, SP_ZENY); + } + sd->heal_xp = 0; // [Fate] Set gainable xp for healing this player to 0 + + return 0; +} + +/*========================================== + * 自分をロックしているMOBの数を数える(foreachclient) + *------------------------------------------ + */ +static int pc_counttargeted_sub (struct block_list *bl, va_list ap) +{ + int id, *c, target_lv; + struct block_list *src; + + nullpo_retr (0, bl); + nullpo_retr (0, ap); + + id = va_arg (ap, int); + + nullpo_retr (0, c = va_arg (ap, int *)); + + src = va_arg (ap, struct block_list *); + target_lv = va_arg (ap, int); + if (id == bl->id || (src && id == src->id)) + return 0; + if (bl->type == BL_PC) + { + struct map_session_data *sd = (struct map_session_data *) bl; + if (sd && sd->attacktarget == id && sd->attacktimer != -1 + && sd->attacktarget_lv >= target_lv) + (*c)++; + } + else if (bl->type == BL_MOB) + { + struct mob_data *md = (struct mob_data *) bl; + if (md && md->target_id == id && md->timer != -1 + && md->state.state == MS_ATTACK && md->target_lv >= target_lv) + + (*c)++; + //printf("md->target_lv:%d, target_lv:%d\n",((struct mob_data *)bl)->target_lv,target_lv); + } + return 0; +} + +int pc_counttargeted (struct map_session_data *sd, struct block_list *src, + int target_lv) +{ + int c = 0; + map_foreachinarea (pc_counttargeted_sub, sd->bl.m, + sd->bl.x - AREA_SIZE, sd->bl.y - AREA_SIZE, + sd->bl.x + AREA_SIZE, sd->bl.y + AREA_SIZE, 0, + sd->bl.id, &c, src, target_lv); + return c; +} + +/*========================================== + * ローカルプロトタイプ宣言 (必要な物のみ) + *------------------------------------------ + */ +static int pc_walktoxy_sub (struct map_session_data *); + +/*========================================== + * saveに必要なステータス修正を行なう + *------------------------------------------ + */ +int pc_makesavestatus (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + // 服の色は色々弊害が多いので保存対象にはしない + if (!battle_config.save_clothcolor) + sd->status.clothes_color = 0; + + // 死亡状態だったのでhpを1、位置をセーブ場所に変更 + if (pc_isdead (sd)) + { + pc_setrestartvalue (sd, 0); + memcpy (&sd->status.last_point, &sd->status.save_point, + sizeof (sd->status.last_point)); + } + else + { + memcpy (sd->status.last_point.map, sd->mapname, 24); + sd->status.last_point.x = sd->bl.x; + sd->status.last_point.y = sd->bl.y; + } + + // セーブ禁止マップだったので指定位置に移動 + if (map[sd->bl.m].flag.nosave) + { + struct map_data *m = &map[sd->bl.m]; + if (strcmp (m->save.map, "SavePoint") == 0) + memcpy (&sd->status.last_point, &sd->status.save_point, + sizeof (sd->status.last_point)); + else + memcpy (&sd->status.last_point, &m->save, + sizeof (sd->status.last_point)); + } + + //マナーポイントがプラスだった場合0に + if (battle_config.muting_players && sd->status.manner > 0) + sd->status.manner = 0; + return 0; +} + +/*========================================== + * 接続時の初期化 + *------------------------------------------ + */ +int pc_setnewpc (struct map_session_data *sd, int account_id, int char_id, + int login_id1, int client_tick, int sex, int fd) +{ + nullpo_retr (0, sd); + + sd->bl.id = account_id; + sd->char_id = char_id; + sd->login_id1 = login_id1; + sd->login_id2 = 0; // at this point, we can not know the value :( + sd->client_tick = client_tick; + sd->sex = sex; + sd->state.auth = 0; + sd->bl.type = BL_PC; + sd->canact_tick = sd->canmove_tick = gettick (); + sd->canlog_tick = gettick (); + sd->state.waitingdisconnect = 0; + + return 0; +} + +int pc_equippoint (struct map_session_data *sd, int n) +{ + int ep = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + if (!sd->inventory_data[n]) + return 0; + + s_class = pc_calc_base_job (sd->status.pc_class); + + ep = sd->inventory_data[n]->equip; + if ((sd->inventory_data[n]->look == 1 || sd->inventory_data[n]->look == 2 + || sd->inventory_data[n]->look == 6) && (ep == 2 + && + (pc_checkskill (sd, AS_LEFT) + > 0 || s_class.job == 12))) + { + return 34; + } + + return ep; +} + +static +int pc_setinventorydata (struct map_session_data *sd) +{ + int i, id; + + nullpo_retr (0, sd); + + for (i = 0; i < MAX_INVENTORY; i++) + { + id = sd->status.inventory[i].nameid; + sd->inventory_data[i] = itemdb_search (id); + } + return 0; +} + +static +int pc_calcweapontype (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->weapontype1 != 0 && sd->weapontype2 == 0) + sd->status.weapon = sd->weapontype1; + if (sd->weapontype1 == 0 && sd->weapontype2 != 0) // 左手武器 Only + sd->status.weapon = sd->weapontype2; + else if (sd->weapontype1 == 1 && sd->weapontype2 == 1) // 双短剣 + sd->status.weapon = 0x11; + else if (sd->weapontype1 == 2 && sd->weapontype2 == 2) // 双単手剣 + sd->status.weapon = 0x12; + else if (sd->weapontype1 == 6 && sd->weapontype2 == 6) // 双単手斧 + sd->status.weapon = 0x13; + else if ((sd->weapontype1 == 1 && sd->weapontype2 == 2) || (sd->weapontype1 == 2 && sd->weapontype2 == 1)) // 短剣 - 単手剣 + sd->status.weapon = 0x14; + else if ((sd->weapontype1 == 1 && sd->weapontype2 == 6) || (sd->weapontype1 == 6 && sd->weapontype2 == 1)) // 短剣 - 斧 + sd->status.weapon = 0x15; + else if ((sd->weapontype1 == 2 && sd->weapontype2 == 6) || (sd->weapontype1 == 6 && sd->weapontype2 == 2)) // 単手剣 - 斧 + sd->status.weapon = 0x16; + else + sd->status.weapon = sd->weapontype1; + + return 0; +} + +static +int pc_setequipindex (struct map_session_data *sd) +{ + int i, j; + + nullpo_retr (0, sd); + + for (i = 0; i < 11; i++) + sd->equip_index[i] = -1; + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid <= 0) + continue; + if (sd->status.inventory[i].equip) + { + for (j = 0; j < 11; j++) + if (sd->status.inventory[i].equip & equip_pos[j]) + sd->equip_index[j] = i; + if (sd->status.inventory[i].equip & 0x0002) + { + if (sd->inventory_data[i]) + sd->weapontype1 = sd->inventory_data[i]->look; + else + sd->weapontype1 = 0; + } + if (sd->status.inventory[i].equip & 0x0020) + { + if (sd->inventory_data[i]) + { + if (sd->inventory_data[i]->type == 4) + { + if (sd->status.inventory[i].equip == 0x0020) + sd->weapontype2 = sd->inventory_data[i]->look; + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + } + } + pc_calcweapontype (sd); + + return 0; +} + +int pc_isequip (struct map_session_data *sd, int n) +{ + struct item_data *item; + struct status_change *sc_data; + //転生や養子の場合の元の職業を算出する + + nullpo_retr (0, sd); + + item = sd->inventory_data[n]; + sc_data = battle_get_sc_data (&sd->bl); + //s_class = pc_calc_base_job(sd->status.class); + + if (battle_config.gm_allequip > 0 + && pc_isGM (sd) >= battle_config.gm_allequip) + return 1; + + if (item == NULL) + return 0; + if (item->sex != 2 && sd->status.sex != item->sex) + return 0; + if (item->elv > 0 && sd->status.base_level < item->elv) + return 0; + + if (map[sd->bl.m].flag.pvp + && (item->flag.no_equip == 1 || item->flag.no_equip == 3)) + return 0; + if (map[sd->bl.m].flag.gvg + && (item->flag.no_equip == 2 || item->flag.no_equip == 3)) + return 0; + if (item->equip & 0x0002 && sc_data + && sc_data[SC_STRIPWEAPON].timer != -1) + return 0; + if (item->equip & 0x0020 && sc_data + && sc_data[SC_STRIPSHIELD].timer != -1) + return 0; + if (item->equip & 0x0010 && sc_data && sc_data[SC_STRIPARMOR].timer != -1) + return 0; + if (item->equip & 0x0100 && sc_data && sc_data[SC_STRIPHELM].timer != -1) + return 0; + return 1; +} + +/*========================================== + * Weapon Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakweapon (struct map_session_data *sd) +{ + struct item_data *item; + char output[255]; + int i; + + if (sd == NULL) + return -1; + if (sd->unbreakable >= MRAND (100)) + return 0; + if (sd->sc_data && sd->sc_data[SC_CP_WEAPON].timer != -1) + return 0; + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].equip + && sd->status.inventory[i].equip & 0x0002 + && !sd->status.inventory[i].broken) + { + item = sd->inventory_data[i]; + sd->status.inventory[i].broken = 1; + //pc_unequipitem(sd,i,0); + if (sd->status.inventory[i].equip + && sd->status.inventory[i].equip & 0x0002 + && sd->status.inventory[i].broken == 1) + { + sprintf (output, "%s has broken.", item->jname); + clif_emotion (&sd->bl, 23); + clif_displaymessage (sd->fd, output); + clif_equiplist (sd); + skill_status_change_start (&sd->bl, SC_BROKNWEAPON, 0, 0, 0, + 0, 0, 0); + } + } + if (sd->status.inventory[i].broken == 1) + return 0; + } + + return 0; +} + +/*========================================== + * Armor Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakarmor (struct map_session_data *sd) +{ + struct item_data *item; + char output[255]; + int i; + + if (sd == NULL) + return -1; + if (sd->unbreakable >= MRAND (100)) + return 0; + if (sd->sc_data && sd->sc_data[SC_CP_ARMOR].timer != -1) + return 0; + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].equip + && sd->status.inventory[i].equip & 0x0010 + && !sd->status.inventory[i].broken) + { + item = sd->inventory_data[i]; + sd->status.inventory[i].broken = 1; + //pc_unequipitem(sd,i,0); + if (sd->status.inventory[i].equip + && sd->status.inventory[i].equip & 0x0010 + && sd->status.inventory[i].broken == 1) + { + sprintf (output, "%s has broken.", item->jname); + clif_emotion (&sd->bl, 23); + clif_displaymessage (sd->fd, output); + clif_equiplist (sd); + skill_status_change_start (&sd->bl, SC_BROKNARMOR, 0, 0, 0, 0, + 0, 0); + } + } + if (sd->status.inventory[i].broken == 1) + return 0; + } + return 0; +} + +/*========================================== + * session idに問題無し + * char鯖から送られてきたステータスを設定 + *------------------------------------------ + */ +int pc_authok (int id, int login_id2, time_t connect_until_time, + short tmw_version, struct mmo_charstatus *st) +{ + struct map_session_data *sd = NULL; + + struct party *p; + struct guild *g; + unsigned long tick = gettick (); + struct sockaddr_in sai; + socklen_t sa_len = sizeof(struct sockaddr); + + sd = map_id2sd (id); + if (sd == NULL) + return 1; + + sd->login_id2 = login_id2; + sd->tmw_version = tmw_version; + + memcpy (&sd->status, st, sizeof (*st)); + + if (sd->status.sex != sd->sex) + { + clif_authfail_fd (sd->fd, 0); + return 1; + } + + MAP_LOG_STATS (sd, "LOGIN"); + MAP_LOG_XP (sd, "LOGIN"); + MAP_LOG_MAGIC (sd, "LOGIN"); + + memset (&sd->state, 0, sizeof (sd->state)); + // 基本的な初期化 + sd->state.connect_new = 1; + sd->bl.prev = sd->bl.next = NULL; + + sd->weapontype1 = sd->weapontype2 = 0; + sd->view_class = sd->status.pc_class; + sd->speed = DEFAULT_WALK_SPEED; + sd->state.dead_sit = 0; + sd->dir = 0; + sd->head_dir = 0; + sd->state.auth = 1; + sd->walktimer = -1; + sd->attacktimer = -1; + sd->followtimer = -1; // [MouseJstr] + sd->skilltimer = -1; + sd->skillitem = -1; + sd->skillitemlv = -1; + sd->invincible_timer = -1; + sd->sg_count = 0; + + sd->deal_locked = 0; + sd->trade_partner = 0; + + sd->inchealhptick = 0; + sd->inchealsptick = 0; + sd->hp_sub = 0; + sd->sp_sub = 0; + sd->quick_regeneration_hp.amount = 0; + sd->quick_regeneration_sp.amount = 0; + sd->heal_xp = 0; + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->attackabletime = tick; + /* We don't want players bypassing spell restrictions. [remoitnane] */ + // Removed because it was buggy with the ~50 day wraparound, + // and there's already a limit on how fast you can log in and log out. + // -o11c + sd->cast_tick = tick; // + pc_readglobalreg (sd, "MAGIC_CAST_TICK"); + + sd->doridori_counter = 0; + + sd->spiritball = 0; + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sd->spirit_timer[i] = -1; + for (int i = 0; i < MAX_SKILLTIMERSKILL; i++) + sd->skilltimerskill[i].timer = -1; + + memset (&sd->dev, 0, sizeof (struct square)); + for (int i = 0; i < 5; i++) + { + sd->dev.val1[i] = 0; + sd->dev.val2[i] = 0; + } + + // アカウント変数の送信要求 + intif_request_accountreg (sd); + + // アイテムチェック + pc_setinventorydata (sd); + pc_checkitem (sd); + + // ステータス異常の初期化 + for (int i = 0; i < MAX_STATUSCHANGE; i++) + { + sd->sc_data[i].timer = -1; + sd->sc_data[i].val1 = sd->sc_data[i].val2 = sd->sc_data[i].val3 = + sd->sc_data[i].val4 = 0; + } + sd->sc_count = 0; + if ((battle_config.atc_gmonly == 0 || pc_isGM (sd)) && + (pc_isGM (sd) >= get_atcommand_level (AtCommand_Hide))) + sd->status.option &= (OPTION_MASK | OPTION_HIDE); + else + sd->status.option &= OPTION_MASK; + + // スキルユニット関係の初期化 + memset (sd->skillunit, 0, sizeof (sd->skillunit)); + memset (sd->skillunittick, 0, sizeof (sd->skillunittick)); + + // init ignore list + memset (sd->ignore, 0, sizeof (sd->ignore)); + + // パーティー関係の初期化 + sd->party_sended = 0; + sd->party_invite = 0; + sd->party_x = -1; + sd->party_y = -1; + sd->party_hp = -1; + + // ギルド関係の初期化 + sd->guild_sended = 0; + sd->guild_invite = 0; + sd->guild_alliance = 0; + + // イベント関係の初期化 + memset (sd->eventqueue, 0, sizeof (sd->eventqueue)); + for (int i = 0; i < MAX_EVENTTIMER; i++) + sd->eventtimer[i] = -1; + + // 位置の設定 + pc_setpos (sd, sd->status.last_point.map, sd->status.last_point.x, + sd->status.last_point.y, 0); + + // パーティ、ギルドデータの要求 + if (sd->status.party_id > 0 + && (p = party_search (sd->status.party_id)) == NULL) + party_request_info (sd->status.party_id); + if (sd->status.guild_id > 0 + && (g = guild_search (sd->status.guild_id)) == NULL) + guild_request_info (sd->status.guild_id); + + // pvpの設定 + sd->pvp_rank = 0; + sd->pvp_point = 0; + sd->pvp_timer = -1; + + // 通知 + + clif_authok (sd); + map_addnickdb (sd); + if (map_charid2nick (sd->status.char_id) == NULL) + map_addchariddb (sd->status.char_id, sd->status.name); + + //スパノビ用死にカウンターのスクリプト変数からの読み出しとsdへのセット + sd->die_counter = pc_readglobalreg (sd, "PC_DIE_COUNTER"); + + if (night_flag == 1) + { + char tmpstr[1024]; + strcpy (tmpstr, "Actually, it's the night..."); + clif_wis_message (sd->fd, wisp_server_name, tmpstr, + strlen (tmpstr) + 1); + sd->opt2 |= STATE_BLIND; + } + + // ステータス初期計算など + pc_calcstatus (sd, 1); + + if (pc_isGM (sd)) + { + printf + ("Connection accepted: character '%s' (account: %d; GM level %d).\n", + sd->status.name, sd->status.account_id, pc_isGM (sd)); + clif_updatestatus (sd, SP_GM); + } + else + printf ("Connection accepted: Character '%s' (account: %d).\n", + sd->status.name, sd->status.account_id); + + // Message of the Dayの送信 + { + char buf[256]; + FILE *fp; + if ((fp = fopen_ (motd_txt, "r")) != NULL) + { + while (fgets (buf, sizeof (buf) - 1, fp) != NULL) + { + for (int i = 0; buf[i]; i++) + { + if (buf[i] == '\r' || buf[i] == '\n') + { + buf[i] = 0; + break; + } + } + clif_displaymessage (sd->fd, buf); + } + fclose_ (fp); + } + } + + sd->auto_ban_info.in_progress = 0; + + // Initialize antispam vars + sd->chat_reset_due = sd->chat_lines_in = sd->chat_total_repeats = + sd->chat_repeat_reset_due = 0; + sd->chat_lastmsg[0] = '\0'; + + memset(sd->flood_rates, 0, sizeof(sd->flood_rates)); + sd->packet_flood_reset_due = sd->packet_flood_in = 0; + + // Obtain IP address (if they are still connected) + if (!getpeername(sd->fd, (struct sockaddr *)&sai, &sa_len)) + sd->ip = sai.sin_addr; + + // message of the limited time of the account + if (connect_until_time != 0) + { // don't display if it's unlimited or unknow value + char tmpstr[1024]; + strftime (tmpstr, sizeof (tmpstr) - 1, "Your account time limit is: %d-%m-%Y %H:%M:%S.", gmtime (&connect_until_time)); + clif_wis_message (sd->fd, wisp_server_name, tmpstr, + strlen (tmpstr) + 1); + } + pc_calcstatus (sd, 1); + + return 0; +} + +/*========================================== + * session idに問題ありなので後始末 + *------------------------------------------ + */ +int pc_authfail (int id) +{ + struct map_session_data *sd; + + sd = map_id2sd (id); + if (sd == NULL) + return 1; + + clif_authfail_fd (sd->fd, 0); + + return 0; +} + +static int pc_calc_skillpoint (struct map_session_data *sd) +{ + int i, skill_points = 0; + + nullpo_retr (0, sd); + + for (i = 0; i < skill_pool_skills_size; i++) { + int lv = sd->status.skill[skill_pool_skills[i]].lv; + if (lv) + skill_points += ((lv * (lv - 1)) >> 1) - 1; + } + + return skill_points; +} + +/*========================================== + * 覚えられるスキルの計算 + *------------------------------------------ + */ +static +int pc_calc_skilltree (struct map_session_data *sd) +{ + int i, id = 0, flag; + int c = 0, s = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + s_class = pc_calc_base_job (sd->status.pc_class); + c = s_class.job; + s = (s_class.upper == 1) ? 1 : 0; //ソ転生以外は通常のスキル? + + if ((battle_config.skillup_limit) + && ((c >= 0 && c < 23) || (c >= 4001 && c < 4023) + || (c >= 4023 && c < 4045))) + { + int skill_point = pc_calc_skillpoint (sd); + if (skill_point < 9) + c = 0; + else if ((sd->status.skill_point >= sd->status.job_level + && skill_point < 58) && ((c > 6 && c < 23) || (c > 4007 + && c < 4023) + || (c > 4029 && c < 4045))) + { + switch (c) + { + case 7: + case 14: + c = 1; + break; + case 8: + case 15: + c = 4; + break; + case 9: + case 16: + c = 2; + break; + case 10: + case 18: + c = 5; + break; + case 11: + case 19: + case 20: + c = 3; + break; + case 12: + case 17: + c = 6; + break; + case 4008: + case 4015: + c = 4002; + break; + case 4009: + case 4016: + c = 4005; + break; + case 4010: + case 4017: + c = 4003; + break; + case 4011: + case 4019: + c = 4006; + break; + case 4012: + case 4020: + case 4021: + c = 4004; + break; + case 4013: + case 4018: + c = 4007; + break; + case 4030: + case 4037: + c = 4024; + break; + case 4031: + case 4038: + c = 4027; + break; + case 4032: + case 4039: + c = 4025; + break; + case 4033: + case 4040: + c = 4028; + break; + case 4034: + case 4041: + case 4042: + c = 4026; + break; + case 4035: + case 4043: + c = 4029; + break; + + } + } + } + + /*Comment this out for now, as we manage skills differently + * for(i=0;i<MAX_SKILL;i++) + * if (i < TMW_MAGIC || i > TMW_MAGIC_END){ // [Fate] This hack gets TMW magic working and persisted without bothering about the skill tree. + * if (sd->status.skill[i].flag != 13) sd->status.skill[i].id=0; + * if (sd->status.skill[i].flag && sd->status.skill[i].flag != 13){ // cardスキルなら、 + * sd->status.skill[i].lv=(sd->status.skill[i].flag==1)?0:sd->status.skill[i].flag-2; // 本当のlvに + * sd->status.skill[i].flag=0; // flagは0にしておく + * } + * } + */ + + if (battle_config.gm_allskill > 0 + && pc_isGM (sd) >= battle_config.gm_allskill) + { + // 全てのスキル + for (i = 1; i < 158; i++) + sd->status.skill[i].id = i; + for (i = 210; i < 291; i++) + sd->status.skill[i].id = i; + for (i = 304; i < 337; i++) + sd->status.skill[i].id = i; + if (battle_config.enable_upper_class) + { //confで無効でなければ読み込む + for (i = 355; i < MAX_SKILL; i++) + sd->status.skill[i].id = i; + } + + } + else + { + // 通常の計算 + do + { + flag = 0; + for (i = 0; (id = skill_tree[s][c][i].id) > 0; i++) + { + int j, f = 1; + if (!battle_config.skillfree) + { + for (j = 0; j < 5; j++) + { + if (skill_tree[s][c][i].need[j].id && + pc_checkskill (sd, + skill_tree[s][c][i].need[j].id) < + skill_tree[s][c][i].need[j].lv) + f = 0; + } + } + if (f && sd->status.skill[id].id == 0) + { + sd->status.skill[id].id = id; + flag = 1; + } + } + } + while (flag); + } +// if(battle_config.etc_log) +// printf("calc skill_tree\n"); + return 0; +} + +/*========================================== + * 重量アイコンの確認 + *------------------------------------------ + */ +int pc_checkweighticon (struct map_session_data *sd) +{ + int flag = 0; + + nullpo_retr (0, sd); + + if (sd->weight * 2 >= sd->max_weight + && sd->sc_data[SC_FLYING_BACKPACK].timer == -1) + flag = 1; + if (sd->weight * 10 >= sd->max_weight * 9) + flag = 2; + + if (flag == 1) + { + if (sd->sc_data[SC_WEIGHT50].timer == -1) + skill_status_change_start (&sd->bl, SC_WEIGHT50, 0, 0, 0, 0, 0, + 0); + } + else + { + skill_status_change_end (&sd->bl, SC_WEIGHT50, -1); + } + if (flag == 2) + { + if (sd->sc_data[SC_WEIGHT90].timer == -1) + skill_status_change_start (&sd->bl, SC_WEIGHT90, 0, 0, 0, 0, 0, + 0); + } + else + { + skill_status_change_end (&sd->bl, SC_WEIGHT90, -1); + } + return 0; +} + +static +void pc_set_weapon_look (struct map_session_data *sd) +{ + if (sd->attack_spell_override) + clif_changelook (&sd->bl, LOOK_WEAPON, + sd->attack_spell_look_override); + else + clif_changelook (&sd->bl, LOOK_WEAPON, sd->status.weapon); +} + +/*========================================== + * パラメータ計算 + * first==0の時、計算対象のパラメータが呼び出し前から + * 変 化した場合自動でsendするが、 + * 能動的に変化させたパラメータは自前でsendするように + *------------------------------------------ + */ +int pc_calcstatus (struct map_session_data *sd, int first) +{ + int b_speed, b_max_hp, b_max_sp, b_hp, b_sp, b_weight, b_max_weight, + b_paramb[6], b_parame[6], b_hit, b_flee; + int b_aspd, b_watk, b_def, b_watk2, b_def2, b_flee2, b_critical, + b_attackrange, b_matk1, b_matk2, b_mdef, b_mdef2, b_class; + int b_base_atk; + struct skill b_skill[MAX_SKILL]; + int i, bl, index; + int skill, aspd_rate, wele, wele_, def_ele, refinedef = 0; + int str, dstr, dex; + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job (sd->status.pc_class); + + b_speed = sd->speed; + b_max_hp = sd->status.max_hp; + b_max_sp = sd->status.max_sp; + b_hp = sd->status.hp; + b_sp = sd->status.sp; + b_weight = sd->weight; + b_max_weight = sd->max_weight; + memcpy (b_paramb, &sd->paramb, sizeof (b_paramb)); + memcpy (b_parame, &sd->paramc, sizeof (b_parame)); + memcpy (b_skill, &sd->status.skill, sizeof (b_skill)); + b_hit = sd->hit; + b_flee = sd->flee; + b_aspd = sd->aspd; + b_watk = sd->watk; + b_def = sd->def; + b_watk2 = sd->watk2; + b_def2 = sd->def2; + b_flee2 = sd->flee2; + b_critical = sd->critical; + b_attackrange = sd->attackrange; + b_matk1 = sd->matk1; + b_matk2 = sd->matk2; + b_mdef = sd->mdef; + b_mdef2 = sd->mdef2; + b_class = sd->view_class; + sd->view_class = sd->status.pc_class; + b_base_atk = sd->base_atk; + + pc_calc_skilltree (sd); // スキルツリーの計算 + + sd->max_weight = max_weight_base[s_class.job] + sd->status.str * 300; + + if (first & 1) + { + sd->weight = 0; + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid == 0 + || sd->inventory_data[i] == NULL) + continue; + sd->weight += + sd->inventory_data[i]->weight * + sd->status.inventory[i].amount; + } + sd->cart_max_weight = battle_config.max_cart_weight; + sd->cart_weight = 0; + sd->cart_max_num = MAX_CART; + sd->cart_num = 0; + for (i = 0; i < MAX_CART; i++) + { + if (sd->status.cart[i].nameid == 0) + continue; + sd->cart_weight += + itemdb_weight (sd->status.cart[i].nameid) * + sd->status.cart[i].amount; + sd->cart_num++; + } + } + + memset (sd->paramb, 0, sizeof (sd->paramb)); + memset (sd->parame, 0, sizeof (sd->parame)); + sd->hit = 0; + sd->flee = 0; + sd->flee2 = 0; + sd->critical = 0; + sd->aspd = 0; + sd->watk = 0; + sd->def = 0; + sd->mdef = 0; + sd->watk2 = 0; + sd->def2 = 0; + sd->mdef2 = 0; + sd->status.max_hp = 0; + sd->status.max_sp = 0; + sd->attackrange = 0; + sd->attackrange_ = 0; + sd->atk_ele = 0; + sd->def_ele = 0; + sd->star = 0; + sd->overrefine = 0; + sd->matk1 = 0; + sd->matk2 = 0; + sd->speed = DEFAULT_WALK_SPEED; + sd->hprate = 100; + sd->sprate = 100; + sd->castrate = 100; + sd->dsprate = 100; + sd->base_atk = 0; + sd->arrow_atk = 0; + sd->arrow_ele = 0; + sd->arrow_hit = 0; + sd->arrow_range = 0; + sd->nhealhp = sd->nhealsp = sd->nshealhp = sd->nshealsp = sd->nsshealhp = + sd->nsshealsp = 0; + memset (sd->addele, 0, sizeof (sd->addele)); + memset (sd->addrace, 0, sizeof (sd->addrace)); + memset (sd->addsize, 0, sizeof (sd->addsize)); + memset (sd->addele_, 0, sizeof (sd->addele_)); + memset (sd->addrace_, 0, sizeof (sd->addrace_)); + memset (sd->addsize_, 0, sizeof (sd->addsize_)); + memset (sd->subele, 0, sizeof (sd->subele)); + memset (sd->subrace, 0, sizeof (sd->subrace)); + memset (sd->addeff, 0, sizeof (sd->addeff)); + memset (sd->addeff2, 0, sizeof (sd->addeff2)); + memset (sd->reseff, 0, sizeof (sd->reseff)); + memset (&sd->special_state, 0, sizeof (sd->special_state)); + memset (sd->weapon_coma_ele, 0, sizeof (sd->weapon_coma_ele)); + memset (sd->weapon_coma_race, 0, sizeof (sd->weapon_coma_race)); + + sd->watk_ = 0; //二刀流用(仮) + sd->watk_2 = 0; + sd->atk_ele_ = 0; + sd->star_ = 0; + sd->overrefine_ = 0; + + sd->aspd_rate = 100; + sd->speed_rate = 100; + sd->hprecov_rate = 100; + sd->sprecov_rate = 100; + sd->critical_def = 0; + sd->double_rate = 0; + sd->near_attack_def_rate = sd->long_attack_def_rate = 0; + sd->atk_rate = sd->matk_rate = 100; + sd->ignore_def_ele = sd->ignore_def_race = 0; + sd->ignore_def_ele_ = sd->ignore_def_race_ = 0; + sd->ignore_mdef_ele = sd->ignore_mdef_race = 0; + sd->arrow_cri = 0; + sd->magic_def_rate = sd->misc_def_rate = 0; + memset (sd->arrow_addele, 0, sizeof (sd->arrow_addele)); + memset (sd->arrow_addrace, 0, sizeof (sd->arrow_addrace)); + memset (sd->arrow_addsize, 0, sizeof (sd->arrow_addsize)); + memset (sd->arrow_addeff, 0, sizeof (sd->arrow_addeff)); + memset (sd->arrow_addeff2, 0, sizeof (sd->arrow_addeff2)); + memset (sd->magic_addele, 0, sizeof (sd->magic_addele)); + memset (sd->magic_addrace, 0, sizeof (sd->magic_addrace)); + memset (sd->magic_subrace, 0, sizeof (sd->magic_subrace)); + sd->perfect_hit = 0; + sd->critical_rate = sd->hit_rate = sd->flee_rate = sd->flee2_rate = 100; + sd->def_rate = sd->def2_rate = sd->mdef_rate = sd->mdef2_rate = 100; + sd->def_ratio_atk_ele = sd->def_ratio_atk_ele_ = 0; + sd->def_ratio_atk_race = sd->def_ratio_atk_race_ = 0; + sd->get_zeny_num = 0; + sd->add_damage_class_count = sd->add_damage_class_count_ = + sd->add_magic_damage_class_count = 0; + sd->add_def_class_count = sd->add_mdef_class_count = 0; + sd->monster_drop_item_count = 0; + memset (sd->add_damage_classrate, 0, sizeof (sd->add_damage_classrate)); + memset (sd->add_damage_classrate_, 0, sizeof (sd->add_damage_classrate_)); + memset (sd->add_magic_damage_classrate, 0, + sizeof (sd->add_magic_damage_classrate)); + memset (sd->add_def_classrate, 0, sizeof (sd->add_def_classrate)); + memset (sd->add_mdef_classrate, 0, sizeof (sd->add_mdef_classrate)); + memset (sd->monster_drop_race, 0, sizeof (sd->monster_drop_race)); + memset (sd->monster_drop_itemrate, 0, sizeof (sd->monster_drop_itemrate)); + sd->speed_add_rate = sd->aspd_add_rate = 100; + sd->double_add_rate = sd->perfect_hit_add = sd->get_zeny_add_num = 0; + sd->splash_range = sd->splash_add_range = 0; + sd->autospell_id = sd->autospell_lv = sd->autospell_rate = 0; + sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate = + sd->sp_drain_per = 0; + sd->hp_drain_rate_ = sd->hp_drain_per_ = sd->sp_drain_rate_ = + sd->sp_drain_per_ = 0; + sd->short_weapon_damage_return = sd->long_weapon_damage_return = 0; + sd->magic_damage_return = 0; //AppleGirl Was Here + sd->random_attack_increase_add = sd->random_attack_increase_per = 0; + + if (!sd->disguiseflag && sd->disguise) + { + sd->disguise = 0; + pc_set_weapon_look (sd); + clif_changelook (&sd->bl, LOOK_SHIELD, sd->status.shield); + clif_changelook (&sd->bl, LOOK_HEAD_BOTTOM, sd->status.head_bottom); + clif_changelook (&sd->bl, LOOK_HEAD_TOP, sd->status.head_top); + clif_changelook (&sd->bl, LOOK_HEAD_MID, sd->status.head_mid); + clif_clearchar (&sd->bl, 9); + pc_setpos (sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + + sd->spellpower_bonus_target = 0; + + for (i = 0; i < 10; i++) + { + index = sd->equip_index[i]; + if (index < 0) + continue; + if (i == 9 && sd->equip_index[8] == index) + continue; + if (i == 5 && sd->equip_index[4] == index) + continue; + if (i == 6 + && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + + if (sd->inventory_data[index]) + { + sd->spellpower_bonus_target += + sd->inventory_data[index]->magic_bonus; + + if (sd->inventory_data[index]->type == 4) + { + if (sd->status.inventory[index].card[0] != 0x00ff + && sd->status.inventory[index].card[0] != 0x00fe + && sd->status.inventory[index].card[0] != (short) 0xff00) + { + int j; + for (j = 0; j < sd->inventory_data[index]->slot; j++) + { // カード + int c = sd->status.inventory[index].card[j]; + if (c > 0) + { + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + if (i == 8 + && sd->status.inventory[index].equip == 0x20) + sd->state.lr_flag = 1; + run_script_l (itemdb_equipscript (c), 0, sd->bl.id, + 0, 2, arg); + sd->state.lr_flag = 0; + } + } + } + } + else if (sd->inventory_data[index]->type == 5) + { // 防具 + if (sd->status.inventory[index].card[0] != 0x00ff + && sd->status.inventory[index].card[0] != 0x00fe + && sd->status.inventory[index].card[0] != (short) 0xff00) + { + int j; + for (j = 0; j < sd->inventory_data[index]->slot; j++) + { // カード + int c = sd->status.inventory[index].card[j]; + if (c > 0) { + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + run_script_l (itemdb_equipscript (c), 0, sd->bl.id, + 0, 2, arg); + } + } + } + } + } + } + +#ifdef USE_ASTRAL_SOUL_SKILL + if (sd->spellpower_bonus_target < 0) + sd->spellpower_bonus_target = + (sd->spellpower_bonus_target * 256) / + (MIN (128 + skill_power (sd, TMW_ASTRAL_SOUL), 256)); +#endif + + if (sd->spellpower_bonus_target < sd->spellpower_bonus_current) + sd->spellpower_bonus_current = sd->spellpower_bonus_target; + + wele = sd->atk_ele; + wele_ = sd->atk_ele_; + def_ele = sd->def_ele; + memcpy (sd->paramcard, sd->parame, sizeof (sd->paramcard)); + + // 装備品によるステータス変化はここで実行 + for (i = 0; i < 10; i++) + { + index = sd->equip_index[i]; + if (index < 0) + continue; + if (i == 9 && sd->equip_index[8] == index) + continue; + if (i == 5 && sd->equip_index[4] == index) + continue; + if (i == 6 + && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + if (sd->inventory_data[index]) + { + sd->def += sd->inventory_data[index]->def; + if (sd->inventory_data[index]->type == 4) + { + int r, wlv = sd->inventory_data[index]->wlv; + if (i == 8 && sd->status.inventory[index].equip == 0x20) + { + //二刀流用データ入力 + sd->watk_ += sd->inventory_data[index]->atk; + sd->watk_2 = (r = sd->status.inventory[index].refine) * // 精錬攻撃力 + refinebonus[wlv][0]; + if ((r -= refinebonus[wlv][2]) > 0) // 過剰精錬ボーナス + sd->overrefine_ = r * refinebonus[wlv][1]; + + if (sd->status.inventory[index].card[0] == 0x00ff) + { // 製造武器 + sd->star_ = (sd->status.inventory[index].card[1] >> 8); // 星のかけら + wele_ = (sd->status.inventory[index].card[1] & 0x0f); // 属 性 + } + sd->attackrange_ += sd->inventory_data[index]->range; + sd->state.lr_flag = 1; + { + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + run_script_l (sd->inventory_data[index]->equip_script, 0, + sd->bl.id, 0, 2, arg); + } + sd->state.lr_flag = 0; + } + else + { //二刀流武器以外 + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + sd->watk += sd->inventory_data[index]->atk; + sd->watk2 += (r = sd->status.inventory[index].refine) * // 精錬攻撃力 + refinebonus[wlv][0]; + if ((r -= refinebonus[wlv][2]) > 0) // 過剰精錬ボーナス + sd->overrefine += r * refinebonus[wlv][1]; + + if (sd->status.inventory[index].card[0] == 0x00ff) + { // 製造武器 + sd->star += (sd->status.inventory[index].card[1] >> 8); // 星のかけら + wele = (sd->status.inventory[index].card[1] & 0x0f); // 属 性 + } + sd->attackrange += sd->inventory_data[index]->range; + run_script_l (sd->inventory_data[index]->equip_script, 0, + sd->bl.id, 0, 2, arg); + } + } + else if (sd->inventory_data[index]->type == 5) + { + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + sd->watk += sd->inventory_data[index]->atk; + refinedef += + sd->status.inventory[index].refine * refinebonus[0][0]; + run_script_l (sd->inventory_data[index]->equip_script, 0, + sd->bl.id, 0, 2, arg); + } + } + } + + if (battle_is_unarmed (&sd->bl)) + { + sd->watk += skill_power (sd, TMW_BRAWLING) / 3; // +66 for 200 + sd->watk2 += skill_power (sd, TMW_BRAWLING) >> 3; // +25 for 200 + sd->watk_ += skill_power (sd, TMW_BRAWLING) / 3; // +66 for 200 + sd->watk_2 += skill_power (sd, TMW_BRAWLING) >> 3; // +25 for 200 + } + + if (sd->equip_index[10] >= 0) + { // 矢 + index = sd->equip_index[10]; + if (sd->inventory_data[index]) + { //まだ属性が入っていない + argrec_t arg[2]; + arg[0].name = "@slotId"; + arg[0].v.i = i; + arg[1].name = "@itemId"; + arg[1].v.i = sd->inventory_data[index]->nameid; + sd->state.lr_flag = 2; + run_script_l (sd->inventory_data[index]->equip_script, 0, sd->bl.id, + 0, 2, arg); + sd->state.lr_flag = 0; + sd->arrow_atk += sd->inventory_data[index]->atk; + } + } + sd->def += (refinedef + 50) / 100; + + if (sd->attackrange < 1) + sd->attackrange = 1; + if (sd->attackrange_ < 1) + sd->attackrange_ = 1; + if (sd->attackrange < sd->attackrange_) + sd->attackrange = sd->attackrange_; + if (sd->status.weapon == 11) + sd->attackrange += sd->arrow_range; + if (wele > 0) + sd->atk_ele = wele; + if (wele_ > 0) + sd->atk_ele_ = wele_; + if (def_ele > 0) + sd->def_ele = def_ele; + sd->double_rate += sd->double_add_rate; + sd->perfect_hit += sd->perfect_hit_add; + sd->get_zeny_num += sd->get_zeny_add_num; + sd->splash_range += sd->splash_add_range; + if (sd->speed_add_rate != 100) + sd->speed_rate += sd->speed_add_rate - 100; + if (sd->aspd_add_rate != 100) + sd->aspd_rate += sd->aspd_add_rate - 100; + + // 武器ATKサイズ補正 (右手) + sd->atkmods[0] = atkmods[0][sd->weapontype1]; + sd->atkmods[1] = atkmods[1][sd->weapontype1]; + sd->atkmods[2] = atkmods[2][sd->weapontype1]; + //武器ATKサイズ補正 (左手) + sd->atkmods_[0] = atkmods[0][sd->weapontype2]; + sd->atkmods_[1] = atkmods[1][sd->weapontype2]; + sd->atkmods_[2] = atkmods[2][sd->weapontype2]; + +/* + // jobボーナス分 + for(i=0;i<sd->status.job_level && i<MAX_LEVEL;i++){ + if(job_bonus[s_class.upper][s_class.job][i]) + sd->paramb[job_bonus[s_class.upper][s_class.job][i]-1]++; + } +*/ + + if ((skill = pc_checkskill (sd, MC_INCCARRY)) > 0) // skill can be used with an item now, thanks to orn [Valaris] + sd->max_weight += skill * 1000; + + // ステータス変化による基本パラメータ補正 + if (sd->sc_count) + { + if (sd->sc_data[SC_CONCENTRATE].timer != -1 + && sd->sc_data[SC_QUAGMIRE].timer == -1) + { // 集中力向上 + sd->paramb[1] += + (sd->status.agi + sd->paramb[1] + sd->parame[1] - + sd->paramcard[1]) * (2 + + sd->sc_data[SC_CONCENTRATE].val1) / 100; + sd->paramb[4] += + (sd->status.dex + sd->paramb[4] + sd->parame[4] - + sd->paramcard[4]) * (2 + + sd->sc_data[SC_CONCENTRATE].val1) / 100; + } + if (sd->sc_data[SC_INCREASEAGI].timer != -1 + && sd->sc_data[SC_QUAGMIRE].timer == -1 + && sd->sc_data[SC_DONTFORGETME].timer == -1) + { // 速度増加 + sd->paramb[1] += 2 + sd->sc_data[SC_INCREASEAGI].val1; + sd->speed -= sd->speed * 25 / 100; + } + if (sd->sc_data[SC_DECREASEAGI].timer != -1) // 速度減少(agiはbattle.cで) + sd->speed = sd->speed * 125 / 100; + if (sd->sc_data[SC_CLOAKING].timer != -1) + sd->speed = + (sd->speed * (76 + (sd->sc_data[SC_INCREASEAGI].val1 * 3))) / + 100; + if (sd->sc_data[SC_BLESSING].timer != -1) + { // ブレッシング + sd->paramb[0] += sd->sc_data[SC_BLESSING].val1; + sd->paramb[3] += sd->sc_data[SC_BLESSING].val1; + sd->paramb[4] += sd->sc_data[SC_BLESSING].val1; + } + if (sd->sc_data[SC_GLORIA].timer != -1) // グロリア + sd->paramb[5] += 30; + if (sd->sc_data[SC_LOUD].timer != -1 && sd->sc_data[SC_QUAGMIRE].timer == -1) // ラウドボイス + sd->paramb[0] += 4; + if (sd->sc_data[SC_QUAGMIRE].timer != -1) + { // クァグマイア + sd->speed = sd->speed * 3 / 2; + sd->paramb[1] -= + (sd->status.agi + sd->paramb[1] + sd->parame[1]) / 2; + sd->paramb[4] -= + (sd->status.dex + sd->paramb[4] + sd->parame[4]) / 2; + } + if (sd->sc_data[SC_TRUESIGHT].timer != -1) + { // トゥルーサイト + sd->paramb[0] += 5; + sd->paramb[1] += 5; + sd->paramb[2] += 5; + sd->paramb[3] += 5; + sd->paramb[4] += 5; + sd->paramb[5] += 5; + } + } + sd->speed -= skill_power (sd, TMW_SPEED) >> 3; + sd->aspd_rate -= skill_power (sd, TMW_SPEED) / 10; + if (sd->aspd_rate < 20) + sd->aspd_rate = 20; + +/* + //1度も死んでないJob70スパノビに+10 + if(s_class.job == 23 && sd->die_counter == 0 && sd->status.job_level >= 70){ + sd->paramb[0]+= 15; + sd->paramb[1]+= 15; + sd->paramb[2]+= 15; + sd->paramb[3]+= 15; + sd->paramb[4]+= 15; + sd->paramb[5]+= 15; + } +*/ + sd->paramc[0] = sd->status.str + sd->paramb[0] + sd->parame[0]; + sd->paramc[1] = sd->status.agi + sd->paramb[1] + sd->parame[1]; + sd->paramc[2] = sd->status.vit + sd->paramb[2] + sd->parame[2]; + sd->paramc[3] = sd->status.int_ + sd->paramb[3] + sd->parame[3]; + sd->paramc[4] = sd->status.dex + sd->paramb[4] + sd->parame[4]; + sd->paramc[5] = sd->status.luk + sd->paramb[5] + sd->parame[5]; + for (i = 0; i < 6; i++) + if (sd->paramc[i] < 0) + sd->paramc[i] = 0; + + if (sd->status.weapon == 11 || sd->status.weapon == 13 + || sd->status.weapon == 14) + { + str = sd->paramc[4]; + dex = sd->paramc[0]; + } + else + { + str = sd->paramc[0]; + dex = sd->paramc[4]; + sd->critical += ((dex * 3) >> 1); + } + dstr = str / 10; + sd->base_atk += str + dstr * dstr + dex / 5 + sd->paramc[5] / 5; +//fprintf(stderr, "baseatk = %d = x + %d + %d + %d + %d\n", sd->base_atk, str, dstr*dstr, dex/5, sd->paramc[5]/5); + sd->matk1 += sd->paramc[3] + (sd->paramc[3] / 5) * (sd->paramc[3] / 5); + sd->matk2 += sd->paramc[3] + (sd->paramc[3] / 7) * (sd->paramc[3] / 7); + if (sd->matk1 < sd->matk2) + { + int temp = sd->matk2; + sd->matk2 = sd->matk1; + sd->matk1 = temp; + } + // [Fate] New tmw magic system + sd->matk1 += sd->status.base_level + sd->spellpower_bonus_current; +#ifdef USE_ASTRAL_SOUL_SKILL + if (sd->matk1 > MAGIC_SKILL_THRESHOLD) + { + int bonus = sd->matk1 - MAGIC_SKILL_THRESHOLD; + // Ok if you are above a certain threshold, you get only (1/8) of that matk1 + // if you have Astral soul skill you can get the whole power again (and additionally the 1/8 added) + sd->matk1 = MAGIC_SKILL_THRESHOLD + (bonus>>3) + ((3*bonus*skill_power(sd, TMW_ASTRAL_SOUL))>>9); + } +#endif + sd->matk2 = 0; + if (sd->matk1 < 0) + sd->matk1 = 0; + + sd->hit += sd->paramc[4] + sd->status.base_level; + sd->flee += sd->paramc[1] + sd->status.base_level; + sd->def2 += sd->paramc[2]; + sd->mdef2 += sd->paramc[3]; + sd->flee2 += sd->paramc[5] + 10; + sd->critical += (sd->paramc[5] * 3) + 10; + + // 200 is the maximum of the skill + // def2 is the defence gained by vit, whereas "def", which is gained by armor, stays as is + int spbsk = skill_power (sd, TMW_RAGING); + if (spbsk != 0 && sd->attackrange <= 2) + { + sd->critical += sd->critical * spbsk / 100; + sd->def2 = (sd->def2 * 256) / (256 + spbsk); + } + + if (sd->base_atk < 1) + sd->base_atk = 1; + if (sd->critical_rate != 100) + sd->critical = (sd->critical * sd->critical_rate) / 100; + if (sd->critical < 10) + sd->critical = 10; + if (sd->hit_rate != 100) + sd->hit = (sd->hit * sd->hit_rate) / 100; + if (sd->hit < 1) + sd->hit = 1; + if (sd->flee_rate != 100) + sd->flee = (sd->flee * sd->flee_rate) / 100; + if (sd->flee < 1) + sd->flee = 1; + if (sd->flee2_rate != 100) + sd->flee2 = (sd->flee2 * sd->flee2_rate) / 100; + if (sd->flee2 < 10) + sd->flee2 = 10; + if (sd->def_rate != 100) + sd->def = (sd->def * sd->def_rate) / 100; + if (sd->def < 0) + sd->def = 0; + if (sd->def2_rate != 100) + sd->def2 = (sd->def2 * sd->def2_rate) / 100; + if (sd->def2 < 1) + sd->def2 = 1; + if (sd->mdef_rate != 100) + sd->mdef = (sd->mdef * sd->mdef_rate) / 100; + if (sd->mdef < 0) + sd->mdef = 0; + if (sd->mdef2_rate != 100) + sd->mdef2 = (sd->mdef2 * sd->mdef2_rate) / 100; + if (sd->mdef2 < 1) + sd->mdef2 = 1; + + // 二刀流 ASPD 修正 + if (sd->status.weapon <= 16) + sd->aspd += + aspd_base[s_class.job][sd->status.weapon] - (sd->paramc[1] * 4 + + sd->paramc[4]) * + aspd_base[s_class.job][sd->status.weapon] / 1000; + else + sd->aspd += ((aspd_base[s_class.job][sd->weapontype1] - + (sd->paramc[1] * 4 + + sd->paramc[4]) * + aspd_base[s_class.job][sd->weapontype1] / 1000) + + (aspd_base[s_class.job][sd->weapontype2] - + (sd->paramc[1] * 4 + + sd->paramc[4]) * + aspd_base[s_class.job][sd->weapontype2] / 1000)) * 140 / + 200; + + aspd_rate = sd->aspd_rate; + + //攻撃速度増加 + + if ((skill = pc_checkskill (sd, AC_VULTURE)) > 0) + { // ワシの目 + sd->hit += skill; + if (sd->status.weapon == 11) + sd->attackrange += skill; + } + + if (sd->attackrange > 2) + { // [fate] ranged weapon? + sd->attackrange += MIN (skill_power (sd, AC_OWL) / 60, 3); + sd->hit += skill_power (sd, AC_OWL) / 10; // 20 for 200 + } + + if ((skill = pc_checkskill (sd, BS_WEAPONRESEARCH)) > 0) // 武器研究の命中率増加 + sd->hit += skill * 2; + if (sd->status.option & 2 && (skill = pc_checkskill (sd, RG_TUNNELDRIVE)) > 0) // トンネルドライブ + sd->speed += (1.2 * DEFAULT_WALK_SPEED - skill * 9); + if (pc_iscarton (sd) && (skill = pc_checkskill (sd, MC_PUSHCART)) > 0) // カートによる速度低下 + sd->speed += (10 - skill) * (DEFAULT_WALK_SPEED * 0.1); + else if (pc_isriding (sd)) // ペコペコ乗りによる速度増加 + sd->speed -= (0.25 * DEFAULT_WALK_SPEED); + sd->max_weight += 1000; + if (sd->sc_count) + { + if (sd->sc_data[SC_WINDWALK].timer != -1) //ウィンドウォーク時はLv*2%減算 + sd->speed -= + sd->speed * (sd->sc_data[SC_WINDWALK].val1 * 2) / 100; + if (sd->sc_data[SC_CARTBOOST].timer != -1) // カートブースト + sd->speed -= (DEFAULT_WALK_SPEED * 20) / 100; + if (sd->sc_data[SC_BERSERK].timer != -1) //バーサーク中はIAと同じぐらい速い? + sd->speed -= sd->speed * 25 / 100; + if (sd->sc_data[SC_WEDDING].timer != -1) //結婚中は歩くのが遅い + sd->speed = 2 * DEFAULT_WALK_SPEED; + } + + if ((skill = pc_checkskill (sd, CR_TRUST)) > 0) + { // フェイス + sd->status.max_hp += skill * 200; + sd->subele[6] += skill * 5; + } + if ((skill = pc_checkskill (sd, BS_SKINTEMPER)) > 0) + sd->subele[3] += skill * 4; + + bl = sd->status.base_level; + + sd->status.max_hp += + (3500 + bl * hp_coefficient2[s_class.job] + + hp_sigma_val[s_class.job][(bl > 0) ? bl - 1 : 0]) / 100 * (100 + + sd->paramc + [2]) / + 100 + (sd->parame[2] - sd->paramcard[2]); + if (s_class.upper == 1) // [MouseJstr] + sd->status.max_hp = sd->status.max_hp * 130 / 100; + if (sd->hprate != 100) + sd->status.max_hp = sd->status.max_hp * sd->hprate / 100; + + if (sd->sc_data && sd->sc_data[SC_BERSERK].timer != -1) + { // バーサーク + sd->status.max_hp = sd->status.max_hp * 3; + sd->status.hp = sd->status.hp * 3; + if (sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if (sd->status.hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.hp = battle_config.max_hp; + } + if (s_class.job == 23 && sd->status.base_level >= 99) + { + sd->status.max_hp = sd->status.max_hp + 2000; + } + + if (sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if (sd->status.max_hp <= 0) + sd->status.max_hp = 1; // end + + // 最大SP計算 + sd->status.max_sp += + ((sp_coefficient[s_class.job] * bl) + 1000) / 100 * (100 + + sd->paramc[3]) / + 100 + (sd->parame[3] - sd->paramcard[3]); + if (s_class.upper == 1) // [MouseJstr] + sd->status.max_sp = sd->status.max_sp * 130 / 100; + if (sd->sprate != 100) + sd->status.max_sp = sd->status.max_sp * sd->sprate / 100; + + if ((skill = pc_checkskill (sd, HP_MEDITATIO)) > 0) // メディテイティオ + sd->status.max_sp += sd->status.max_sp * skill / 100; + if ((skill = pc_checkskill (sd, HW_SOULDRAIN)) > 0) // ソウルドレイン + sd->status.max_sp += sd->status.max_sp * 2 * skill / 100; + + if (sd->status.max_sp < 0 || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + + //自然回復HP + sd->nhealhp = 1 + (sd->paramc[2] / 5) + (sd->status.max_hp / 200); + if ((skill = pc_checkskill (sd, SM_RECOVERY)) > 0) + { // HP回復力向上 + sd->nshealhp = skill * 5 + (sd->status.max_hp * skill / 500); + if (sd->nshealhp > 0x7fff) + sd->nshealhp = 0x7fff; + } + //自然回復SP + sd->nhealsp = 1 + (sd->paramc[3] / 6) + (sd->status.max_sp / 100); + if (sd->paramc[3] >= 120) + sd->nhealsp += ((sd->paramc[3] - 120) >> 1) + 4; + if ((skill = pc_checkskill (sd, MG_SRECOVERY)) > 0) + { // SP回復力向上 + sd->nshealsp = skill * 3 + (sd->status.max_sp * skill / 500); + if (sd->nshealsp > 0x7fff) + sd->nshealsp = 0x7fff; + } + + if ((skill = pc_checkskill (sd, MO_SPIRITSRECOVERY)) > 0) + { + sd->nsshealhp = skill * 4 + (sd->status.max_hp * skill / 500); + sd->nsshealsp = skill * 2 + (sd->status.max_sp * skill / 500); + if (sd->nsshealhp > 0x7fff) + sd->nsshealhp = 0x7fff; + if (sd->nsshealsp > 0x7fff) + sd->nsshealsp = 0x7fff; + } + if (sd->hprecov_rate != 100) + { + sd->nhealhp = sd->nhealhp * sd->hprecov_rate / 100; + if (sd->nhealhp < 1) + sd->nhealhp = 1; + } + if (sd->sprecov_rate != 100) + { + sd->nhealsp = sd->nhealsp * sd->sprecov_rate / 100; + if (sd->nhealsp < 1) + sd->nhealsp = 1; + } + if ((skill = pc_checkskill (sd, HP_MEDITATIO)) > 0) + { // メディテイティオはSPRではなく自然回復にかかる + sd->nhealsp += 3 * skill * (sd->status.max_sp) / 100; + if (sd->nhealsp > 0x7fff) + sd->nhealsp = 0x7fff; + } + + // 種族耐性(これでいいの? ディバインプロテクションと同じ処理がいるかも) + if ((skill = pc_checkskill (sd, SA_DRAGONOLOGY)) > 0) + { // ドラゴノロジー + skill = skill * 4; + sd->addrace[9] += skill; + sd->addrace_[9] += skill; + sd->subrace[9] += skill; + sd->magic_addrace[9] += skill; + sd->magic_subrace[9] -= skill; + } + + //Flee上昇 + if ((skill = pc_checkskill (sd, TF_MISS)) > 0) + { // 回避率増加 + if (sd->status.pc_class == 6 || sd->status.pc_class == 4007 + || sd->status.pc_class == 23) + { + sd->flee += skill * 3; + } + if (sd->status.pc_class == 12 || sd->status.pc_class == 17 + || sd->status.pc_class == 4013 || sd->status.pc_class == 4018) + sd->flee += skill * 4; + if (sd->status.pc_class == 12 || sd->status.pc_class == 4013) + sd->speed -= sd->speed * (skill * .5) / 100; + } + if ((skill = pc_checkskill (sd, MO_DODGE)) > 0) // 見切り + sd->flee += (skill * 3) >> 1; + + // スキルやステータス異常による残りのパラメータ補正 + if (sd->sc_count) + { + // ATK/DEF変化形 + if (sd->sc_data[SC_ANGELUS].timer != -1) // エンジェラス + sd->def2 = + sd->def2 * (110 + 5 * sd->sc_data[SC_ANGELUS].val1) / 100; + if (sd->sc_data[SC_IMPOSITIO].timer != -1) + { // インポシティオマヌス + sd->watk += sd->sc_data[SC_IMPOSITIO].val1 * 5; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_IMPOSITIO].val1 * 5; + } + if (sd->sc_data[SC_PROVOKE].timer != -1) + { // プロボック + sd->def2 = + sd->def2 * (100 - 6 * sd->sc_data[SC_PROVOKE].val1) / 100; + sd->base_atk = + sd->base_atk * (100 + 2 * sd->sc_data[SC_PROVOKE].val1) / 100; + sd->watk = + sd->watk * (100 + 2 * sd->sc_data[SC_PROVOKE].val1) / 100; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->type == 4) + sd->watk_ = + sd->watk_ * (100 + + 2 * sd->sc_data[SC_PROVOKE].val1) / 100; + } + if (sd->sc_data[SC_ENDURE].timer != -1) + sd->mdef2 += sd->sc_data[SC_ENDURE].val1; + if (sd->sc_data[SC_MINDBREAKER].timer != -1) + { // プロボック + sd->mdef2 = + sd->mdef2 * (100 - + 6 * sd->sc_data[SC_MINDBREAKER].val1) / 100; + sd->matk1 = + sd->matk1 * (100 + + 2 * sd->sc_data[SC_MINDBREAKER].val1) / 100; + sd->matk2 = + sd->matk2 * (100 + + 2 * sd->sc_data[SC_MINDBREAKER].val1) / 100; + } + if (sd->sc_data[SC_POISON].timer != -1) // 毒状態 + sd->def2 = sd->def2 * 75 / 100; + if (sd->sc_data[SC_DRUMBATTLE].timer != -1) + { // 戦太鼓の響き + sd->watk += sd->sc_data[SC_DRUMBATTLE].val2; + sd->def += sd->sc_data[SC_DRUMBATTLE].val3; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_DRUMBATTLE].val2; + } + if (sd->sc_data[SC_NIBELUNGEN].timer != -1) + { // ニーベルングの指輪 + index = sd->equip_index[9]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->wlv == 3) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val3; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->wlv == 3) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val3; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->wlv == 4) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val2; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->wlv == 4) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val2; + } + + if (sd->sc_data[SC_VOLCANO].timer != -1 && sd->def_ele == 3) + { // ボルケーノ + sd->watk += sd->sc_data[SC_VIOLENTGALE].val3; + } + + if (sd->sc_data[SC_SIGNUMCRUCIS].timer != -1) + sd->def = + sd->def * (100 - sd->sc_data[SC_SIGNUMCRUCIS].val2) / 100; + if (sd->sc_data[SC_ETERNALCHAOS].timer != -1) // エターナルカオス + sd->def = 0; + + if (sd->sc_data[SC_CONCENTRATION].timer != -1) + { //コンセントレーション + sd->watk = + sd->watk * (100 + + 5 * sd->sc_data[SC_CONCENTRATION].val1) / 100; + index = sd->equip_index[8]; + if (index >= 0 && sd->inventory_data[index] + && sd->inventory_data[index]->type == 4) + sd->watk_ = + sd->watk * (100 + + 5 * sd->sc_data[SC_CONCENTRATION].val1) / 100; + sd->def = + sd->def * (100 - + 5 * sd->sc_data[SC_CONCENTRATION].val1) / 100; + } + + if (sd->sc_data[SC_MAGICPOWER].timer != -1) + { //魔法力増幅 + sd->matk1 = + sd->matk1 * (100 + 2 * sd->sc_data[SC_MAGICPOWER].val1) / 100; + sd->matk2 = + sd->matk2 * (100 + 2 * sd->sc_data[SC_MAGICPOWER].val1) / 100; + } + if (sd->sc_data[SC_ATKPOT].timer != -1) + sd->watk += sd->sc_data[SC_ATKPOT].val1; + if (sd->sc_data[SC_MATKPOT].timer != -1) + { + sd->matk1 += sd->sc_data[SC_MATKPOT].val1; + sd->matk2 += sd->sc_data[SC_MATKPOT].val1; + } + + // ASPD/移動速度変化系 + if (sd->sc_data[SC_TWOHANDQUICKEN].timer != -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + if (sd->sc_data[SC_ADRENALINE].timer != -1 + && sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 + && sd->sc_data[SC_QUAGMIRE].timer == -1 + && sd->sc_data[SC_DONTFORGETME].timer == -1) + { // アドレナリンラッシュ + if (sd->sc_data[SC_ADRENALINE].val2 + || !battle_config.party_skill_penaly) + aspd_rate -= 30; + else + aspd_rate -= 25; + } + if (sd->sc_data[SC_SPEARSQUICKEN].timer != -1 && sd->sc_data[SC_ADRENALINE].timer == -1 && sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sd->sc_data[SC_SPEARSQUICKEN].val2; + if (sd->sc_data[SC_ASSNCROS].timer != -1 && // 夕陽のアサシンクロス + sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 + && sd->sc_data[SC_ADRENALINE].timer == -1 + && sd->sc_data[SC_SPEARSQUICKEN].timer == -1 + && sd->sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= + 5 + sd->sc_data[SC_ASSNCROS].val1 + + sd->sc_data[SC_ASSNCROS].val2 + sd->sc_data[SC_ASSNCROS].val3; + if (sd->sc_data[SC_DONTFORGETME].timer != -1) + { // 私を忘れないで + aspd_rate += + sd->sc_data[SC_DONTFORGETME].val1 * 3 + + sd->sc_data[SC_DONTFORGETME].val2 + + (sd->sc_data[SC_DONTFORGETME].val3 >> 16); + sd->speed = + sd->speed * (100 + sd->sc_data[SC_DONTFORGETME].val1 * 2 + + sd->sc_data[SC_DONTFORGETME].val2 + + (sd->sc_data[SC_DONTFORGETME].val3 & 0xffff)) / + 100; + } + if (sd->sc_data[i = SC_SPEEDPOTION2].timer != -1 || sd->sc_data[i = SC_SPEEDPOTION1].timer != -1 || sd->sc_data[i = SC_SPEEDPOTION0].timer != -1) // 増 速ポーション + aspd_rate -= sd->sc_data[i].val1; + + if (sd->sc_data[SC_HASTE].timer != -1) + aspd_rate -= sd->sc_data[SC_HASTE].val1; + + /* Slow down if protected */ + + if (sd->sc_data[SC_PHYS_SHIELD].timer != -1) + aspd_rate += sd->sc_data[SC_PHYS_SHIELD].val1; + + // HIT/FLEE変化系 + if (sd->sc_data[SC_WHISTLE].timer != -1) + { // 口笛 + sd->flee += sd->flee * (sd->sc_data[SC_WHISTLE].val1 + + sd->sc_data[SC_WHISTLE].val2 + + (sd->sc_data[SC_WHISTLE].val3 >> 16)) / + 100; + sd->flee2 += + (sd->sc_data[SC_WHISTLE].val1 + sd->sc_data[SC_WHISTLE].val2 + + (sd->sc_data[SC_WHISTLE].val3 & 0xffff)) * 10; + } + if (sd->sc_data[SC_HUMMING].timer != -1) // ハミング + sd->hit += + (sd->sc_data[SC_HUMMING].val1 * 2 + + sd->sc_data[SC_HUMMING].val2 + + sd->sc_data[SC_HUMMING].val3) * sd->hit / 100; + if (sd->sc_data[SC_VIOLENTGALE].timer != -1 && sd->def_ele == 4) + { // バイオレントゲイル + sd->flee += sd->flee * sd->sc_data[SC_VIOLENTGALE].val3 / 100; + } + if (sd->sc_data[SC_BLIND].timer != -1) + { // 暗黒 + sd->hit -= sd->hit * 25 / 100; + sd->flee -= sd->flee * 25 / 100; + } + if (sd->sc_data[SC_WINDWALK].timer != -1) // ウィンドウォーク + sd->flee += sd->flee * (sd->sc_data[SC_WINDWALK].val2) / 100; + if (sd->sc_data[SC_SPIDERWEB].timer != -1) //スパイダーウェブ + sd->flee -= sd->flee * 50 / 100; + if (sd->sc_data[SC_TRUESIGHT].timer != -1) //トゥルーサイト + sd->hit += 3 * (sd->sc_data[SC_TRUESIGHT].val1); + if (sd->sc_data[SC_CONCENTRATION].timer != -1) //コンセントレーション + sd->hit += (10 * (sd->sc_data[SC_CONCENTRATION].val1)); + + // 耐性 + if (sd->sc_data[SC_SIEGFRIED].timer != -1) + { // 不死身のジークフリード + sd->subele[1] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[2] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[3] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[4] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[5] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[6] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[7] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[8] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[9] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + } + if (sd->sc_data[SC_PROVIDENCE].timer != -1) + { // プロヴィデンス + sd->subele[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 聖属性 + sd->subrace[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 悪魔 + } + + // その他 + if (sd->sc_data[SC_APPLEIDUN].timer != -1) + { // イドゥンの林檎 + sd->status.max_hp += + ((5 + sd->sc_data[SC_APPLEIDUN].val1 * 2 + + ((sd->sc_data[SC_APPLEIDUN].val2 + 1) >> 1) + + sd->sc_data[SC_APPLEIDUN].val3 / 10) * sd->status.max_hp) / + 100; + if (sd->status.max_hp < 0 + || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if (sd->sc_data[SC_DELUGE].timer != -1 && sd->def_ele == 1) + { // デリュージ + sd->status.max_hp += + sd->status.max_hp * sd->sc_data[SC_DELUGE].val3 / 100; + if (sd->status.max_hp < 0 + || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if (sd->sc_data[SC_SERVICE4U].timer != -1) + { // サービスフォーユー + sd->status.max_sp += + sd->status.max_sp * (10 + sd->sc_data[SC_SERVICE4U].val1 + + sd->sc_data[SC_SERVICE4U].val2 + + sd->sc_data[SC_SERVICE4U].val3) / 100; + if (sd->status.max_sp < 0 + || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + sd->dsprate -= + (10 + sd->sc_data[SC_SERVICE4U].val1 * 3 + + sd->sc_data[SC_SERVICE4U].val2 + + sd->sc_data[SC_SERVICE4U].val3); + if (sd->dsprate < 0) + sd->dsprate = 0; + } + + if (sd->sc_data[SC_FORTUNE].timer != -1) // 幸運のキス + sd->critical += + (10 + sd->sc_data[SC_FORTUNE].val1 + + sd->sc_data[SC_FORTUNE].val2 + + sd->sc_data[SC_FORTUNE].val3) * 10; + + if (sd->sc_data[SC_EXPLOSIONSPIRITS].timer != -1) + { // 爆裂波動 + if (s_class.job == 23) + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val1 * 100; + else + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val2; + } + + if (sd->sc_data[SC_STEELBODY].timer != -1) + { // 金剛 + sd->def = 90; + sd->mdef = 90; + aspd_rate += 25; + sd->speed = (sd->speed * 125) / 100; + } + if (sd->sc_data[SC_DEFENDER].timer != -1) + { + sd->aspd += (550 - sd->sc_data[SC_DEFENDER].val1 * 50); + sd->speed = + (sd->speed * (155 - sd->sc_data[SC_DEFENDER].val1 * 5)) / 100; + } + if (sd->sc_data[SC_ENCPOISON].timer != -1) + sd->addeff[4] += sd->sc_data[SC_ENCPOISON].val2; + + if (sd->sc_data[SC_DANCING].timer != -1) + { // 演奏/ダンス使用中 + sd->speed *= 4; + sd->nhealsp = 0; + sd->nshealsp = 0; + sd->nsshealsp = 0; + } + if (sd->sc_data[SC_CURSE].timer != -1) + sd->speed += 450; + + if (sd->sc_data[SC_TRUESIGHT].timer != -1) //トゥルーサイト + sd->critical += + sd->critical * (sd->sc_data[SC_TRUESIGHT].val1) / 100; + +/* if(sd->sc_data[SC_VOLCANO].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[2]+=sd->sc_data[SC_VOLCANO].val2;//% of granting + if(sd->sc_data[SC_DELUGE].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[0]+=sd->sc_data[SC_DELUGE].val2;//% of granting + */ + } + + if (sd->speed_rate != 100) + sd->speed = sd->speed * sd->speed_rate / 100; + if (sd->speed < 1) + sd->speed = 1; + if (aspd_rate != 100) + sd->aspd = sd->aspd * aspd_rate / 100; + if (pc_isriding (sd)) // 騎兵修練 + sd->aspd = + sd->aspd * (100 + + 10 * (5 - + pc_checkskill (sd, KN_CAVALIERMASTERY))) / 100; + + if (sd->attack_spell_override) + sd->aspd = sd->attack_spell_delay; + + if (sd->aspd < battle_config.max_aspd) + sd->aspd = battle_config.max_aspd; + sd->amotion = sd->aspd; + sd->dmotion = 800 - sd->paramc[1] * 4; + if (sd->dmotion < 400) + sd->dmotion = 400; + if (sd->skilltimer != -1 && (skill = pc_checkskill (sd, SA_FREECAST)) > 0) + { + sd->prev_speed = sd->speed; + sd->speed = sd->speed * (175 - skill * 5) / 100; + } + + if (sd->status.hp > sd->status.max_hp) + sd->status.hp = sd->status.max_hp; + if (sd->status.sp > sd->status.max_sp) + sd->status.sp = sd->status.max_sp; + + if (first & 4) + return 0; + if (first & 3) + { + clif_updatestatus (sd, SP_SPEED); + clif_updatestatus (sd, SP_MAXHP); + clif_updatestatus (sd, SP_MAXSP); + if (first & 1) + { + clif_updatestatus (sd, SP_HP); + clif_updatestatus (sd, SP_SP); + } + return 0; + } + + if (b_class != sd->view_class) + { + clif_changelook (&sd->bl, LOOK_BASE, sd->view_class); + clif_changelook (&sd->bl, LOOK_WEAPON, 0); + } + + if (memcmp (b_skill, sd->status.skill, sizeof (sd->status.skill)) + || b_attackrange != sd->attackrange) + clif_skillinfoblock (sd); // スキル送信 + + if (b_speed != sd->speed) + clif_updatestatus (sd, SP_SPEED); + if (b_weight != sd->weight) + clif_updatestatus (sd, SP_WEIGHT); + if (b_max_weight != sd->max_weight) + { + clif_updatestatus (sd, SP_MAXWEIGHT); + pc_checkweighticon (sd); + } + for (i = 0; i < 6; i++) + if (b_paramb[i] + b_parame[i] != sd->paramb[i] + sd->parame[i]) + clif_updatestatus (sd, SP_STR + i); + if (b_hit != sd->hit) + clif_updatestatus (sd, SP_HIT); + if (b_flee != sd->flee) + clif_updatestatus (sd, SP_FLEE1); + if (b_aspd != sd->aspd) + clif_updatestatus (sd, SP_ASPD); + if (b_watk != sd->watk || b_base_atk != sd->base_atk) + clif_updatestatus (sd, SP_ATK1); + if (b_def != sd->def) + clif_updatestatus (sd, SP_DEF1); + if (b_watk2 != sd->watk2) + clif_updatestatus (sd, SP_ATK2); + if (b_def2 != sd->def2) + clif_updatestatus (sd, SP_DEF2); + if (b_flee2 != sd->flee2) + clif_updatestatus (sd, SP_FLEE2); + if (b_critical != sd->critical) + clif_updatestatus (sd, SP_CRITICAL); + if (b_matk1 != sd->matk1) + clif_updatestatus (sd, SP_MATK1); + if (b_matk2 != sd->matk2) + clif_updatestatus (sd, SP_MATK2); + if (b_mdef != sd->mdef) + clif_updatestatus (sd, SP_MDEF1); + if (b_mdef2 != sd->mdef2) + clif_updatestatus (sd, SP_MDEF2); + if (b_attackrange != sd->attackrange) + clif_updatestatus (sd, SP_ATTACKRANGE); + if (b_max_hp != sd->status.max_hp) + clif_updatestatus (sd, SP_MAXHP); + if (b_max_sp != sd->status.max_sp) + clif_updatestatus (sd, SP_MAXSP); + if (b_hp != sd->status.hp) + clif_updatestatus (sd, SP_HP); + if (b_sp != sd->status.sp) + clif_updatestatus (sd, SP_SP); + +/* if(before.cart_num != before.cart_num || before.cart_max_num != before.cart_max_num || + before.cart_weight != before.cart_weight || before.cart_max_weight != before.cart_max_weight ) + clif_updatestatus(sd,SP_CARTINFO);*/ + + if (sd->status.hp < sd->status.max_hp >> 2 + && pc_checkskill (sd, SM_AUTOBERSERK) > 0 + && (sd->sc_data[SC_PROVOKE].timer == -1 + || sd->sc_data[SC_PROVOKE].val2 == 0) && !pc_isdead (sd)) + // オートバーサーク発動 + skill_status_change_start (&sd->bl, SC_PROVOKE, 10, 1, 0, 0, 0, 0); + + return 0; +} + +/*========================================== + * 装 備品による能力等のボーナス設定 + *------------------------------------------ + */ +int pc_bonus (struct map_session_data *sd, int type, int val) +{ + nullpo_retr (0, sd); + + switch (type) + { + case SP_STR: + case SP_AGI: + case SP_VIT: + case SP_INT: + case SP_DEX: + case SP_LUK: + if (sd->state.lr_flag != 2) + sd->parame[type - SP_STR] += val; + break; + case SP_ATK1: + if (!sd->state.lr_flag) + sd->watk += val; + else if (sd->state.lr_flag == 1) + sd->watk_ += val; + break; + case SP_ATK2: + if (!sd->state.lr_flag) + sd->watk2 += val; + else if (sd->state.lr_flag == 1) + sd->watk_2 += val; + break; + case SP_BASE_ATK: + if (sd->state.lr_flag != 2) + sd->base_atk += val; + break; + case SP_MATK1: + if (sd->state.lr_flag != 2) + sd->matk1 += val; + break; + case SP_MATK2: + if (sd->state.lr_flag != 2) + sd->matk2 += val; + break; + case SP_MATK: + if (sd->state.lr_flag != 2) + { + sd->matk1 += val; + sd->matk2 += val; + } + break; + case SP_DEF1: + if (sd->state.lr_flag != 2) + sd->def += val; + break; + case SP_MDEF1: + if (sd->state.lr_flag != 2) + sd->mdef += val; + break; + case SP_MDEF2: + if (sd->state.lr_flag != 2) + sd->mdef += val; + break; + case SP_HIT: + if (sd->state.lr_flag != 2) + sd->hit += val; + else + sd->arrow_hit += val; + break; + case SP_FLEE1: + if (sd->state.lr_flag != 2) + sd->flee += val; + break; + case SP_FLEE2: + if (sd->state.lr_flag != 2) + sd->flee2 += val * 10; + break; + case SP_CRITICAL: + if (sd->state.lr_flag != 2) + sd->critical += val * 10; + else + sd->arrow_cri += val * 10; + break; + case SP_ATKELE: + if (!sd->state.lr_flag) + sd->atk_ele = val; + else if (sd->state.lr_flag == 1) + sd->atk_ele_ = val; + else if (sd->state.lr_flag == 2) + sd->arrow_ele = val; + break; + case SP_DEFELE: + if (sd->state.lr_flag != 2) + sd->def_ele = val; + break; + case SP_MAXHP: + if (sd->state.lr_flag != 2) + sd->status.max_hp += val; + break; + case SP_MAXSP: + if (sd->state.lr_flag != 2) + sd->status.max_sp += val; + break; + case SP_CASTRATE: + if (sd->state.lr_flag != 2) + sd->castrate += val; + break; + case SP_MAXHPRATE: + if (sd->state.lr_flag != 2) + sd->hprate += val; + break; + case SP_MAXSPRATE: + if (sd->state.lr_flag != 2) + sd->sprate += val; + break; + case SP_SPRATE: + if (sd->state.lr_flag != 2) + sd->dsprate += val; + break; + case SP_ATTACKRANGE: + if (!sd->state.lr_flag) + sd->attackrange += val; + else if (sd->state.lr_flag == 1) + sd->attackrange_ += val; + else if (sd->state.lr_flag == 2) + sd->arrow_range += val; + break; + case SP_ADD_SPEED: + if (sd->state.lr_flag != 2) + sd->speed -= val; + break; + case SP_SPEED_RATE: + if (sd->state.lr_flag != 2) + { + if (sd->speed_rate > 100 - val) + sd->speed_rate = 100 - val; + } + break; + case SP_SPEED_ADDRATE: + if (sd->state.lr_flag != 2) + sd->speed_add_rate = sd->speed_add_rate * (100 - val) / 100; + break; + case SP_ASPD: + if (sd->state.lr_flag != 2) + sd->aspd -= val * 10; + break; + case SP_ASPD_RATE: + if (sd->state.lr_flag != 2) + { + if (sd->aspd_rate > 100 - val) + sd->aspd_rate = 100 - val; + } + break; + case SP_ASPD_ADDRATE: + if (sd->state.lr_flag != 2) + sd->aspd_add_rate = sd->aspd_add_rate * (100 - val) / 100; + break; + case SP_HP_RECOV_RATE: + if (sd->state.lr_flag != 2) + sd->hprecov_rate += val; + break; + case SP_SP_RECOV_RATE: + if (sd->state.lr_flag != 2) + sd->sprecov_rate += val; + break; + case SP_CRITICAL_DEF: + if (sd->state.lr_flag != 2) + sd->critical_def += val; + break; + case SP_NEAR_ATK_DEF: + if (sd->state.lr_flag != 2) + sd->near_attack_def_rate += val; + break; + case SP_LONG_ATK_DEF: + if (sd->state.lr_flag != 2) + sd->long_attack_def_rate += val; + break; + case SP_DOUBLE_RATE: + if (sd->state.lr_flag == 0 && sd->double_rate < val) + sd->double_rate = val; + break; + case SP_DOUBLE_ADD_RATE: + if (sd->state.lr_flag == 0) + sd->double_add_rate += val; + break; + case SP_MATK_RATE: + if (sd->state.lr_flag != 2) + sd->matk_rate += val; + break; + case SP_IGNORE_DEF_ELE: + if (!sd->state.lr_flag) + sd->ignore_def_ele |= 1 << val; + else if (sd->state.lr_flag == 1) + sd->ignore_def_ele_ |= 1 << val; + break; + case SP_IGNORE_DEF_RACE: + if (!sd->state.lr_flag) + sd->ignore_def_race |= 1 << val; + else if (sd->state.lr_flag == 1) + sd->ignore_def_race_ |= 1 << val; + break; + case SP_ATK_RATE: + if (sd->state.lr_flag != 2) + sd->atk_rate += val; + break; + case SP_MAGIC_ATK_DEF: + if (sd->state.lr_flag != 2) + sd->magic_def_rate += val; + break; + case SP_MISC_ATK_DEF: + if (sd->state.lr_flag != 2) + sd->misc_def_rate += val; + break; + case SP_IGNORE_MDEF_ELE: + if (sd->state.lr_flag != 2) + sd->ignore_mdef_ele |= 1 << val; + break; + case SP_IGNORE_MDEF_RACE: + if (sd->state.lr_flag != 2) + sd->ignore_mdef_race |= 1 << val; + break; + case SP_PERFECT_HIT_RATE: + if (sd->state.lr_flag != 2 && sd->perfect_hit < val) + sd->perfect_hit = val; + break; + case SP_PERFECT_HIT_ADD_RATE: + if (sd->state.lr_flag != 2) + sd->perfect_hit_add += val; + break; + case SP_CRITICAL_RATE: + if (sd->state.lr_flag != 2) + sd->critical_rate += val; + break; + case SP_GET_ZENY_NUM: + if (sd->state.lr_flag != 2 && sd->get_zeny_num < val) + sd->get_zeny_num = val; + break; + case SP_ADD_GET_ZENY_NUM: + if (sd->state.lr_flag != 2) + sd->get_zeny_add_num += val; + break; + case SP_DEF_RATIO_ATK_ELE: + if (!sd->state.lr_flag) + sd->def_ratio_atk_ele |= 1 << val; + else if (sd->state.lr_flag == 1) + sd->def_ratio_atk_ele_ |= 1 << val; + break; + case SP_DEF_RATIO_ATK_RACE: + if (!sd->state.lr_flag) + sd->def_ratio_atk_race |= 1 << val; + else if (sd->state.lr_flag == 1) + sd->def_ratio_atk_race_ |= 1 << val; + break; + case SP_HIT_RATE: + if (sd->state.lr_flag != 2) + sd->hit_rate += val; + break; + case SP_FLEE_RATE: + if (sd->state.lr_flag != 2) + sd->flee_rate += val; + break; + case SP_FLEE2_RATE: + if (sd->state.lr_flag != 2) + sd->flee2_rate += val; + break; + case SP_DEF_RATE: + if (sd->state.lr_flag != 2) + sd->def_rate += val; + break; + case SP_DEF2_RATE: + if (sd->state.lr_flag != 2) + sd->def2_rate += val; + break; + case SP_MDEF_RATE: + if (sd->state.lr_flag != 2) + sd->mdef_rate += val; + break; + case SP_MDEF2_RATE: + if (sd->state.lr_flag != 2) + sd->mdef2_rate += val; + break; + case SP_RESTART_FULL_RECORVER: + if (sd->state.lr_flag != 2) + sd->special_state.restart_full_recover = 1; + break; + case SP_NO_CASTCANCEL: + if (sd->state.lr_flag != 2) + sd->special_state.no_castcancel = 1; + break; + case SP_NO_CASTCANCEL2: + if (sd->state.lr_flag != 2) + sd->special_state.no_castcancel2 = 1; + break; + case SP_NO_SIZEFIX: + if (sd->state.lr_flag != 2) + sd->special_state.no_sizefix = 1; + break; + case SP_NO_MAGIC_DAMAGE: + if (sd->state.lr_flag != 2) + sd->special_state.no_magic_damage = 1; + break; + case SP_NO_WEAPON_DAMAGE: + if (sd->state.lr_flag != 2) + sd->special_state.no_weapon_damage = 1; + break; + case SP_NO_GEMSTONE: + if (sd->state.lr_flag != 2) + sd->special_state.no_gemstone = 1; + break; + case SP_INFINITE_ENDURE: + if (sd->state.lr_flag != 2) + sd->special_state.infinite_endure = 1; + break; + case SP_SPLASH_RANGE: + if (sd->state.lr_flag != 2 && sd->splash_range < val) + sd->splash_range = val; + break; + case SP_SPLASH_ADD_RANGE: + if (sd->state.lr_flag != 2) + sd->splash_add_range += val; + break; + case SP_SHORT_WEAPON_DAMAGE_RETURN: + if (sd->state.lr_flag != 2) + sd->short_weapon_damage_return += val; + break; + case SP_LONG_WEAPON_DAMAGE_RETURN: + if (sd->state.lr_flag != 2) + sd->long_weapon_damage_return += val; + break; + case SP_MAGIC_DAMAGE_RETURN: //AppleGirl Was Here + if (sd->state.lr_flag != 2) + sd->magic_damage_return += val; + break; + case SP_ALL_STATS: // [Valaris] + if (sd->state.lr_flag != 2) + { + sd->parame[SP_STR - SP_STR] += val; + sd->parame[SP_AGI - SP_STR] += val; + sd->parame[SP_VIT - SP_STR] += val; + sd->parame[SP_INT - SP_STR] += val; + sd->parame[SP_DEX - SP_STR] += val; + sd->parame[SP_LUK - SP_STR] += val; + clif_updatestatus (sd, 13); + clif_updatestatus (sd, 14); + clif_updatestatus (sd, 15); + clif_updatestatus (sd, 16); + clif_updatestatus (sd, 17); + clif_updatestatus (sd, 18); + } + break; + case SP_AGI_VIT: // [Valaris] + if (sd->state.lr_flag != 2) + { + sd->parame[SP_AGI - SP_STR] += val; + sd->parame[SP_VIT - SP_STR] += val; + clif_updatestatus (sd, 14); + clif_updatestatus (sd, 15); + } + break; + case SP_AGI_DEX_STR: // [Valaris] + if (sd->state.lr_flag != 2) + { + sd->parame[SP_AGI - SP_STR] += val; + sd->parame[SP_DEX - SP_STR] += val; + sd->parame[SP_STR - SP_STR] += val; + clif_updatestatus (sd, 14); + clif_updatestatus (sd, 17); + clif_updatestatus (sd, 13); + } + break; + case SP_PERFECT_HIDE: // [Valaris] + if (sd->state.lr_flag != 2) + { + sd->perfect_hiding = 1; + } + break; + case SP_DISGUISE: // Disguise script for items [Valaris] + if (sd->state.lr_flag != 2 && sd->disguiseflag == 0) + { + if (pc_isriding (sd)) + { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage (sd->fd, + "Cannot wear disguise when riding a Peco."); + break; + } + sd->disguise = val; + clif_clearchar (&sd->bl, 9); + pc_setpos (sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + break; + case SP_UNBREAKABLE: + if (sd->state.lr_flag != 2) + { + sd->unbreakable += val; + } + break; + case SP_DEAF: + sd->special_state.deaf = 1; + break; + default: + if (battle_config.error_log) + printf ("pc_bonus: unknown type %d %d !\n", type, val); + break; + } + return 0; +} + +/*========================================== + * ソスソス ソスソスソスiソスノゑソスソスソスソス\ソスヘ難ソスソスフボソス[ソスiソスXソスン抵ソス + *------------------------------------------ + */ +int pc_bonus2 (struct map_session_data *sd, int type, int type2, int val) +{ + int i; + + nullpo_retr (0, sd); + + switch (type) + { + case SP_ADDELE: + if (!sd->state.lr_flag) + sd->addele[type2] += val; + else if (sd->state.lr_flag == 1) + sd->addele_[type2] += val; + else if (sd->state.lr_flag == 2) + sd->arrow_addele[type2] += val; + break; + case SP_ADDRACE: + if (!sd->state.lr_flag) + sd->addrace[type2] += val; + else if (sd->state.lr_flag == 1) + sd->addrace_[type2] += val; + else if (sd->state.lr_flag == 2) + sd->arrow_addrace[type2] += val; + break; + case SP_ADDSIZE: + if (!sd->state.lr_flag) + sd->addsize[type2] += val; + else if (sd->state.lr_flag == 1) + sd->addsize_[type2] += val; + else if (sd->state.lr_flag == 2) + sd->arrow_addsize[type2] += val; + break; + case SP_SUBELE: + if (sd->state.lr_flag != 2) + sd->subele[type2] += val; + break; + case SP_SUBRACE: + if (sd->state.lr_flag != 2) + sd->subrace[type2] += val; + break; + case SP_ADDEFF: + if (sd->state.lr_flag != 2) + sd->addeff[type2] += val; + else + sd->arrow_addeff[type2] += val; + break; + case SP_ADDEFF2: + if (sd->state.lr_flag != 2) + sd->addeff2[type2] += val; + else + sd->arrow_addeff2[type2] += val; + break; + case SP_RESEFF: + if (sd->state.lr_flag != 2) + sd->reseff[type2] += val; + break; + case SP_MAGIC_ADDELE: + if (sd->state.lr_flag != 2) + sd->magic_addele[type2] += val; + break; + case SP_MAGIC_ADDRACE: + if (sd->state.lr_flag != 2) + sd->magic_addrace[type2] += val; + break; + case SP_MAGIC_SUBRACE: + if (sd->state.lr_flag != 2) + sd->magic_subrace[type2] += val; + break; + case SP_ADD_DAMAGE_CLASS: + if (!sd->state.lr_flag) + { + for (i = 0; i < sd->add_damage_class_count; i++) + { + if (sd->add_damage_classid[i] == type2) + { + sd->add_damage_classrate[i] += val; + break; + } + } + if (i >= sd->add_damage_class_count + && sd->add_damage_class_count < 10) + { + sd->add_damage_classid[sd->add_damage_class_count] = + type2; + sd->add_damage_classrate[sd->add_damage_class_count] += + val; + sd->add_damage_class_count++; + } + } + else if (sd->state.lr_flag == 1) + { + for (i = 0; i < sd->add_damage_class_count_; i++) + { + if (sd->add_damage_classid_[i] == type2) + { + sd->add_damage_classrate_[i] += val; + break; + } + } + if (i >= sd->add_damage_class_count_ + && sd->add_damage_class_count_ < 10) + { + sd->add_damage_classid_[sd->add_damage_class_count_] = + type2; + sd->add_damage_classrate_[sd->add_damage_class_count_] += + val; + sd->add_damage_class_count_++; + } + } + break; + case SP_ADD_MAGIC_DAMAGE_CLASS: + if (sd->state.lr_flag != 2) + { + for (i = 0; i < sd->add_magic_damage_class_count; i++) + { + if (sd->add_magic_damage_classid[i] == type2) + { + sd->add_magic_damage_classrate[i] += val; + break; + } + } + if (i >= sd->add_magic_damage_class_count + && sd->add_magic_damage_class_count < 10) + { + sd->add_magic_damage_classid + [sd->add_magic_damage_class_count] = type2; + sd->add_magic_damage_classrate + [sd->add_magic_damage_class_count] += val; + sd->add_magic_damage_class_count++; + } + } + break; + case SP_ADD_DEF_CLASS: + if (sd->state.lr_flag != 2) + { + for (i = 0; i < sd->add_def_class_count; i++) + { + if (sd->add_def_classid[i] == type2) + { + sd->add_def_classrate[i] += val; + break; + } + } + if (i >= sd->add_def_class_count + && sd->add_def_class_count < 10) + { + sd->add_def_classid[sd->add_def_class_count] = type2; + sd->add_def_classrate[sd->add_def_class_count] += val; + sd->add_def_class_count++; + } + } + break; + case SP_ADD_MDEF_CLASS: + if (sd->state.lr_flag != 2) + { + for (i = 0; i < sd->add_mdef_class_count; i++) + { + if (sd->add_mdef_classid[i] == type2) + { + sd->add_mdef_classrate[i] += val; + break; + } + } + if (i >= sd->add_mdef_class_count + && sd->add_mdef_class_count < 10) + { + sd->add_mdef_classid[sd->add_mdef_class_count] = type2; + sd->add_mdef_classrate[sd->add_mdef_class_count] += val; + sd->add_mdef_class_count++; + } + } + break; + case SP_HP_DRAIN_RATE: + if (!sd->state.lr_flag) + { + sd->hp_drain_rate += type2; + sd->hp_drain_per += val; + } + else if (sd->state.lr_flag == 1) + { + sd->hp_drain_rate_ += type2; + sd->hp_drain_per_ += val; + } + break; + case SP_SP_DRAIN_RATE: + if (!sd->state.lr_flag) + { + sd->sp_drain_rate += type2; + sd->sp_drain_per += val; + } + else if (sd->state.lr_flag == 1) + { + sd->sp_drain_rate_ += type2; + sd->sp_drain_per_ += val; + } + break; + case SP_WEAPON_COMA_ELE: + if (sd->state.lr_flag != 2) + sd->weapon_coma_ele[type2] += val; + break; + case SP_WEAPON_COMA_RACE: + if (sd->state.lr_flag != 2) + sd->weapon_coma_race[type2] += val; + break; + case SP_RANDOM_ATTACK_INCREASE: // [Valaris] + if (sd->state.lr_flag != 2) + { + sd->random_attack_increase_add = type2; + sd->random_attack_increase_per += val; + break; + } // end addition + default: + if (battle_config.error_log) + printf ("pc_bonus2: unknown type %d %d %d!\n", type, type2, + val); + break; + } + return 0; +} + +int pc_bonus3 (struct map_session_data *sd, int type, int type2, int type3, + int val) +{ + int i; + switch (type) + { + case SP_ADD_MONSTER_DROP_ITEM: + if (sd->state.lr_flag != 2) + { + for (i = 0; i < sd->monster_drop_item_count; i++) + { + if (sd->monster_drop_itemid[i] == type2) + { + sd->monster_drop_race[i] |= 1 << type3; + if (sd->monster_drop_itemrate[i] < val) + sd->monster_drop_itemrate[i] = val; + break; + } + } + if (i >= sd->monster_drop_item_count + && sd->monster_drop_item_count < 10) + { + sd->monster_drop_itemid[sd->monster_drop_item_count] = + type2; + sd->monster_drop_race[sd->monster_drop_item_count] |= + 1 << type3; + sd->monster_drop_itemrate[sd->monster_drop_item_count] = + val; + sd->monster_drop_item_count++; + } + } + break; + case SP_AUTOSPELL: + if (sd->state.lr_flag != 2) + { + sd->autospell_id = type2; + sd->autospell_lv = type3; + sd->autospell_rate = val; + } + break; + default: + if (battle_config.error_log) + printf ("pc_bonus3: unknown type %d %d %d %d!\n", type, type2, + type3, val); + break; + } + + return 0; +} + +/*========================================== + * スクリプトによるスキル所得 + *------------------------------------------ + */ +int pc_skill (struct map_session_data *sd, int id, int level, int flag) +{ + nullpo_retr (0, sd); + + if (level > MAX_SKILL_LEVEL) + { + if (battle_config.error_log) + printf ("support card skill only!\n"); + return 0; + } + if (!flag && (sd->status.skill[id].id == id || level == 0)) + { // クエスト所得ならここで条件を確認して送信する + sd->status.skill[id].lv = level; + pc_calcstatus (sd, 0); + clif_skillinfoblock (sd); + } + else if (sd->status.skill[id].lv < level) + { // 覚えられるがlvが小さいなら + sd->status.skill[id].id = id; + sd->status.skill[id].lv = level; + } + + return 0; +} + +/*========================================== + * カード挿入 + *------------------------------------------ + */ +int pc_insert_card (struct map_session_data *sd, int idx_card, int idx_equip) +{ + nullpo_retr (0, sd); + + if (idx_card >= 0 && idx_card < MAX_INVENTORY && idx_equip >= 0 + && idx_equip < MAX_INVENTORY && sd->inventory_data[idx_card]) + { + int i; + int nameid = sd->status.inventory[idx_equip].nameid; + int cardid = sd->status.inventory[idx_card].nameid; + int ep = sd->inventory_data[idx_card]->equip; + + if (nameid <= 0 || sd->inventory_data[idx_equip] == NULL || + (sd->inventory_data[idx_equip]->type != 4 && sd->inventory_data[idx_equip]->type != 5) || // 装 備じゃない + (sd->status.inventory[idx_equip].identify == 0) || // 未鑑定 + (sd->status.inventory[idx_equip].card[0] == 0x00ff) || // 製造武器 + (sd->status.inventory[idx_equip].card[0] == 0x00fe) || + ((sd->inventory_data[idx_equip]->equip & ep) == 0) || // 装 備個所違い + (sd->inventory_data[idx_equip]->type == 4 && ep == 32) || // 両 手武器と盾カード + (sd->status.inventory[idx_equip].card[0] == (short) 0xff00) + || sd->status.inventory[idx_equip].equip) + { + + clif_insert_card (sd, idx_equip, idx_card, 1); + return 0; + } + for (i = 0; i < sd->inventory_data[idx_equip]->slot; i++) + { + if (sd->status.inventory[idx_equip].card[i] == 0) + { + // 空きスロットがあったので差し込む + sd->status.inventory[idx_equip].card[i] = cardid; + + // カードは減らす + clif_insert_card (sd, idx_equip, idx_card, 0); + pc_delitem (sd, idx_card, 1, 1); + return 0; + } + } + } + else + clif_insert_card (sd, idx_equip, idx_card, 1); + + return 0; +} + +// +// アイテム物 +// + +/*========================================== + * スキルによる買い値修正 + *------------------------------------------ + */ +int pc_modifybuyvalue (struct map_session_data *sd, int orig_value) +{ + int skill, val = orig_value, rate1 = 0, rate2 = 0; + if ((skill = pc_checkskill (sd, MC_DISCOUNT)) > 0) // ディスカウント + rate1 = 5 + skill * 2 - ((skill == 10) ? 1 : 0); + if ((skill = pc_checkskill (sd, RG_COMPULSION)) > 0) // コムパルションディスカウント + rate2 = 5 + skill * 4; + if (rate1 < rate2) + rate1 = rate2; + if (rate1) + val = (int) ((double) orig_value * (double) (100 - rate1) / 100.); + if (val < 0) + val = 0; + if (orig_value > 0 && val < 1) + val = 1; + + return val; +} + +/*========================================== + * スキルによる売り値修正 + *------------------------------------------ + */ +int pc_modifysellvalue (struct map_session_data *sd, int orig_value) +{ + int skill, val = orig_value, rate = 0; + if ((skill = pc_checkskill (sd, MC_OVERCHARGE)) > 0) // オーバーチャージ + rate = 5 + skill * 2 - ((skill == 10) ? 1 : 0); + if (rate) + val = (int) ((double) orig_value * (double) (100 + rate) / 100.); + if (val < 0) + val = 0; + if (orig_value > 0 && val < 1) + val = 1; + + return val; +} + +/*========================================== + * アイテムを買った時に、新しいアイテム欄を使うか、 + * 3万個制限にかかるか確認 + *------------------------------------------ + */ +int pc_checkadditem (struct map_session_data *sd, int nameid, int amount) +{ + int i; + + nullpo_retr (0, sd); + + if (itemdb_isequip (nameid)) + return ADDITEM_NEW; + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid == nameid) + { + if (sd->status.inventory[i].amount + amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_EXIST; + } + } + + if (amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_NEW; +} + +/*========================================== + * 空きアイテム欄の個数 + *------------------------------------------ + */ +int pc_inventoryblank (struct map_session_data *sd) +{ + int i, b; + + nullpo_retr (0, sd); + + for (i = 0, b = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid == 0) + b++; + } + + return b; +} + +/*========================================== + * お金を払う + *------------------------------------------ + */ +int pc_payzeny (struct map_session_data *sd, int zeny) +{ + double z; + + nullpo_retr (0, sd); + + z = (double) sd->status.zeny; + if (sd->status.zeny < zeny || z - (double) zeny > MAX_ZENY) + return 1; + sd->status.zeny -= zeny; + clif_updatestatus (sd, SP_ZENY); + + return 0; +} + +/*========================================== + * お金を得る + *------------------------------------------ + */ +int pc_getzeny (struct map_session_data *sd, int zeny) +{ + double z; + + nullpo_retr (0, sd); + + z = (double) sd->status.zeny; + if (z + (double) zeny > MAX_ZENY) + { + zeny = 0; + sd->status.zeny = MAX_ZENY; + } + sd->status.zeny += zeny; + clif_updatestatus (sd, SP_ZENY); + + return 0; +} + +/*========================================== + * アイテムを探して、インデックスを返す + *------------------------------------------ + */ +int pc_search_inventory (struct map_session_data *sd, int item_id) +{ + int i; + + nullpo_retr (-1, sd); + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid == item_id && + (sd->status.inventory[i].amount > 0 || item_id == 0)) + return i; + } + + return -1; +} + +int pc_count_all_items (struct map_session_data *player, int item_id) +{ + int i; + int count = 0; + + nullpo_retr (0, player); + + for (i = 0; i < MAX_INVENTORY; i++) + { + if (player->status.inventory[i].nameid == item_id) + count += player->status.inventory[i].amount; + } + + return count; +} + +int pc_remove_items (struct map_session_data *player, int item_id, int count) +{ + int i; + + nullpo_retr (0, player); + + for (i = 0; i < MAX_INVENTORY && count; i++) + { + if (player->status.inventory[i].nameid == item_id) + { + int to_delete = count; + /* only delete as much as we have */ + if (to_delete > player->status.inventory[i].amount) + to_delete = player->status.inventory[i].amount; + + count -= to_delete; + + pc_delitem (player, i, to_delete, + 0 /* means `really delete and update status' */ ); + + if (!count) + return 0; + } + } + return 0; +} + +/*========================================== + * アイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_additem (struct map_session_data *sd, struct item *item_data, + int amount) +{ + struct item_data *data; + int i, w; + + MAP_LOG_PC (sd, "PICKUP %d %d", item_data->nameid, amount); + + nullpo_retr (1, sd); + nullpo_retr (1, item_data); + + if (item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search (item_data->nameid); + if ((w = data->weight * amount) + sd->weight > sd->max_weight) + return 2; + + i = MAX_INVENTORY; + + if (!itemdb_isequip2 (data)) + { + // 装 備品ではないので、既所有品なら個数のみ変化させる + for (i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid == item_data->nameid && + sd->status.inventory[i].card[0] == item_data->card[0] + && sd->status.inventory[i].card[1] == item_data->card[1] + && sd->status.inventory[i].card[2] == item_data->card[2] + && sd->status.inventory[i].card[3] == item_data->card[3]) + { + if (sd->status.inventory[i].amount + amount > MAX_AMOUNT) + return 5; + sd->status.inventory[i].amount += amount; + clif_additem (sd, i, amount, 0); + break; + } + } + if (i >= MAX_INVENTORY) + { + // 装 備品か未所有品だったので空き欄へ追加 + i = pc_search_inventory (sd, 0); + if (i >= 0) + { + memcpy (&sd->status.inventory[i], item_data, + sizeof (sd->status.inventory[0])); + + if (item_data->equip) + sd->status.inventory[i].equip = 0; + + sd->status.inventory[i].amount = amount; + sd->inventory_data[i] = data; + clif_additem (sd, i, amount, 0); + } + else + return 4; + } + sd->weight += w; + clif_updatestatus (sd, SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを減らす + *------------------------------------------ + */ +int pc_delitem (struct map_session_data *sd, int n, int amount, int type) +{ + nullpo_retr (1, sd); + + if (sd->trade_partner != 0) + trade_tradecancel (sd); + + if (sd->status.inventory[n].nameid == 0 || amount <= 0 + || sd->status.inventory[n].amount < amount + || sd->inventory_data[n] == NULL) + return 1; + + sd->status.inventory[n].amount -= amount; + sd->weight -= sd->inventory_data[n]->weight * amount; + if (sd->status.inventory[n].amount <= 0) + { + if (sd->status.inventory[n].equip) + pc_unequipitem (sd, n, 0); + memset (&sd->status.inventory[n], 0, + sizeof (sd->status.inventory[0])); + sd->inventory_data[n] = NULL; + } + if (!(type & 1)) + clif_delitem (sd, n, amount); + if (!(type & 2)) + clif_updatestatus (sd, SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを落す + *------------------------------------------ + */ +int pc_dropitem (struct map_session_data *sd, int n, int amount) +{ + nullpo_retr (1, sd); + + if (sd->trade_partner != 0 || sd->npc_id != 0 || sd->state.storage_flag) + return 0; // no dropping while trading/npc/storage + + if (n < 0 || n >= MAX_INVENTORY) + return 0; + + if (amount <= 0) + return 0; + + pc_unequipinvyitem (sd, n, 0); + + if (sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount < amount || + sd->trade_partner != 0 || sd->status.inventory[n].amount <= 0) + return 1; + map_addflooritem (&sd->status.inventory[n], amount, sd->bl.m, sd->bl.x, + sd->bl.y, NULL, NULL, NULL, 0); + pc_delitem (sd, n, amount, 0); + + return 0; +} + +/*========================================== + * アイテムを拾う + *------------------------------------------ + */ + +static int can_pick_item_up_from (struct map_session_data *self, int other_id) +{ + struct party *p = party_search (self->status.party_id); + + /* From ourselves or from no-one? */ + if (!self || self->bl.id == other_id || !other_id) + return 1; + + struct map_session_data *other = map_id2sd (other_id); + + /* Other no longer exists? */ + if (!other) + return 1; + + /* From our partner? */ + if (self->status.partner_id == other->status.char_id) + return 1; + + /* From a party member? */ + if (self->status.party_id + && self->status.party_id == other->status.party_id + && p && p->item != 0) + return 1; + + /* From someone who is far away? */ + /* On another map? */ + if (other->bl.m != self->bl.m) + return 1; + else + { + int distance_x = abs (other->bl.x - self->bl.x); + int distance_y = abs (other->bl.y - self->bl.y); + int distance = (distance_x > distance_y) ? distance_x : distance_y; + + return distance > battle_config.drop_pickup_safety_zone; + } +} + +int pc_takeitem (struct map_session_data *sd, struct flooritem_data *fitem) +{ + int flag; + unsigned int tick = gettick (); + int can_take; + + nullpo_retr (0, sd); + nullpo_retr (0, fitem); + + /* Sometimes the owners reported to us are buggy: */ + + if (fitem->first_get_id == fitem->third_get_id + || fitem->second_get_id == fitem->third_get_id) + fitem->third_get_id = 0; + + if (fitem->first_get_id == fitem->second_get_id) + { + fitem->second_get_id = fitem->third_get_id; + fitem->third_get_id = 0; + } + + can_take = can_pick_item_up_from (sd, fitem->first_get_id); + if (!can_take) + can_take = fitem->first_get_tick <= tick + && can_pick_item_up_from (sd, fitem->second_get_id); + + if (!can_take) + can_take = fitem->second_get_tick <= tick + && can_pick_item_up_from (sd, fitem->third_get_id); + + if (!can_take) + can_take = fitem->third_get_tick <= tick; + + if (can_take) + { + /* Can pick up */ + + if ((flag = + pc_additem (sd, &fitem->item_data, fitem->item_data.amount))) + // 重量overで取得失敗 + clif_additem (sd, 0, 0, flag); + else + { + // 取得成功 + if (sd->attacktimer != -1) + pc_stopattack (sd); + clif_takeitem (&sd->bl, &fitem->bl); + map_clearflooritem (fitem->bl.id); + } + return 0; + } + + /* Otherwise, we can't pick up */ + clif_additem (sd, 0, 0, 6); + return 0; +} + +static +int pc_isUseitem (struct map_session_data *sd, int n) +{ + struct item_data *item; + int nameid; + + nullpo_retr (0, sd); + + item = sd->inventory_data[n]; + nameid = sd->status.inventory[n].nameid; + + if (item == NULL) + return 0; + if (itemdb_type (nameid) != 0) + return 0; + if ((nameid == 605) && map[sd->bl.m].flag.gvg) + return 0; + if (nameid == 601 + && (map[sd->bl.m].flag.noteleport || map[sd->bl.m].flag.gvg)) + { + clif_skill_teleportmessage (sd, 0); + return 0; + } + + if (nameid == 602 && map[sd->bl.m].flag.noreturn) + return 0; + if (nameid == 604 + && (map[sd->bl.m].flag.nobranch || map[sd->bl.m].flag.gvg)) + return 0; + if (item->sex != 2 && sd->status.sex != item->sex) + return 0; + if (item->elv > 0 && sd->status.base_level < item->elv) + return 0; + + return 1; +} + +/*========================================== + * アイテムを使う + *------------------------------------------ + */ +int pc_useitem (struct map_session_data *sd, int n) +{ + int nameid, amount; + + nullpo_retr (1, sd); + + if (n >= 0 && n < MAX_INVENTORY && sd->inventory_data[n]) + { + nameid = sd->status.inventory[n].nameid; + amount = sd->status.inventory[n].amount; + if (sd->status.inventory[n].nameid <= 0 + || sd->status.inventory[n].amount <= 0 + || sd->sc_data[SC_BERSERK].timer != -1 || !pc_isUseitem (sd, n)) + { + clif_useitemack (sd, n, 0, 0); + return 1; + } + + run_script (sd->inventory_data[n]->use_script, 0, sd->bl.id, 0); + + clif_useitemack (sd, n, amount - 1, 1); + pc_delitem (sd, n, 1, 1); + } + + return 0; +} + +/*========================================== + * カートアイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_cart_additem (struct map_session_data *sd, struct item *item_data, + int amount) +{ + struct item_data *data; + int i, w; + + nullpo_retr (1, sd); + nullpo_retr (1, item_data); + + if (item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search (item_data->nameid); + + if ((w = data->weight * amount) + sd->cart_weight > sd->cart_max_weight) + return 1; + + i = MAX_CART; + if (!itemdb_isequip2 (data)) + { + // 装 備品ではないので、既所有品なら個数のみ変化させる + for (i = 0; i < MAX_CART; i++) + { + if (sd->status.cart[i].nameid == item_data->nameid && + sd->status.cart[i].card[0] == item_data->card[0] + && sd->status.cart[i].card[1] == item_data->card[1] + && sd->status.cart[i].card[2] == item_data->card[2] + && sd->status.cart[i].card[3] == item_data->card[3]) + { + if (sd->status.cart[i].amount + amount > MAX_AMOUNT) + return 1; + sd->status.cart[i].amount += amount; + clif_cart_additem (sd, i, amount, 0); + break; + } + } + } + if (i >= MAX_CART) + { + // 装 備品か未所有品だったので空き欄へ追加 + for (i = 0; i < MAX_CART; i++) + { + if (sd->status.cart[i].nameid == 0) + { + memcpy (&sd->status.cart[i], item_data, + sizeof (sd->status.cart[0])); + sd->status.cart[i].amount = amount; + sd->cart_num++; + clif_cart_additem (sd, i, amount, 0); + break; + } + } + if (i >= MAX_CART) + return 1; + } + sd->cart_weight += w; + clif_updatestatus (sd, SP_CARTINFO); + + return 0; +} + +/*========================================== + * カートアイテムを減らす + *------------------------------------------ + */ +int pc_cart_delitem (struct map_session_data *sd, int n, int amount, int type) +{ + nullpo_retr (1, sd); + + if (sd->status.cart[n].nameid == 0 || sd->status.cart[n].amount < amount) + return 1; + + sd->status.cart[n].amount -= amount; + sd->cart_weight -= itemdb_weight (sd->status.cart[n].nameid) * amount; + if (sd->status.cart[n].amount <= 0) + { + memset (&sd->status.cart[n], 0, sizeof (sd->status.cart[0])); + sd->cart_num--; + } + if (!type) + { + clif_cart_delitem (sd, n, amount); + clif_updatestatus (sd, SP_CARTINFO); + } + + return 0; +} + +/*========================================== + * カートへアイテム移動 + *------------------------------------------ + */ +int pc_putitemtocart (struct map_session_data *sd, int idx, int amount) +{ + struct item *item_data; + + nullpo_retr (0, sd); + + if (idx < 0 || idx >= MAX_INVENTORY) + return 1; + + nullpo_retr (0, item_data = &sd->status.inventory[idx]); + + if (!pc_iscarton (sd)) + return 1; + if (item_data->nameid == 0 || item_data->amount < amount) + return 1; + if (pc_cart_additem (sd, item_data, amount) == 0) + return pc_delitem (sd, idx, amount, 0); + + return 1; +} + +/*========================================== + * カート内のアイテム数確認(個数の差分を返す) + *------------------------------------------ + */ +int pc_cartitem_amount (struct map_session_data *sd, int idx, int amount) +{ + struct item *item_data; + + nullpo_retr (-1, sd); + + if (idx < 0 || idx >= MAX_CART) + return -1; + + nullpo_retr (-1, item_data = &sd->status.cart[idx]); + + if (!pc_iscarton (sd)) + return -1; + if (item_data->nameid == 0 || !item_data->amount) + return -1; + return item_data->amount - amount; +} + +/*========================================== + * カートからアイテム移動 + *------------------------------------------ + */ + +int pc_getitemfromcart (struct map_session_data *sd, int idx, int amount) +{ + struct item *item_data; + int flag; + + nullpo_retr (0, sd); + + if (idx < 0 || idx >= MAX_CART) + return 1; + + nullpo_retr (0, item_data = &sd->status.cart[idx]); + + if (!pc_iscarton (sd)) + return 1; + if (item_data->nameid == 0 || item_data->amount < amount) + return 1; + if ((flag = pc_additem (sd, item_data, amount)) == 0) + return pc_cart_delitem (sd, idx, amount, 0); + + clif_additem (sd, 0, 0, flag); + return 1; +} + +/*========================================== + * アイテム鑑定 + *------------------------------------------ + */ +int pc_item_identify (struct map_session_data *sd, int idx) +{ + int flag = 1; + + nullpo_retr (0, sd); + + if (idx >= 0 && idx < MAX_INVENTORY) + { + if (sd->status.inventory[idx].nameid > 0 + && sd->status.inventory[idx].identify == 0) + { + flag = 0; + sd->status.inventory[idx].identify = 1; + } + clif_item_identified (sd, idx, flag); + } + else + clif_item_identified (sd, idx, flag); + + return !flag; +} + +/*========================================== + * スティル品公開 + *------------------------------------------ + */ +static +int pc_show_steal (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd; + int itemid; + int type; + + struct item_data *item = NULL; + char output[100]; + + nullpo_retr (0, bl); + nullpo_retr (0, ap); + nullpo_retr (0, sd = va_arg (ap, struct map_session_data *)); + + itemid = va_arg (ap, int); + type = va_arg (ap, int); + + if (!type) + { + if ((item = itemdb_exists (itemid)) == NULL) + sprintf (output, "%s stole an Unknown_Item.", sd->status.name); + else + sprintf (output, "%s stole %s.", sd->status.name, item->jname); + clif_displaymessage (((struct map_session_data *) bl)->fd, output); + } + else + { + sprintf (output, + "%s has not stolen the item because of being overweight.", + sd->status.name); + clif_displaymessage (((struct map_session_data *) bl)->fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** pc.c: Small Steal Item fix by fritz +int pc_steal_item (struct map_session_data *sd, struct block_list *bl) +{ + if (sd != NULL && bl != NULL && bl->type == BL_MOB) + { + int i, skill, rate, itemid, flag, count; + struct mob_data *md; + md = (struct mob_data *) bl; + if (!md->state.steal_flag && mob_db[md->mob_class].mexp <= 0 && + !(mob_db[md->mob_class].mode & 0x20) && + md->sc_data[SC_STONE].timer == -1 && + md->sc_data[SC_FREEZE].timer == -1 && + (!(md->mob_class > 1324 && md->mob_class < 1364))) // prevent stealing from treasure boxes [Valaris] + { + skill = sd->paramc[4] - mob_db[md->mob_class].dex + + pc_checkskill (sd, TF_STEAL) + 10; + + if (0 < skill) + { + for (count = 8; count <= 8 && count != 0; count--) + { + i = rand () % 8; + itemid = mob_db[md->mob_class].dropitem[i].nameid; + + if (itemid > 0 && itemdb_type (itemid) != 6) + { + rate = + (mob_db[md->mob_class].dropitem[i].p / + battle_config.item_rate_common * 100 * skill) / + 100; + + if (rand () % 10000 < rate) + { + struct item tmp_item; + memset (&tmp_item, 0, sizeof (tmp_item)); + tmp_item.nameid = itemid; + tmp_item.amount = 1; + tmp_item.identify = 1; + flag = pc_additem (sd, &tmp_item, 1); + if (battle_config.show_steal_in_same_party) + { + party_foreachsamemap (pc_show_steal, sd, 1, + sd, tmp_item.nameid, 0); + } + + if (flag) + { + if (battle_config.show_steal_in_same_party) + { + party_foreachsamemap (pc_show_steal, sd, + 1, sd, + tmp_item.nameid, 1); + } + + clif_additem (sd, 0, 0, flag); + } + md->state.steal_flag = 1; + return 1; + } + } + } + } + } + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_steal_coin (struct map_session_data *sd, struct block_list *bl) +{ + if (sd != NULL && bl != NULL && bl->type == BL_MOB) + { + int rate, skill; + struct mob_data *md = (struct mob_data *) bl; + if (md && !md->state.steal_coin_flag && md->sc_data + && md->sc_data[SC_STONE].timer == -1 + && md->sc_data[SC_FREEZE].timer == -1) + { + skill = pc_checkskill (sd, RG_STEALCOIN) * 10; + rate = + skill + (sd->status.base_level - mob_db[md->mob_class].lv) * 3 + + sd->paramc[4] * 2 + sd->paramc[5] * 2; + if (MRAND (1000) < rate) + { + pc_getzeny (sd, mob_db[md->mob_class].lv * 10 + MRAND (100)); + md->state.steal_coin_flag = 1; + return 1; + } + } + } + + return 0; +} + +// +// +// +/*========================================== + * PCの位置設定 + *------------------------------------------ + */ +int pc_setpos (struct map_session_data *sd, const char *mapname_org, int x, int y, + int clrtype) +{ + char mapname[24]; + int m = 0, c = 0, disguise = 0; + + nullpo_retr (0, sd); + + if (sd->chatID) // チャットから出る + chat_leavechat (sd); + if (sd->trade_partner) // 取引を中断する + trade_tradecancel (sd); + if (sd->state.storage_flag == 1) + storage_storage_quit (sd); // 倉庫を開いてるなら保存する + else if (sd->state.storage_flag == 2) + storage_guild_storage_quit (sd, 0); + + if (sd->party_invite > 0) // パーティ勧誘を拒否する + party_reply_invite (sd, sd->party_invite_account, 0); + if (sd->guild_invite > 0) // ギルド勧誘を拒否する + guild_reply_invite (sd, sd->guild_invite, 0); + if (sd->guild_alliance > 0) // ギルド同盟勧誘を拒否する + guild_reply_reqalliance (sd, sd->guild_alliance_account, 0); + + skill_castcancel (&sd->bl, 0); // 詠唱中断 + pc_stop_walking (sd, 0); // 歩行中断 + pc_stopattack (sd); // 攻撃中断 + + if (pc_issit (sd)) + { +// pc_setstand (sd); // [fate] Nothing wrong with warping while sitting + skill_gangsterparadise (sd, 0); + } + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end (&sd->bl, SC_TRICKDEAD, -1); + if (sd->status.option & 2) + skill_status_change_end (&sd->bl, SC_HIDING, -1); + if (sd->status.option & 4) + skill_status_change_end (&sd->bl, SC_CLOAKING, -1); + if (sd->status.option & 16386) + skill_status_change_end (&sd->bl, SC_CHASEWALK, -1); + if (sd->sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end (&sd->bl, SC_BLADESTOP, -1); + if (sd->sc_data[SC_DANCING].timer != -1) // clear dance effect when warping [Valaris] + skill_stop_dancing (&sd->bl, 0); + + if (sd->disguise) + { // clear disguises when warping [Valaris] + clif_clearchar (&sd->bl, 9); + disguise = sd->disguise; + sd->disguise = 0; + } + + memcpy (mapname, mapname_org, 24); + mapname[16] = 0; + if (strstr (mapname, ".gat") == NULL && strlen (mapname) < 16) + { + strcat (mapname, ".gat"); + } + + m = map_mapname2mapid (mapname); + if (m < 0) + { + if (sd->mapname[0]) + { + struct in_addr ip; + int port; + if (map_mapname2ipport (mapname, &ip, &port) == 0) + { + skill_stop_dancing (&sd->bl, 1); + skill_unit_out_all (&sd->bl, gettick (), 1); + clif_clearchar_area (&sd->bl, clrtype & 0xffff); + skill_gangsterparadise (sd, 0); + map_delblock (&sd->bl); + memcpy (sd->mapname, mapname, 24); + sd->bl.x = x; + sd->bl.y = y; + sd->state.waitingdisconnect = 1; + pc_makesavestatus (sd); + //The storage close routines save the char data. [Skotlex] + if (!sd->state.storage_flag) + chrif_save (sd); + else if (sd->state.storage_flag == 1) + storage_storage_quit (sd); + else if (sd->state.storage_flag == 2) + storage_guild_storage_quit (sd, 1); + + chrif_changemapserver (sd, mapname, x, y, ip, port); + return 0; + } + } +#if 0 + clif_authfail_fd (sd->fd, 0); // cancel + clif_setwaitclose (sd->fd); +#endif + return 1; + } + + if (x < 0 || x >= map[m].xs || y < 0 || y >= map[m].ys) + x = y = 0; + if ((x == 0 && y == 0) || (c = read_gat (m, x, y)) == 1 || c == 5) + { + if (x || y) + { + if (battle_config.error_log) + printf ("stacked (%d,%d)\n", x, y); + } + do + { + x = MRAND (map[m].xs - 2) + 1; + y = MRAND (map[m].ys - 2) + 1; + } + while ((c = read_gat (m, x, y)) == 1 || c == 5); + } + + if (sd->mapname[0] && sd->bl.prev != NULL) + { + skill_unit_out_all (&sd->bl, gettick (), 1); + clif_clearchar_area (&sd->bl, clrtype & 0xffff); + skill_gangsterparadise (sd, 0); + map_delblock (&sd->bl); + clif_changemap (sd, map[m].name, x, y); // [MouseJstr] + } + + if (disguise) // disguise teleport fix [Valaris] + sd->disguise = disguise; + + memcpy (sd->mapname, mapname, 24); + sd->bl.m = m; + sd->to_x = x; + sd->to_y = y; + + // moved and changed dance effect stopping + + sd->bl.x = x; + sd->bl.y = y; + +// map_addblock(&sd->bl); // ブロック登録とspawnは +// clif_spawnpc(sd); + + return 0; +} + +/*========================================== + * PCのランダムワープ + *------------------------------------------ + */ +int pc_randomwarp (struct map_session_data *sd, int type) +{ + int x, y, c, i = 0; + int m; + + nullpo_retr (0, sd); + + m = sd->bl.m; + + if (map[sd->bl.m].flag.noteleport) // テレポート禁止 + return 0; + + do + { + x = MRAND (map[m].xs - 2) + 1; + y = MRAND (map[m].ys - 2) + 1; + } + while (((c = read_gat (m, x, y)) == 1 || c == 5) && (i++) < 1000); + + if (i < 1000) + pc_setpos (sd, map[m].name, x, y, type); + + return 0; +} + +/*========================================== + * 現在位置のメモ + *------------------------------------------ + */ +int pc_memo (struct map_session_data *sd, int i) +{ + int skill; + int j; + + nullpo_retr (0, sd); + + skill = pc_checkskill (sd, AL_WARP); + + if (i >= MIN_PORTAL_MEMO) + i -= MIN_PORTAL_MEMO; + else if (map[sd->bl.m].flag.nomemo + || (map[sd->bl.m].flag.nowarpto + && battle_config.any_warp_GM_min_level > pc_isGM (sd))) + { + clif_skill_teleportmessage (sd, 1); + return 0; + } + + if (skill < 1) + { + clif_skill_memo (sd, 2); + } + + if (skill < 2 || i < -1 || i > 2) + { + clif_skill_memo (sd, 1); + return 0; + } + + for (j = 0; j < 3; j++) + { + if (strcmp (sd->status.memo_point[j].map, map[sd->bl.m].name) == 0) + { + i = j; + break; + } + } + + if (i == -1) + { + for (i = skill - 3; i >= 0; i--) + { + memcpy (&sd->status.memo_point[i + 1], &sd->status.memo_point[i], + sizeof (struct point)); + } + i = 0; + } + memcpy (sd->status.memo_point[i].map, map[sd->bl.m].name, 24); + sd->status.memo_point[i].x = sd->bl.x; + sd->status.memo_point[i].y = sd->bl.y; + + clif_skill_memo (sd, 0); + + return 1; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_can_reach (struct map_session_data *sd, int x, int y) +{ + struct walkpath_data wpd; + + nullpo_retr (0, sd); + + if (sd->bl.x == x && sd->bl.y == y) // 同じマス + return 1; + + // 障害物判定 + wpd.path_len = 0; + wpd.path_pos = 0; + wpd.path_half = 0; + return (path_search (&wpd, sd->bl.m, sd->bl.x, sd->bl.y, x, y, 0) != + -1) ? 1 : 0; +} + +// +// 歩 行物 +// +/*========================================== + * 次の1歩にかかる時間を計算 + *------------------------------------------ + */ +static int calc_next_walk_step (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->walkpath.path_pos >= sd->walkpath.path_len) + return -1; + if (sd->walkpath.path[sd->walkpath.path_pos] & 1) + return sd->speed * 14 / 10; + + return sd->speed; +} + +/*========================================== + * 半歩進む(timer関数) + *------------------------------------------ + */ +static void pc_walk (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd; + int i, ctype; + int moveblock; + int x, y, dx, dy; + + sd = map_id2sd (id); + if (sd == NULL) + return; + + if (sd->walktimer != tid) + { + if (battle_config.error_log) + printf ("pc_walk %d != %d\n", sd->walktimer, tid); + return; + } + sd->walktimer = -1; + if (sd->walkpath.path_pos >= sd->walkpath.path_len + || sd->walkpath.path_pos != data) + return; + + //歩いたので息吹のタイマーを初期化 + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + + sd->walkpath.path_half ^= 1; + if (sd->walkpath.path_half == 0) + { // マス目中心へ到着 + sd->walkpath.path_pos++; + if (sd->state.change_walk_target) + { + pc_walktoxy_sub (sd); + return; + } + } + else + { // マス目境界へ到着 + if (sd->walkpath.path[sd->walkpath.path_pos] >= 8) + return; + + x = sd->bl.x; + y = sd->bl.y; + ctype = map_getcell (sd->bl.m, x, y); + if (ctype == 1 || ctype == 5) + { + pc_stop_walking (sd, 1); + return; + } + sd->dir = sd->head_dir = sd->walkpath.path[sd->walkpath.path_pos]; + dx = dirx[(int) sd->dir]; + dy = diry[(int) sd->dir]; + ctype = map_getcell (sd->bl.m, x + dx, y + dy); + if (ctype == 1 || ctype == 5) + { + pc_walktoxy_sub (sd); + return; + } + + moveblock = (x / BLOCK_SIZE != (x + dx) / BLOCK_SIZE + || y / BLOCK_SIZE != (y + dy) / BLOCK_SIZE); + + sd->walktimer = 1; + map_foreachinmovearea (clif_pcoutsight, sd->bl.m, x - AREA_SIZE, + y - AREA_SIZE, x + AREA_SIZE, y + AREA_SIZE, + dx, dy, 0, sd); + + x += dx; + y += dy; + + if (moveblock) + map_delblock (&sd->bl); + sd->bl.x = x; + sd->bl.y = y; + if (moveblock) + map_addblock (&sd->bl); + + if (sd->sc_data[SC_DANCING].timer != -1) + skill_unit_move_unit_group ((struct skill_unit_group *) + sd->sc_data[SC_DANCING].val2, + sd->bl.m, dx, dy); + + map_foreachinmovearea (clif_pcinsight, sd->bl.m, x - AREA_SIZE, + y - AREA_SIZE, x + AREA_SIZE, y + AREA_SIZE, + -dx, -dy, 0, sd); + sd->walktimer = -1; + + if (sd->status.party_id > 0) + { // パーティのHP情報通知検査 + struct party *p = party_search (sd->status.party_id); + if (p != NULL) + { + int p_flag = 0; + map_foreachinmovearea (party_send_hp_check, sd->bl.m, + x - AREA_SIZE, y - AREA_SIZE, + x + AREA_SIZE, y + AREA_SIZE, -dx, -dy, + BL_PC, sd->status.party_id, &p_flag); + if (p_flag) + sd->party_hp = -1; + } + } + if (sd->status.option & 4) // クローキングの消滅検査 + skill_check_cloaking (&sd->bl); + // ディボーション検査 + for (i = 0; i < 5; i++) + if (sd->dev.val1[i]) + { + skill_devotion3 (&sd->bl, sd->dev.val1[i]); + break; + } + // 被ディボーション検査 + if (sd->sc_data && sd->sc_data[SC_DEVOTION].val1) + { + skill_devotion2 (&sd->bl, sd->sc_data[SC_DEVOTION].val1); + } + + skill_unit_move (&sd->bl, tick, 1); // スキルユニットの検査 + + if (map_getcell (sd->bl.m, x, y) & 0x80) + npc_touch_areanpc (sd, sd->bl.m, x, y); + else + sd->areanpc_id = 0; + } + if ((i = calc_next_walk_step (sd)) > 0) + { + i = i >> 1; + if (i < 1 && sd->walkpath.path_half == 0) + i = 1; + sd->walktimer = + add_timer (tick + i, pc_walk, id, sd->walkpath.path_pos); + } +} + +/*========================================== + * 移動可能か確認して、可能なら歩行開始 + *------------------------------------------ + */ +static int pc_walktoxy_sub (struct map_session_data *sd) +{ + struct walkpath_data wpd; + int i; + + nullpo_retr (1, sd); + + if (path_search + (&wpd, sd->bl.m, sd->bl.x, sd->bl.y, sd->to_x, sd->to_y, 0)) + return 1; + memcpy (&sd->walkpath, &wpd, sizeof (wpd)); + + clif_walkok (sd); + sd->state.change_walk_target = 0; + + if ((i = calc_next_walk_step (sd)) > 0) + { + i = i >> 2; + sd->walktimer = add_timer (gettick () + i, pc_walk, sd->bl.id, 0); + } + clif_movechar (sd); + + return 0; +} + +/*========================================== + * pc歩 行要求 + *------------------------------------------ + */ +int pc_walktoxy (struct map_session_data *sd, int x, int y) +{ + + nullpo_retr (0, sd); + + sd->to_x = x; + sd->to_y = y; + + if (pc_issit (sd)) + pc_setstand (sd); + + if (sd->walktimer != -1 && sd->state.change_walk_target == 0) + { + // 現在歩いている最中の目的地変更なのでマス目の中心に来た時に + // timer関数からpc_walktoxy_subを呼ぶようにする + sd->state.change_walk_target = 1; + } + else + { + pc_walktoxy_sub (sd); + } + + return 0; +} + +/*========================================== + * 歩 行停止 + *------------------------------------------ + */ +int pc_stop_walking (struct map_session_data *sd, int type) +{ + nullpo_retr (0, sd); + + if (sd->walktimer != -1) + { + delete_timer (sd->walktimer, pc_walk); + sd->walktimer = -1; + } + sd->walkpath.path_len = 0; + sd->to_x = sd->bl.x; + sd->to_y = sd->bl.y; + if (type & 0x01) + clif_fixpos (&sd->bl); + if (type & 0x02 && battle_config.pc_damage_delay) + { + unsigned int tick = gettick (); + int delay = battle_get_dmotion (&sd->bl); + if (sd->canmove_tick < tick) + sd->canmove_tick = tick + delay; + } + + return 0; +} + +void pc_touch_all_relevant_npcs (struct map_session_data *sd) +{ + if (map_getcell (sd->bl.m, sd->bl.x, sd->bl.y) & 0x80) + npc_touch_areanpc (sd, sd->bl.m, sd->bl.x, sd->bl.y); + else + sd->areanpc_id = 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_movepos (struct map_session_data *sd, int dst_x, int dst_y) +{ + int moveblock; + int dx, dy, dist; + + struct walkpath_data wpd; + + nullpo_retr (0, sd); + + if (path_search (&wpd, sd->bl.m, sd->bl.x, sd->bl.y, dst_x, dst_y, 0)) + return 1; + + sd->dir = sd->head_dir = map_calc_dir (&sd->bl, dst_x, dst_y); + + dx = dst_x - sd->bl.x; + dy = dst_y - sd->bl.y; + dist = distance (sd->bl.x, sd->bl.y, dst_x, dst_y); + + moveblock = (sd->bl.x / BLOCK_SIZE != dst_x / BLOCK_SIZE + || sd->bl.y / BLOCK_SIZE != dst_y / BLOCK_SIZE); + + map_foreachinmovearea (clif_pcoutsight, sd->bl.m, sd->bl.x - AREA_SIZE, + sd->bl.y - AREA_SIZE, sd->bl.x + AREA_SIZE, + sd->bl.y + AREA_SIZE, dx, dy, 0, sd); + + if (moveblock) + map_delblock (&sd->bl); + sd->bl.x = dst_x; + sd->bl.y = dst_y; + if (moveblock) + map_addblock (&sd->bl); + + map_foreachinmovearea (clif_pcinsight, sd->bl.m, sd->bl.x - AREA_SIZE, + sd->bl.y - AREA_SIZE, sd->bl.x + AREA_SIZE, + sd->bl.y + AREA_SIZE, -dx, -dy, 0, sd); + + if (sd->status.party_id > 0) + { // パーティのHP情報通知検査 + struct party *p = party_search (sd->status.party_id); + if (p != NULL) + { + int flag = 0; + map_foreachinmovearea (party_send_hp_check, sd->bl.m, + sd->bl.x - AREA_SIZE, sd->bl.y - AREA_SIZE, + sd->bl.x + AREA_SIZE, sd->bl.y + AREA_SIZE, + -dx, -dy, BL_PC, sd->status.party_id, + &flag); + if (flag) + sd->party_hp = -1; + } + } + + if (sd->status.option & 4) // クローキングの消滅検査 + skill_check_cloaking (&sd->bl); + + skill_unit_move (&sd->bl, gettick (), dist + 7); // スキルユニットの検査 + + pc_touch_all_relevant_npcs (sd); + return 0; +} + +// +// 武器戦闘 +// +/*========================================== + * スキルの検索 所有していた場合Lvが返る + *------------------------------------------ + */ +int pc_checkskill (struct map_session_data *sd, int skill_id) +{ + if (sd == NULL) + return 0; + if (skill_id >= 10000) + { + struct guild *g; + if (sd->status.guild_id > 0 + && (g = guild_search (sd->status.guild_id)) != NULL) + return guild_checkskill (g, skill_id); + return 0; + } + + if (sd->status.skill[skill_id].id == skill_id) + return (sd->status.skill[skill_id].lv); + + return 0; +} + +/*========================================== + * 武器変更によるスキルの継続チェック + * 引数: + * struct map_session_data *sd セッションデータ + * int nameid 装備品ID + * 返り値: + * 0 変更なし + * -1 スキルを解除 + *------------------------------------------ + */ +int pc_checkallowskill (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->sc_data == NULL) + return 0; + + if (!(skill_get_weapontype (KN_TWOHANDQUICKEN) & (1 << sd->status.weapon)) + && sd->sc_data[SC_TWOHANDQUICKEN].timer != -1) + { // 2HQ + skill_status_change_end (&sd->bl, SC_TWOHANDQUICKEN, -1); // 2HQを解除 + return -1; + } + if (!(skill_get_weapontype (LK_AURABLADE) & (1 << sd->status.weapon)) + && sd->sc_data[SC_AURABLADE].timer != -1) + { // オーラブレード + skill_status_change_end (&sd->bl, SC_AURABLADE, -1); // オーラブレードを解除 + return -1; + } + if (!(skill_get_weapontype (LK_PARRYING) & (1 << sd->status.weapon)) + && sd->sc_data[SC_PARRYING].timer != -1) + { // パリイング + skill_status_change_end (&sd->bl, SC_PARRYING, -1); // パリイングを解除 + return -1; + } + if (!(skill_get_weapontype (LK_CONCENTRATION) & (1 << sd->status.weapon)) + && sd->sc_data[SC_CONCENTRATION].timer != -1) + { // コンセントレーション + skill_status_change_end (&sd->bl, SC_CONCENTRATION, -1); // コンセントレーションを解除 + return -1; + } + if (!(skill_get_weapontype (CR_SPEARQUICKEN) & (1 << sd->status.weapon)) + && sd->sc_data[SC_SPEARSQUICKEN].timer != -1) + { // スピアクィッケン + skill_status_change_end (&sd->bl, SC_SPEARSQUICKEN, -1); // スピアクイッケンを解除 + return -1; + } + if (!(skill_get_weapontype (BS_ADRENALINE) & (1 << sd->status.weapon)) + && sd->sc_data[SC_ADRENALINE].timer != -1) + { // アドレナリンラッシュ + skill_status_change_end (&sd->bl, SC_ADRENALINE, -1); // アドレナリンラッシュを解除 + return -1; + } + + if (sd->status.shield <= 0) + { + if (sd->sc_data[SC_AUTOGUARD].timer != -1) + { // オートガード + skill_status_change_end (&sd->bl, SC_AUTOGUARD, -1); + return -1; + } + if (sd->sc_data[SC_DEFENDER].timer != -1) + { // ディフェンダー + skill_status_change_end (&sd->bl, SC_DEFENDER, -1); + return -1; + } + if (sd->sc_data[SC_REFLECTSHIELD].timer != -1) + { //リフレクトシールド + skill_status_change_end (&sd->bl, SC_REFLECTSHIELD, -1); + return -1; + } + } + + return 0; +} + +/*========================================== + * 装 備品のチェック + *------------------------------------------ + */ +int pc_checkequip (struct map_session_data *sd, int pos) +{ + int i; + + nullpo_retr (-1, sd); + + for (i = 0; i < 11; i++) + { + if (pos & equip_pos[i]) + return sd->equip_index[i]; + } + + return -1; +} + +/*========================================== + * 転生職や養子職の元の職業を返す + *------------------------------------------ + */ +struct pc_base_job pc_calc_base_job (int b_class) +{ + struct pc_base_job bj; + //転生や養子の場合の元の職業を算出する + if (b_class < MAX_PC_CLASS) + { //通常 + bj.job = b_class; + bj.upper = 0; + } + else if (b_class >= 4001 && b_class < 4023) + { //転生職 + bj.job = b_class - 4001; + bj.upper = 1; + } + else if (b_class == 23 + 4023 - 1) + { //養子スパノビ + bj.job = b_class - (4023 - 1); + bj.upper = 2; + } + else + { //養子スパノビ以外の養子 + bj.job = b_class - 4023; + bj.upper = 2; + } + + if (battle_config.enable_upper_class == 0) + { //confで無効になっていたらupper=0 + bj.upper = 0; + } + + if (bj.job == 0) + { + bj.type = 0; + } + else if (bj.job < 7) + { + bj.type = 1; + } + else + { + bj.type = 2; + } + + return bj; +} + +/*========================================== + * PCの攻撃 (timer関数) + *------------------------------------------ + */ +static +void pc_attack_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd; + struct block_list *bl; + struct status_change *sc_data; + short *opt; + int dist, skill, range; + int attack_spell_delay; + + sd = map_id2sd (id); + if (sd == NULL) + return; + if (sd->attacktimer != tid) + { + if (battle_config.error_log) + printf ("pc_attack_timer %d != %d\n", sd->attacktimer, tid); + return; + } + sd->attacktimer = -1; + + if (sd->bl.prev == NULL) + return; + + bl = map_id2bl (sd->attacktarget); + if (bl == NULL || bl->prev == NULL) + return; + + if (bl->type == BL_PC && pc_isdead ((struct map_session_data *) bl)) + return; + + // 同じmapでないなら攻撃しない + // PCが死んでても攻撃しない + if (sd->bl.m != bl->m || pc_isdead (sd)) + return; + + if (sd->opt1 > 0 || sd->status.option & 2 || sd->status.option & 16388) // 異常などで攻撃できない + return; + + if (sd->sc_data[SC_AUTOCOUNTER].timer != -1) + return; + if (sd->sc_data[SC_BLADESTOP].timer != -1) + return; + + if ((opt = battle_get_option (bl)) != NULL && *opt & 0x46) + return; + if (((sc_data = battle_get_sc_data (bl)) != NULL + && sc_data[SC_TRICKDEAD].timer != -1) + || ((sc_data = battle_get_sc_data (bl)) != NULL + && sc_data[SC_BASILICA].timer != -1)) + return; + + if (sd->skilltimer != -1 && pc_checkskill (sd, SA_FREECAST) <= 0) + return; + + if (!battle_config.sdelay_attack_enable + && pc_checkskill (sd, SA_FREECAST) <= 0) + { + if (DIFF_TICK (tick, sd->canact_tick) < 0) + { + clif_skill_fail (sd, 1, 4, 0); + return; + } + } + + if (sd->attackabletime > tick) + return; // cannot attack yet + + attack_spell_delay = sd->attack_spell_delay; + if (sd->attack_spell_override // [Fate] If we have an active attack spell, use that + && spell_attack (id, sd->attacktarget)) + { + // Return if the spell succeeded. If the spell had disspiated, spell_attack() may fail. + sd->attackabletime = tick + attack_spell_delay; + + } + else + { + dist = distance (sd->bl.x, sd->bl.y, bl->x, bl->y); + range = sd->attackrange; + if (sd->status.weapon != 11) + range++; + if (dist > range) + { // 届 かないので移動 + //if(pc_can_reach(sd,bl->x,bl->y)) + //clif_movetoattack(sd,bl); + return; + } + + if (dist <= range && !battle_check_range (&sd->bl, bl, range)) + { + if (pc_can_reach (sd, bl->x, bl->y) && sd->canmove_tick < tick + && (sd->sc_data[SC_ANKLE].timer == -1 + || sd->sc_data[SC_SPIDERWEB].timer == -1)) + // TMW client doesn't support this + //pc_walktoxy(sd,bl->x,bl->y); + clif_movetoattack (sd, bl); + sd->attackabletime = tick + (sd->aspd << 1); + } + else + { + if (battle_config.pc_attack_direction_change) + sd->dir = sd->head_dir = map_calc_dir (&sd->bl, bl->x, bl->y); // 向き設定 + + if (sd->walktimer != -1) + pc_stop_walking (sd, 1); + + if (sd->sc_data[SC_COMBO].timer == -1) + { + map_freeblock_lock (); + pc_stop_walking (sd, 0); + sd->attacktarget_lv = + battle_weapon_attack (&sd->bl, bl, tick, 0); + if (!(battle_config.pc_cloak_check_type & 2) + && sd->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end (&sd->bl, SC_CLOAKING, -1); + map_freeblock_unlock (); + if (sd->skilltimer != -1 && (skill = pc_checkskill (sd, SA_FREECAST)) > 0) // フリーキャスト + sd->attackabletime = + tick + ((sd->aspd << 1) * (150 - skill * 5) / 100); + else + sd->attackabletime = tick + (sd->aspd << 1); + } + else if (sd->attackabletime <= tick) + { + if (sd->skilltimer != -1 && (skill = pc_checkskill (sd, SA_FREECAST)) > 0) // フリーキャスト + sd->attackabletime = + tick + ((sd->aspd << 1) * (150 - skill * 5) / 100); + else + sd->attackabletime = tick + (sd->aspd << 1); + } + if (sd->attackabletime <= tick) + sd->attackabletime = tick + (battle_config.max_aspd << 1); + } + } + + if (sd->state.attack_continue) + { + sd->attacktimer = + add_timer (sd->attackabletime, pc_attack_timer, sd->bl.id, 0); + } +} + +/*========================================== + * 攻撃要求 + * typeが1なら継続攻撃 + *------------------------------------------ + */ +int pc_attack (struct map_session_data *sd, int target_id, int type) +{ + struct block_list *bl; + int d; + + nullpo_retr (0, sd); + + bl = map_id2bl (target_id); + if (bl == NULL) + return 1; + + if (bl->type == BL_NPC) + { // monster npcs [Valaris] + npc_click (sd, RFIFOL (sd->fd, 2)); + return 0; + } + + if (!battle_check_target (&sd->bl, bl, BCT_ENEMY)) + return 1; + if (sd->attacktimer != -1) + pc_stopattack (sd); + sd->attacktarget = target_id; + sd->state.attack_continue = type; + + d = DIFF_TICK (sd->attackabletime, gettick ()); + if (d > 0 && d < 2000) + { // 攻撃delay中 + sd->attacktimer = + add_timer (sd->attackabletime, pc_attack_timer, sd->bl.id, 0); + } + else + { + // 本来timer関数なので引数を合わせる + pc_attack_timer (-1, gettick (), sd->bl.id, 0); + } + + return 0; +} + +/*========================================== + * 継続攻撃停止 + *------------------------------------------ + */ +int pc_stopattack (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->attacktimer != -1) + { + delete_timer (sd->attacktimer, pc_attack_timer); + sd->attacktimer = -1; + } + sd->attacktarget = 0; + sd->state.attack_continue = 0; + + return 0; +} + +static +void pc_follow_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd, *bl; + + sd = map_id2sd (id); + if (sd == NULL || sd->followtimer != tid) + return; + + sd->followtimer = -1; + + do + { + if (sd->bl.prev == NULL) + break; + + bl = (struct map_session_data *) map_id2bl (sd->followtarget); + + if (bl == NULL) + return; + + if (bl->bl.prev == NULL) + break; + + if (bl->bl.type == BL_PC + && pc_isdead ((struct map_session_data *) bl)) + return; + + if (sd->skilltimer == -1 && sd->attacktimer == -1 + && sd->walktimer == -1) + { + if ((sd->bl.m == bl->bl.m) + && pc_can_reach (sd, bl->bl.x, bl->bl.y)) + { + if (distance (sd->bl.x, sd->bl.y, bl->bl.x, bl->bl.y) > 5) + pc_walktoxy (sd, bl->bl.x, bl->bl.y); + } + else + pc_setpos ((struct map_session_data *) sd, bl->mapname, + bl->bl.x, bl->bl.y, 3); + } + } + while (0); + + sd->followtimer = + add_timer (tick + sd->aspd, pc_follow_timer, sd->bl.id, 0); +} + +int pc_follow (struct map_session_data *sd, int target_id) +{ + struct block_list *bl; + + bl = map_id2bl (target_id); + if (bl == NULL) + return 1; + sd->followtarget = target_id; + if (sd->followtimer != -1) + { + delete_timer (sd->followtimer, pc_follow_timer); + sd->followtimer = -1; + } + + pc_follow_timer (-1, gettick (), sd->bl.id, 0); + + return 0; +} + +int pc_checkbaselevelup (struct map_session_data *sd) +{ + int next = pc_nextbaseexp (sd); + + nullpo_retr (0, sd); + + if (sd->status.base_exp >= next && next > 0) + { + struct pc_base_job s_class = pc_calc_base_job (sd->status.pc_class); + + // base側レベルアップ処理 + sd->status.base_exp -= next; + + sd->status.base_level++; + sd->status.status_point += (sd->status.base_level + 14) / 4; + clif_updatestatus (sd, SP_STATUSPOINT); + clif_updatestatus (sd, SP_BASELEVEL); + clif_updatestatus (sd, SP_NEXTBASEEXP); + pc_calcstatus (sd, 0); + pc_heal (sd, sd->status.max_hp, sd->status.max_sp); + + //スパノビはキリエ、イムポ、マニピ、グロ、サフラLv1がかかる + if (s_class.job == 23) + { + skill_status_change_start (&sd->bl, + SkillStatusChangeTable[PR_KYRIE], 1, 0, + 0, 0, skill_get_time (PR_KYRIE, 1), 0); + skill_status_change_start (&sd->bl, + SkillStatusChangeTable[PR_IMPOSITIO], + 1, 0, 0, 0, + skill_get_time (PR_IMPOSITIO, 1), 0); + skill_status_change_start (&sd->bl, + SkillStatusChangeTable[PR_MAGNIFICAT], + 1, 0, 0, 0, + skill_get_time (PR_MAGNIFICAT, 1), 0); + skill_status_change_start (&sd->bl, + SkillStatusChangeTable[PR_GLORIA], 1, + 0, 0, 0, skill_get_time (PR_GLORIA, 1), + 0); + skill_status_change_start (&sd->bl, + SkillStatusChangeTable[PR_SUFFRAGIUM], + 1, 0, 0, 0, + skill_get_time (PR_SUFFRAGIUM, 1), 0); + } + + clif_misceffect (&sd->bl, 0); + //レベルアップしたのでパーティー情報を更新する + //(公平範囲チェック) + party_send_movemap (sd); + MAP_LOG_XP (sd, "LEVELUP") return 1; + } + + return 0; +} + +/*======================================== + * Compute the maximum for sd->skill_point, i.e., the max. number of skill points that can still be filled in + *---------------------------------------- + */ +static +int pc_skillpt_potential (struct map_session_data *sd) +{ + int skill_id; + int potential = 0; + +#define RAISE_COST(x) (((x)*((x)-1))>>1) + + for (skill_id = 0; skill_id < MAX_SKILL; skill_id++) + if (sd->status.skill[skill_id].id != 0 + && sd->status.skill[skill_id].lv < skill_db[skill_id].max_raise) + potential += RAISE_COST (skill_db[skill_id].max_raise) + - RAISE_COST (sd->status.skill[skill_id].lv); +#undef RAISE_COST + + return potential; +} + +int pc_checkjoblevelup (struct map_session_data *sd) +{ + int next = pc_nextjobexp (sd); + + nullpo_retr (0, sd); + + if (sd->status.job_exp >= next && next > 0) + { + if (pc_skillpt_potential (sd) < sd->status.skill_point) + { // [Fate] Bah, this is is painful. + // But the alternative is quite error-prone, and eAthena has far worse performance issues... + sd->status.job_exp = next - 1; + pc_calcstatus(sd,0); + return 0; + } + + // job側レベルアップ処理 + sd->status.job_exp -= next; + clif_updatestatus (sd, SP_NEXTJOBEXP); + sd->status.skill_point++; + clif_updatestatus (sd, SP_SKILLPOINT); + pc_calcstatus (sd, 0); + + MAP_LOG_PC (sd, "SKILLPOINTS-UP %d", sd->status.skill_point); + + if (sd->status.job_level < 250 + && sd->status.job_level < sd->status.base_level * 2) + sd->status.job_level++; // Make levelling up a little harder + + clif_misceffect (&sd->bl, 1); + return 1; + } + + return 0; +} + +/*========================================== + * 経験値取得 + *------------------------------------------ + */ +int pc_gainexp (struct map_session_data *sd, int base_exp, int job_exp) +{ + return pc_gainexp_reason (sd, base_exp, job_exp, + PC_GAINEXP_REASON_KILLING); +} + +int pc_gainexp_reason (struct map_session_data *sd, int base_exp, int job_exp, + int reason) +{ + char output[256]; + nullpo_retr (0, sd); + + if (sd->bl.prev == NULL || pc_isdead (sd)) + return 0; + + if ((battle_config.pvp_exp == 0) && map[sd->bl.m].flag.pvp) // [MouseJstr] + return 0; // no exp on pvp maps + + MAP_LOG_PC (sd, "GAINXP %d %d %s", base_exp, job_exp, + ((reason == + 2) ? "SCRIPTXP" : ((reason == 1) ? "HEALXP" : "KILLXP"))); + + if (sd->sc_data[SC_RICHMANKIM].timer != -1) + { // added bounds checking [Vaalris] + base_exp += + base_exp * (25 + sd->sc_data[SC_RICHMANKIM].val1 * 25) / 100; + job_exp += + job_exp * (25 + sd->sc_data[SC_RICHMANKIM].val1 * 25) / 100; + } + + if (sd->status.guild_id > 0) + { // ギルドに上納 + base_exp -= guild_payexp (sd, base_exp); + if (base_exp < 0) + base_exp = 0; + } + + if (!battle_config.multi_level_up && pc_nextbaseafter (sd)) + { + while (sd->status.base_exp + base_exp >= pc_nextbaseafter (sd) + && sd->status.base_exp <= pc_nextbaseexp (sd) + && pc_nextbaseafter (sd) > 0) + { + base_exp *= .90; + } + } + + sd->status.base_exp += base_exp; + + // [Fate] Adjust experience points that healers can extract from this character + if (reason != PC_GAINEXP_REASON_HEALING) + { + const int max_heal_xp = + 20 + (sd->status.base_level * sd->status.base_level); + + sd->heal_xp += base_exp; + if (sd->heal_xp > max_heal_xp) + sd->heal_xp = max_heal_xp; + } + + if (sd->status.base_exp < 0) + sd->status.base_exp = 0; + + while (pc_checkbaselevelup (sd)); + + clif_updatestatus (sd, SP_BASEEXP); + if (!battle_config.multi_level_up && pc_nextjobafter (sd)) + { + while (sd->status.job_exp + job_exp >= pc_nextjobafter (sd) + && sd->status.job_exp <= pc_nextjobexp (sd) + && pc_nextjobafter (sd) > 0) + { + job_exp *= .90; + } + } + + sd->status.job_exp += job_exp; + if (sd->status.job_exp < 0) + sd->status.job_exp = 0; + + while (pc_checkjoblevelup (sd)); + + clif_updatestatus (sd, SP_JOBEXP); + + if (battle_config.disp_experience) + { + sprintf (output, + "Experienced Gained Base:%d Job:%d", base_exp, job_exp); + clif_disp_onlyself (sd, output, strlen (output)); + } + + return 0; +} + +int pc_extract_healer_exp (struct map_session_data *sd, int max) +{ + int amount; + nullpo_retr (0, sd); + + amount = sd->heal_xp; + if (max < amount) + amount = max; + + sd->heal_xp -= amount; + return amount; +} + +/*========================================== + * base level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextbaseexp (struct map_session_data *sd) +{ + int i; + + nullpo_retr (0, sd); + + if (sd->status.base_level >= MAX_LEVEL || sd->status.base_level <= 0) + return 0; + + if (sd->status.pc_class == 0) + i = 0; + else if (sd->status.pc_class <= 6) + i = 1; + else if (sd->status.pc_class <= 22) + i = 2; + else if (sd->status.pc_class == 23) + i = 3; + else if (sd->status.pc_class == 4001) + i = 4; + else if (sd->status.pc_class <= 4007) + i = 5; + else + i = 6; + + return exp_table[i][sd->status.base_level - 1]; +} + +/*========================================== + * job level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextjobexp (struct map_session_data *sd) +{ + // [fate] For normal levels, this ranges from 20k to 50k, depending on job level. + // Job level is at most twice the player's experience level (base_level). Levelling + // from 2 to 9 is 44 points, i.e., 880k to 2.2M job experience points (this is per + // skill, obviously.) + + return 20000 + sd->status.job_level * 150; +} + +/*========================================== + * base level after next [Valaris] + *------------------------------------------ + */ +int pc_nextbaseafter (struct map_session_data *sd) +{ + int i; + + nullpo_retr (0, sd); + + if (sd->status.base_level >= MAX_LEVEL || sd->status.base_level <= 0) + return 0; + + if (sd->status.pc_class == 0) + i = 0; + else if (sd->status.pc_class <= 6) + i = 1; + else if (sd->status.pc_class <= 22) + i = 2; + else if (sd->status.pc_class == 23) + i = 3; + else if (sd->status.pc_class == 4001) + i = 4; + else if (sd->status.pc_class <= 4007) + i = 5; + else + i = 6; + + return exp_table[i][sd->status.base_level]; +} + +/*========================================== + * job level after next [Valaris] + *------------------------------------------ + */ +int pc_nextjobafter (struct map_session_data *sd) +{ + int i; + + nullpo_retr (0, sd); + + if (sd->status.job_level >= MAX_LEVEL || sd->status.job_level <= 0) + return 0; + + if (sd->status.pc_class == 0) + i = 7; + else if (sd->status.pc_class <= 6) + i = 8; + else if (sd->status.pc_class <= 22) + i = 9; + else if (sd->status.pc_class == 23) + i = 10; + else if (sd->status.pc_class == 4001) + i = 11; + else if (sd->status.pc_class <= 4007) + i = 12; + else + i = 13; + + return exp_table[i][sd->status.job_level]; +} + +/*========================================== + + * 必要ステータスポイント計算 + *------------------------------------------ + */ +int pc_need_status_point (struct map_session_data *sd, int type) +{ + int val; + + nullpo_retr (-1, sd); + + if (type < SP_STR || type > SP_LUK) + return -1; + val = + type == SP_STR ? sd->status.str : + type == SP_AGI ? sd->status.agi : + type == SP_VIT ? sd->status.vit : + type == SP_INT ? sd->status.int_ : + type == SP_DEX ? sd->status.dex : sd->status.luk; + + return (val + 9) / 10 + 1; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup (struct map_session_data *sd, int type) +{ + int need, val = 0; + + nullpo_retr (0, sd); + + switch (type) + { + case SP_STR: + val = sd->status.str; + break; + case SP_AGI: + val = sd->status.agi; + break; + case SP_VIT: + val = sd->status.vit; + break; + case SP_INT: + val = sd->status.int_; + break; + case SP_DEX: + val = sd->status.dex; + break; + case SP_LUK: + val = sd->status.luk; + break; + } + + need = pc_need_status_point (sd, type); + if (type < SP_STR || type > SP_LUK || need < 0 + || need > sd->status.status_point + || val >= battle_config.max_parameter) + { + clif_statusupack (sd, type, 0, val); + clif_updatestatus (sd, SP_STATUSPOINT); + return 1; + } + switch (type) + { + case SP_STR: + val = ++sd->status.str; + break; + case SP_AGI: + val = ++sd->status.agi; + break; + case SP_VIT: + val = ++sd->status.vit; + break; + case SP_INT: + val = ++sd->status.int_; + break; + case SP_DEX: + val = ++sd->status.dex; + break; + case SP_LUK: + val = ++sd->status.luk; + break; + } + sd->status.status_point -= need; + if (need != pc_need_status_point (sd, type)) + { + clif_updatestatus (sd, type - SP_STR + SP_USTR); + } + clif_updatestatus (sd, SP_STATUSPOINT); + clif_updatestatus (sd, type); + pc_calcstatus (sd, 0); + clif_statusupack (sd, type, 1, val); + + MAP_LOG_STATS (sd, "STATUP"); + + return 0; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup2 (struct map_session_data *sd, int type, int val) +{ + nullpo_retr (0, sd); + + if (type < SP_STR || type > SP_LUK) + { + clif_statusupack (sd, type, 0, 0); + return 1; + } + switch (type) + { + case SP_STR: + if (sd->status.str + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.str + val < 1) + val = 1; + else + val += sd->status.str; + sd->status.str = val; + break; + case SP_AGI: + if (sd->status.agi + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.agi + val < 1) + val = 1; + else + val += sd->status.agi; + sd->status.agi = val; + break; + case SP_VIT: + if (sd->status.vit + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.vit + val < 1) + val = 1; + else + val += sd->status.vit; + sd->status.vit = val; + break; + case SP_INT: + if (sd->status.int_ + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.int_ + val < 1) + val = 1; + else + val += sd->status.int_; + sd->status.int_ = val; + break; + case SP_DEX: + if (sd->status.dex + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.dex + val < 1) + val = 1; + else + val += sd->status.dex; + sd->status.dex = val; + break; + case SP_LUK: + if (sd->status.luk + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if (sd->status.luk + val < 1) + val = 1; + else + val = sd->status.luk + val; + sd->status.luk = val; + break; + } + clif_updatestatus (sd, type - SP_STR + SP_USTR); + clif_updatestatus (sd, type); + pc_calcstatus (sd, 0); + clif_statusupack (sd, type, 1, val); + MAP_LOG_STATS (sd, "STATUP2"); + + return 0; +} + +/*========================================== + * スキルポイント割り振り + *------------------------------------------ + */ +int pc_skillup (struct map_session_data *sd, int skill_num) +{ + nullpo_retr (0, sd); + + if (sd->status.skill[skill_num].id != 0 + && sd->status.skill_point >= sd->status.skill[skill_num].lv + && sd->status.skill[skill_num].lv < skill_db[skill_num].max_raise) + { + sd->status.skill_point -= sd->status.skill[skill_num].lv; + sd->status.skill[skill_num].lv++; + + pc_calcstatus (sd, 0); + clif_skillup (sd, skill_num); + clif_updatestatus (sd, SP_SKILLPOINT); + clif_skillinfoblock (sd); + MAP_LOG_PC(sd, "SKILLUP %d %d %d", + skill_num, sd->status.skill[skill_num].lv, skill_power(sd, skill_num)); + } + + return 0; +} + +/*========================================== + * /allskill + *------------------------------------------ + */ +int pc_allskillup (struct map_session_data *sd) +{ + int i, id; + int c = 0, s = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + s_class = pc_calc_base_job (sd->status.pc_class); + c = s_class.job; + s = (s_class.upper == 1) ? 1 : 0; //転生以外は通常のスキル? + + for (i = 0; i < MAX_SKILL; i++) + sd->status.skill[i].id = 0; + + if (battle_config.gm_allskill > 0 + && pc_isGM (sd) >= battle_config.gm_allskill) + { + // 全てのスキル + for (i = 1; i < 158; i++) + sd->status.skill[i].lv = skill_get_max (i); + for (i = 210; i < 291; i++) + sd->status.skill[i].lv = skill_get_max (i); + for (i = 304; i < MAX_SKILL; i++) + sd->status.skill[i].lv = skill_get_max (i); + } + else + { + for (i = 0; (id = skill_tree[s][c][i].id) > 0; i++) + { + if (sd->status.skill[id].id == 0 && skill_get_inf2 (id) & 0x01) + sd->status.skill[id].lv = skill_get_max (id); + } + } + pc_calcstatus (sd, 0); + + return 0; +} + +/*========================================== + * /resetlvl + *------------------------------------------ + */ +int pc_resetlvl (struct map_session_data *sd, int type) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 1; i < MAX_SKILL; i++) + { + sd->status.skill[i].lv = 0; + } + + if (type == 1) + { + sd->status.skill_point = 0; + sd->status.base_level = 1; + sd->status.job_level = 1; + sd->status.base_exp = 0; + sd->status.job_exp = 0; + if (sd->status.option != 0) + sd->status.option = 0; + + sd->status.str = 1; + sd->status.agi = 1; + sd->status.vit = 1; + sd->status.int_ = 1; + sd->status.dex = 1; + sd->status.luk = 1; + if (sd->status.pc_class == 4001) + sd->status.status_point = 100; + } + + if (type == 2) + { + sd->status.skill_point = 0; + sd->status.base_level = 1; + sd->status.job_level = 1; + sd->status.base_exp = 0; + sd->status.job_exp = 0; + } + if (type == 3) + { + sd->status.base_level = 1; + sd->status.base_exp = 0; + } + if (type == 4) + { + sd->status.job_level = 1; + sd->status.job_exp = 0; + } + + clif_updatestatus (sd, SP_STATUSPOINT); + clif_updatestatus (sd, SP_STR); + clif_updatestatus (sd, SP_AGI); + clif_updatestatus (sd, SP_VIT); + clif_updatestatus (sd, SP_INT); + clif_updatestatus (sd, SP_DEX); + clif_updatestatus (sd, SP_LUK); + clif_updatestatus (sd, SP_BASELEVEL); + clif_updatestatus (sd, SP_JOBLEVEL); + clif_updatestatus (sd, SP_STATUSPOINT); + clif_updatestatus (sd, SP_NEXTBASEEXP); + clif_updatestatus (sd, SP_NEXTJOBEXP); + clif_updatestatus (sd, SP_SKILLPOINT); + + clif_updatestatus (sd, SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus (sd, SP_UAGI); + clif_updatestatus (sd, SP_UVIT); + clif_updatestatus (sd, SP_UINT); + clif_updatestatus (sd, SP_UDEX); + clif_updatestatus (sd, SP_ULUK); // End Addition + + for (i = 0; i < 11; i++) + { // unequip items that can't be equipped by base 1 [Valaris] + if (sd->equip_index[i] >= 0) + if (!pc_isequip (sd, sd->equip_index[i])) + { + pc_unequipitem (sd, sd->equip_index[i], 1); + sd->equip_index[i] = -1; + } + } + + clif_skillinfoblock (sd); + pc_calcstatus (sd, 0); + + MAP_LOG_STATS (sd, "STATRESET"); + + return 0; +} + +/*========================================== + * /resetstate + *------------------------------------------ + */ +int pc_resetstate (struct map_session_data *sd) +{ +#define sumsp(a) ((a)*((a-2)/10+2) - 5*((a-2)/10)*((a-2)/10) - 6*((a-2)/10) -2) +// int add=0; // Removed by Dexity + + nullpo_retr (0, sd); + +// New statpoint table used here - Dexity + sd->status.status_point = atoi (statp[sd->status.base_level - 1]); +// End addition + +// Removed by Dexity - old count +// add += sumsp(sd->status.str); +// add += sumsp(sd->status.agi); +// add += sumsp(sd->status.vit); +// add += sumsp(sd->status.int_); +// add += sumsp(sd->status.dex); +// add += sumsp(sd->status.luk); +// sd->status.status_point+=add; + + clif_updatestatus (sd, SP_STATUSPOINT); + + sd->status.str = 1; + sd->status.agi = 1; + sd->status.vit = 1; + sd->status.int_ = 1; + sd->status.dex = 1; + sd->status.luk = 1; + + clif_updatestatus (sd, SP_STR); + clif_updatestatus (sd, SP_AGI); + clif_updatestatus (sd, SP_VIT); + clif_updatestatus (sd, SP_INT); + clif_updatestatus (sd, SP_DEX); + clif_updatestatus (sd, SP_LUK); + + clif_updatestatus (sd, SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus (sd, SP_UAGI); + clif_updatestatus (sd, SP_UVIT); + clif_updatestatus (sd, SP_UINT); + clif_updatestatus (sd, SP_UDEX); + clif_updatestatus (sd, SP_ULUK); // End Addition + + pc_calcstatus (sd, 0); + + return 0; +} + +/*========================================== + * /resetskill + *------------------------------------------ + */ +int pc_resetskill (struct map_session_data *sd) +{ + int i, skill; + + nullpo_retr (0, sd); + + sd->status.skill_point += pc_calc_skillpoint (sd); + + for (i = 1; i < MAX_SKILL; i++) + if ((skill = pc_checkskill (sd, i)) > 0) + { + sd->status.skill[i].lv = 0; + sd->status.skill[i].flags = 0; + } + + clif_updatestatus (sd, SP_SKILLPOINT); + clif_skillinfoblock (sd); + pc_calcstatus (sd, 0); + + return 0; +} + +/*========================================== + * pcにダメージを与える + *------------------------------------------ + */ +int pc_damage (struct block_list *src, struct map_session_data *sd, + int damage) +{ + int i = 0, j = 0; + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job (sd->status.pc_class); + // 既に死んでいたら無効 + if (pc_isdead (sd)) + return 0; + // 座ってたら立ち上がる + if (pc_issit (sd)) + { + pc_setstand (sd); + skill_gangsterparadise (sd, 0); + } + + if (src) + { + if (src->type == BL_PC) + { + MAP_LOG_PC (sd, "INJURED-BY PC%d FOR %d", + ((struct map_session_data *) src)->status.char_id, + damage); + } + else + { + MAP_LOG_PC (sd, "INJURED-BY MOB%d FOR %d", src->id, damage); + } + } + else + MAP_LOG_PC (sd, "INJURED-BY null FOR %d", damage); + + // 歩 いていたら足を止める + if (sd->sc_data[SC_ENDURE].timer == -1 + && !sd->special_state.infinite_endure) + pc_stop_walking (sd, 3); + // 演奏/ダンスの中断 + if (damage > sd->status.max_hp >> 2) + skill_stop_dancing (&sd->bl, 0); + + sd->status.hp -= damage; + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end (&sd->bl, SC_TRICKDEAD, -1); + if (sd->status.option & 2) + skill_status_change_end (&sd->bl, SC_HIDING, -1); + if (sd->status.option & 4) + skill_status_change_end (&sd->bl, SC_CLOAKING, -1); + if (sd->status.option & 16386) + skill_status_change_end (&sd->bl, SC_CHASEWALK, -1); + + if (sd->status.hp > 0) + { + // まだ生きているならHP更新 + clif_updatestatus (sd, SP_HP); + + if (sd->status.hp < sd->status.max_hp >> 2 + && pc_checkskill (sd, SM_AUTOBERSERK) > 0 + && (sd->sc_data[SC_PROVOKE].timer == -1 + || sd->sc_data[SC_PROVOKE].val2 == 0)) + // オートバーサーク発動 + skill_status_change_start (&sd->bl, SC_PROVOKE, 10, 1, 0, 0, 0, + 0); + + sd->canlog_tick = gettick (); + + if (sd->status.party_id > 0) + { // on-the-fly party hp updates [Valaris] + struct party *p = party_search (sd->status.party_id); + if (p != NULL) + clif_party_hp (p, sd); + } // end addition [Valaris] + + return 0; + } + + MAP_LOG_PC (sd, "DEAD%s", ""); + + // Character is dead! + + sd->status.hp = 0; + // [Fate] Stop quickregen + sd->quick_regeneration_hp.amount = 0; + sd->quick_regeneration_sp.amount = 0; + skill_update_heal_animation (sd); + + pc_setdead (sd); + + pc_stop_walking (sd, 0); + skill_castcancel (&sd->bl, 0); // 詠唱の中止 + clif_clearchar_area (&sd->bl, 1); + skill_unit_out_all (&sd->bl, gettick (), 1); + if (sd->sc_data[SC_BLADESTOP].timer != -1) //白刃は事前に解除 + skill_status_change_end (&sd->bl, SC_BLADESTOP, -1); + pc_setglobalreg (sd, "PC_DIE_COUNTER", ++sd->die_counter); //死にカウンター書き込み + skill_status_change_clear (&sd->bl, 0); // ステータス異常を解除する + clif_updatestatus (sd, SP_HP); + pc_calcstatus (sd, 0); + // [Fate] Reset magic + sd->cast_tick = gettick (); + magic_stop_completely (sd); + + for (i = 0; i < 5; i++) + if (sd->dev.val1[i]) + { + skill_status_change_end (&map_id2sd (sd->dev.val1[i])->bl, + SC_DEVOTION, -1); + sd->dev.val1[i] = sd->dev.val2[i] = 0; + } + + if (battle_config.death_penalty_type > 0 && sd->status.base_level >= 20) + { // changed penalty options, added death by player if pk_mode [Valaris] + if (!map[sd->bl.m].flag.nopenalty && !map[sd->bl.m].flag.gvg) + { + if (battle_config.death_penalty_type == 1 + && battle_config.death_penalty_base > 0) + sd->status.base_exp -= + (double) pc_nextbaseexp (sd) * + (double) battle_config.death_penalty_base / 10000; + if (battle_config.pk_mode && src && src->type == BL_PC) + sd->status.base_exp -= + (double) pc_nextbaseexp (sd) * + (double) battle_config.death_penalty_base / 10000; + else if (battle_config.death_penalty_type == 2 + && battle_config.death_penalty_base > 0) + { + if (pc_nextbaseexp (sd) > 0) + sd->status.base_exp -= + (double) sd->status.base_exp * + (double) battle_config.death_penalty_base / 10000; + if (battle_config.pk_mode && src && src->type == BL_PC) + sd->status.base_exp -= + (double) sd->status.base_exp * + (double) battle_config.death_penalty_base / 10000; + } + if (sd->status.base_exp < 0) + sd->status.base_exp = 0; + clif_updatestatus (sd, SP_BASEEXP); + + if (battle_config.death_penalty_type == 1 + && battle_config.death_penalty_job > 0) + sd->status.job_exp -= + (double) pc_nextjobexp (sd) * + (double) battle_config.death_penalty_job / 10000; + if (battle_config.pk_mode && src && src->type == BL_PC) + sd->status.job_exp -= + (double) pc_nextjobexp (sd) * + (double) battle_config.death_penalty_job / 10000; + else if (battle_config.death_penalty_type == 2 + && battle_config.death_penalty_job > 0) + { + if (pc_nextjobexp (sd) > 0) + sd->status.job_exp -= + (double) sd->status.job_exp * + (double) battle_config.death_penalty_job / 10000; + if (battle_config.pk_mode && src && src->type == BL_PC) + sd->status.job_exp -= + (double) sd->status.job_exp * + (double) battle_config.death_penalty_job / 10000; + } + if (sd->status.job_exp < 0) + sd->status.job_exp = 0; + clif_updatestatus (sd, SP_JOBEXP); + } + } + //ナイトメアモードアイテムドロップ + if (map[sd->bl.m].flag.pvp_nightmaredrop) + { // Moved this outside so it works when PVP isnt enabled and during pk mode [Ancyker] + for (j = 0; j < MAX_DROP_PER_MAP; j++) + { + int id = map[sd->bl.m].drop_list[j].drop_id; + int type = map[sd->bl.m].drop_list[j].drop_type; + int per = map[sd->bl.m].drop_list[j].drop_per; + if (id == 0) + continue; + if (id == -1) + { //ランダムドロップ + int eq_num = 0, eq_n[MAX_INVENTORY]; + memset (eq_n, 0, sizeof (eq_n)); + //先ず装備しているアイテム数をカウント + for (i = 0; i < MAX_INVENTORY; i++) + { + int k; + if ((type == 1 && !sd->status.inventory[i].equip) + || (type == 2 && sd->status.inventory[i].equip) + || type == 3) + { + //InventoryIndexを格納 + for (k = 0; k < MAX_INVENTORY; k++) + { + if (eq_n[k] <= 0) + { + eq_n[k] = i; + break; + } + } + eq_num++; + } + } + if (eq_num > 0) + { + int n = eq_n[MRAND (eq_num)]; //該当アイテムの中からランダム + if (MRAND (10000) < per) + { + if (sd->status.inventory[n].equip) + pc_unequipitem (sd, n, 0); + pc_dropitem (sd, n, 1); + } + } + } + else if (id > 0) + { + for (i = 0; i < MAX_INVENTORY; i++) + { + if (sd->status.inventory[i].nameid == id //ItemIDが一致していて + && MRAND (10000) < per //ドロップ率判定もOKで + && ((type == 1 && !sd->status.inventory[i].equip) //タイプ判定もOKならドロップ + || (type == 2 && sd->status.inventory[i].equip) + || type == 3)) + { + if (sd->status.inventory[i].equip) + pc_unequipitem (sd, i, 0); + pc_dropitem (sd, i, 1); + break; + } + } + } + } + } + // pvp + if (map[sd->bl.m].flag.pvp && !battle_config.pk_mode) + { // disable certain pvp functions on pk_mode [Valaris] + //ランキング計算 + if (!map[sd->bl.m].flag.pvp_nocalcrank) + { + sd->pvp_point -= 5; + if (src && src->type == BL_PC) + ((struct map_session_data *) src)->pvp_point++; + //} //fixed wrong '{' placement by Lupus + pc_setdead (sd); + } + // 強制送還 + if (sd->pvp_point < 0) + { + sd->pvp_point = 0; + pc_setstand (sd); + pc_setrestartvalue (sd, 3); + pc_setpos (sd, sd->status.save_point.map, sd->status.save_point.x, + sd->status.save_point.y, 0); + } + } + //GvG + if (map[sd->bl.m].flag.gvg) + { + pc_setstand (sd); + pc_setrestartvalue (sd, 3); + pc_setpos (sd, sd->status.save_point.map, sd->status.save_point.x, + sd->status.save_point.y, 0); + } + + if (src && src->type == BL_PC) + { + // [Fate] PK death, trigger scripts + argrec_t arg[3]; + arg[0].name = "@killerrid"; + arg[0].v.i = src->id; + arg[1].name = "@victimrid"; + arg[1].v.i = sd->bl.id; + arg[2].name = "@victimlvl"; + arg[2].v.i = sd->status.base_level; + npc_event_doall_l ("OnPCKilledEvent", sd->bl.id, 3, arg); + npc_event_doall_l ("OnPCKillEvent", src->id, 3, arg); + } + npc_event_doall_l ("OnPCDieEvent", sd->bl.id, 0, NULL); + + return 0; +} + +// +// script関 連 +// +/*========================================== + * script用PCステータス読み出し + *------------------------------------------ + */ +int pc_readparam (struct map_session_data *sd, int type) +{ + int val = 0; + struct pc_base_job s_class; + + s_class = pc_calc_base_job (sd->status.pc_class); + + nullpo_retr (0, sd); + + switch (type) + { + case SP_SKILLPOINT: + val = sd->status.skill_point; + break; + case SP_STATUSPOINT: + val = sd->status.status_point; + break; + case SP_ZENY: + val = sd->status.zeny; + break; + case SP_BASELEVEL: + val = sd->status.base_level; + break; + case SP_JOBLEVEL: + val = sd->status.job_level; + break; + case SP_CLASS: + if (val >= 24 && val < 45) + val += 3978; + else + val = sd->status.pc_class; + break; + case SP_UPPER: + val = s_class.upper; + break; + case SP_SEX: + val = sd->sex; + break; + case SP_WEIGHT: + val = sd->weight; + break; + case SP_MAXWEIGHT: + val = sd->max_weight; + break; + case SP_BASEEXP: + val = sd->status.base_exp; + break; + case SP_JOBEXP: + val = sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + val = pc_nextbaseexp (sd); + break; + case SP_NEXTJOBEXP: + val = pc_nextjobexp (sd); + break; + case SP_HP: + val = sd->status.hp; + break; + case SP_MAXHP: + val = sd->status.max_hp; + break; + case SP_SP: + val = sd->status.sp; + break; + case SP_MAXSP: + val = sd->status.max_sp; + break; + case SP_STR: + val = sd->status.str; + break; + case SP_AGI: + val = sd->status.agi; + break; + case SP_VIT: + val = sd->status.vit; + break; + case SP_INT: + val = sd->status.int_; + break; + case SP_DEX: + val = sd->status.dex; + break; + case SP_LUK: + val = sd->status.luk; + break; + case SP_FAME: + val = sd->fame; + break; + } + + return val; +} + +/*========================================== + * script用PCステータス設定 + *------------------------------------------ + */ +int pc_setparam (struct map_session_data *sd, int type, int val) +{ + int i = 0, up_level = 50; + struct pc_base_job s_class; + + nullpo_retr (0, sd); + + s_class = pc_calc_base_job (sd->status.pc_class); + + switch (type) + { + case SP_BASELEVEL: + if (val > sd->status.base_level) + { + for (i = 1; i <= (val - sd->status.base_level); i++) + sd->status.status_point += + (sd->status.base_level + i + 14) / 4; + } + sd->status.base_level = val; + sd->status.base_exp = 0; + clif_updatestatus (sd, SP_BASELEVEL); + clif_updatestatus (sd, SP_NEXTBASEEXP); + clif_updatestatus (sd, SP_STATUSPOINT); + clif_updatestatus (sd, SP_BASEEXP); + pc_calcstatus (sd, 0); + pc_heal (sd, sd->status.max_hp, sd->status.max_sp); + break; + case SP_JOBLEVEL: + if (sd->status.pc_class == 0) + up_level -= 40; + if ((sd->status.pc_class == 23) + || (sd->status.pc_class >= 4001 && sd->status.pc_class <= 4022)) + up_level += 20; + if (val >= sd->status.job_level) + { + if (val > up_level) + val = up_level; + sd->status.skill_point += (val - sd->status.job_level); + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus (sd, SP_JOBLEVEL); + clif_updatestatus (sd, SP_NEXTJOBEXP); + clif_updatestatus (sd, SP_JOBEXP); + clif_updatestatus (sd, SP_SKILLPOINT); + pc_calcstatus (sd, 0); + clif_misceffect (&sd->bl, 1); + } + else + { + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus (sd, SP_JOBLEVEL); + clif_updatestatus (sd, SP_NEXTJOBEXP); + clif_updatestatus (sd, SP_JOBEXP); + pc_calcstatus (sd, 0); + } + clif_updatestatus (sd, type); + break; + case SP_SKILLPOINT: + sd->status.skill_point = val; + break; + case SP_STATUSPOINT: + sd->status.status_point = val; + break; + case SP_ZENY: + sd->status.zeny = val; + break; + case SP_BASEEXP: + if (pc_nextbaseexp (sd) > 0) + { + sd->status.base_exp = val; + if (sd->status.base_exp < 0) + sd->status.base_exp = 0; + pc_checkbaselevelup (sd); + } + break; + case SP_JOBEXP: + if (pc_nextjobexp (sd) > 0) + { + sd->status.job_exp = val; + if (sd->status.job_exp < 0) + sd->status.job_exp = 0; + pc_checkjoblevelup (sd); + } + break; + case SP_SEX: + sd->sex = val; + break; + case SP_WEIGHT: + sd->weight = val; + break; + case SP_MAXWEIGHT: + sd->max_weight = val; + break; + case SP_HP: + sd->status.hp = val; + break; + case SP_MAXHP: + sd->status.max_hp = val; + break; + case SP_SP: + sd->status.sp = val; + break; + case SP_MAXSP: + sd->status.max_sp = val; + break; + case SP_STR: + sd->status.str = val; + break; + case SP_AGI: + sd->status.agi = val; + break; + case SP_VIT: + sd->status.vit = val; + break; + case SP_INT: + sd->status.int_ = val; + break; + case SP_DEX: + sd->status.dex = val; + break; + case SP_LUK: + sd->status.luk = val; + break; + case SP_FAME: + sd->fame = val; + break; + } + clif_updatestatus (sd, type); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_heal (struct map_session_data *sd, int hp, int sp) +{ +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr (0, sd); + + if (pc_checkoverhp (sd)) + { + if (hp > 0) + hp = 0; + } + if (pc_checkoversp (sd)) + { + if (sp > 0) + sp = 0; + } + + if (sd->sc_data && sd->sc_data[SC_BERSERK].timer != -1) //バーサーク中は回復させないらしい + return 0; + + if (hp + sd->status.hp > sd->status.max_hp) + hp = sd->status.max_hp - sd->status.hp; + if (sp + sd->status.sp > sd->status.max_sp) + sp = sd->status.max_sp - sd->status.sp; + sd->status.hp += hp; + if (sd->status.hp <= 0) + { + sd->status.hp = 0; + pc_damage (NULL, sd, 1); + hp = 0; + } + sd->status.sp += sp; + if (sd->status.sp <= 0) + sd->status.sp = 0; + if (hp) + clif_updatestatus (sd, SP_HP); + if (sp) + clif_updatestatus (sd, SP_SP); + + if (sd->status.party_id > 0) + { // on-the-fly party hp updates [Valaris] + struct party *p = party_search (sd->status.party_id); + if (p != NULL) + clif_party_hp (p, sd); + } // end addition [Valaris] + + return hp + sp; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +static int pc_itemheal_effect (struct map_session_data *sd, int hp, int sp); + +static int // Compute how quickly we regenerate (less is faster) for that amount +pc_heal_quick_speed (int amount) +{ + if (amount >= 100) + { + if (amount >= 500) + return 0; + if (amount >= 250) + return 1; + return 2; + } + else + { // < 100 + if (amount >= 50) + return 3; + if (amount >= 20) + return 4; + return 5; + } +} + +static void +pc_heal_quick_accumulate (int new_amount, + struct quick_regeneration *quick_regen, int max) +{ + int current_amount = quick_regen->amount; + int current_speed = quick_regen->speed; + int new_speed = pc_heal_quick_speed (new_amount); + + int average_speed = ((new_speed * new_amount) + (current_speed * current_amount)) / (current_amount + new_amount); // new_amount > 0, current_amount >= 0 + + quick_regen->speed = average_speed; + quick_regen->amount = MIN (current_amount + new_amount, max); + + quick_regen->tickdelay = MIN (quick_regen->speed, quick_regen->tickdelay); +} + +int pc_itemheal (struct map_session_data *sd, int hp, int sp) +{ + /* defer healing */ + if (hp > 0) + { + pc_heal_quick_accumulate (hp, + &sd->quick_regeneration_hp, + sd->status.max_hp - sd->status.hp); + hp = 0; + } + if (sp > 0) + { + pc_heal_quick_accumulate (sp, + &sd->quick_regeneration_sp, + sd->status.max_sp - sd->status.sp); + + sp = 0; + } + + /* Hurt right away, if necessary */ + if (hp < 0 || sp < 0) + pc_itemheal_effect (sd, hp, sp); + + return 0; +} + +/* pc_itemheal_effect is invoked once every 0.5s whenever the pc + * has health recovery queued up (cf. pc_natural_heal_sub). + */ +static int pc_itemheal_effect (struct map_session_data *sd, int hp, int sp) +{ + int bonus; +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr (0, sd); + + if (sd->sc_data && sd->sc_data[SC_GOSPEL].timer != -1) //バーサーク中は回復させないらしい + return 0; + + if (sd->state.potionpitcher_flag) + { + sd->potion_hp = hp; + sd->potion_sp = sp; + return 0; + } + + if (pc_checkoverhp (sd)) + { + if (hp > 0) + hp = 0; + } + if (pc_checkoversp (sd)) + { + if (sp > 0) + sp = 0; + } + if (hp > 0) + { + bonus = + (sd->paramc[2] << 1) + 100 + pc_checkskill (sd, SM_RECOVERY) * 10; + if (bonus != 100) + hp = hp * bonus / 100; + bonus = 100 + pc_checkskill (sd, AM_LEARNINGPOTION) * 5; + if (bonus != 100) + hp = hp * bonus / 100; + } + if (sp > 0) + { + bonus = + (sd->paramc[3] << 1) + 100 + pc_checkskill (sd, + MG_SRECOVERY) * 10; + if (bonus != 100) + sp = sp * bonus / 100; + bonus = 100 + pc_checkskill (sd, AM_LEARNINGPOTION) * 5; + if (bonus != 100) + sp = sp * bonus / 100; + } + if (hp + sd->status.hp > sd->status.max_hp) + hp = sd->status.max_hp - sd->status.hp; + if (sp + sd->status.sp > sd->status.max_sp) + sp = sd->status.max_sp - sd->status.sp; + sd->status.hp += hp; + if (sd->status.hp <= 0) + { + sd->status.hp = 0; + pc_damage (NULL, sd, 1); + hp = 0; + } + sd->status.sp += sp; + if (sd->status.sp <= 0) + sd->status.sp = 0; + if (hp) + clif_updatestatus (sd, SP_HP); + if (sp) + clif_updatestatus (sd, SP_SP); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_percentheal (struct map_session_data *sd, int hp, int sp) +{ + nullpo_retr (0, sd); + + if (sd->state.potionpitcher_flag) + { + sd->potion_per_hp = hp; + sd->potion_per_sp = sp; + return 0; + } + + if (pc_checkoverhp (sd)) + { + if (hp > 0) + hp = 0; + } + if (pc_checkoversp (sd)) + { + if (sp > 0) + sp = 0; + } + if (hp) + { + if (hp >= 100) + { + sd->status.hp = sd->status.max_hp; + } + else if (hp <= -100) + { + sd->status.hp = 0; + pc_damage (NULL, sd, 1); + } + else + { + sd->status.hp += sd->status.max_hp * hp / 100; + if (sd->status.hp > sd->status.max_hp) + sd->status.hp = sd->status.max_hp; + if (sd->status.hp <= 0) + { + sd->status.hp = 0; + pc_damage (NULL, sd, 1); + hp = 0; + } + } + } + if (sp) + { + if (sp >= 100) + { + sd->status.sp = sd->status.max_sp; + } + else if (sp <= -100) + { + sd->status.sp = 0; + } + else + { + sd->status.sp += sd->status.max_sp * sp / 100; + if (sd->status.sp > sd->status.max_sp) + sd->status.sp = sd->status.max_sp; + if (sd->status.sp < 0) + sd->status.sp = 0; + } + } + if (hp) + clif_updatestatus (sd, SP_HP); + if (sp) + clif_updatestatus (sd, SP_SP); + + return 0; +} + +/*========================================== + * 職変更 + * 引数 job 職業 0〜23 + * upper 通常 0, 転生 1, 養子 2, そのまま -1 + *------------------------------------------ + */ +int pc_jobchange (struct map_session_data *sd, int job, int upper) +{ + int i; + int b_class = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class = pc_calc_base_job (sd->status.pc_class); + + nullpo_retr (0, sd); + + if ((job > 23) && (job < 68)) + job += 3977; + + if ((job > 69) && (job < 4000)) + return 1; + + if (upper < 0) //現在転生かどうかを判断する + upper = s_class.upper; + + if (upper == 0) + { //通常職ならjobそのまんま + b_class = job; + } + else if (upper == 1) + { + if (job == 23) + { //転生にスパノビは存在しないのでお断り + return 1; + } + else + { + b_class = job + 4001; + } + } + else if (upper == 2) + { //養子に結婚はないけどどうせ次で蹴られるからいいや + b_class = (job == 23) ? job + 4022 : job + 4023; + } + else + { + return 1; + } + + if ((sd->status.sex == 0 && job == 19) || (sd->status.sex == 1 && job == 20) || + (sd->status.sex == 0 && job == 4020) || (sd->status.sex == 1 && job == 4021) || + job == 22 || sd->status.pc_class == b_class) //♀はバードになれない、♂はダンサーになれない、結婚衣裳もお断り + return 1; + + sd->status.pc_class = sd->view_class = b_class; + + sd->status.job_level = 1; + sd->status.job_exp = 0; + clif_updatestatus (sd, SP_JOBLEVEL); + clif_updatestatus (sd, SP_JOBEXP); + clif_updatestatus (sd, SP_NEXTJOBEXP); + + for (i = 0; i < 11; i++) + { + if (sd->equip_index[i] >= 0) + if (!pc_isequip (sd, sd->equip_index[i])) + pc_unequipitem (sd, sd->equip_index[i], 1); // 装備外し + } + + clif_changelook (&sd->bl, LOOK_BASE, sd->view_class); // move sprite update to prevent client crashes with incompatible equipment [Valaris] + if (sd->status.clothes_color > 0) + clif_changelook (&sd->bl, LOOK_CLOTHES_COLOR, + sd->status.clothes_color); + if (battle_config.muting_players && sd->status.manner < 0) + clif_changestatus (&sd->bl, SP_MANNER, sd->status.manner); + + pc_calcstatus (sd, 0); + pc_checkallowskill (sd); + pc_equiplookall (sd); + clif_equiplist (sd); + + if (pc_isriding (sd)) + { // remove peco status if changing into invalid class [Valaris] + if (!(pc_checkskill (sd, KN_RIDING))) + pc_setoption (sd, sd->status.option | -0x0000); + if (pc_checkskill (sd, KN_RIDING) > 0) + pc_setriding (sd); + } + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_equiplookall (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + clif_changelook (&sd->bl, LOOK_WEAPON, 0); +// clif_changelook(&sd->bl,LOOK_SHOES,0); + clif_changelook (&sd->bl, LOOK_HEAD_BOTTOM, sd->status.head_bottom); + clif_changelook (&sd->bl, LOOK_HEAD_TOP, sd->status.head_top); + clif_changelook (&sd->bl, LOOK_HEAD_MID, sd->status.head_mid); + + clif_changelook_accessories (&sd->bl, NULL); + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_changelook (struct map_session_data *sd, int type, int val) +{ + nullpo_retr (0, sd); + + switch (type) + { + case LOOK_HAIR: + sd->status.hair = val; + break; + case LOOK_WEAPON: + sd->status.weapon = val; + break; + case LOOK_HEAD_BOTTOM: + sd->status.head_bottom = val; + break; + case LOOK_HEAD_TOP: + sd->status.head_top = val; + break; + case LOOK_HEAD_MID: + sd->status.head_mid = val; + break; + case LOOK_HAIR_COLOR: + sd->status.hair_color = val; + break; + case LOOK_CLOTHES_COLOR: + sd->status.clothes_color = val; + break; + case LOOK_SHIELD: + sd->status.shield = val; + break; + case LOOK_SHOES: + break; + } + clif_changelook (&sd->bl, type, val); + + return 0; +} + +/*========================================== + * 付属品(鷹,ペコ,カート)設定 + *------------------------------------------ + */ +int pc_setoption (struct map_session_data *sd, int type) +{ + nullpo_retr (0, sd); + + sd->status.option = type; + clif_changeoption (&sd->bl); + pc_calcstatus (sd, 0); + + return 0; +} + +/*========================================== + * カート設定 + *------------------------------------------ + */ +int pc_setcart (struct map_session_data *sd, int type) +{ + int cart[6] = { 0x0000, 0x0008, 0x0080, 0x0100, 0x0200, 0x0400 }; + + nullpo_retr (0, sd); + + if (pc_checkskill (sd, MC_PUSHCART) > 0) + { // プッシュカートスキル所持 + if (!pc_iscarton (sd)) + { // カートを付けていない + pc_setoption (sd, cart[type]); + clif_cart_itemlist (sd); + clif_cart_equiplist (sd); + clif_updatestatus (sd, SP_CARTINFO); + clif_status_change (&sd->bl, 0x0c, 0); + } + else + { + pc_setoption (sd, cart[type]); + } + } + + return 0; +} + +/*========================================== + * 鷹設定 + *------------------------------------------ + */ +int pc_setfalcon (struct map_session_data *sd) +{ + if (pc_checkskill (sd, HT_FALCON) > 0) + { // ファルコンマスタリースキル所持 + pc_setoption (sd, sd->status.option | 0x0010); + } + + return 0; +} + +/*========================================== + * ペコペコ設定 + *------------------------------------------ + */ +int pc_setriding (struct map_session_data *sd) +{ + if (sd->disguise > 0) + { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage (sd->fd, + "Cannot mount a Peco while in disguise."); + return 0; + } + + if ((pc_checkskill (sd, KN_RIDING) > 0)) + { // ライディングスキル所持 + pc_setoption (sd, sd->status.option | 0x0020); + + if (sd->status.pc_class == 7) + sd->status.pc_class = sd->view_class = 13; + + if (sd->status.pc_class == 14) + sd->status.pc_class = sd->view_class = 21; + + if (sd->status.pc_class == 4008) + sd->status.pc_class = sd->view_class = 4014; + + if (sd->status.pc_class == 4015) + sd->status.pc_class = sd->view_class = 4022; + } + + return 0; +} + +/*========================================== + * script用変数の値を読む + *------------------------------------------ + */ +int pc_readreg (struct map_session_data *sd, int reg) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->reg_num; i++) + if (sd->reg[i].index == reg) + return sd->reg[i].data; + + return 0; +} + +/*========================================== + * script用変数の値を設定 + *------------------------------------------ + */ +int pc_setreg (struct map_session_data *sd, int reg, int val) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->reg_num; i++) + { + if (sd->reg[i].index == reg) + { + sd->reg[i].data = val; + return 0; + } + } + sd->reg_num++; + RECREATE (sd->reg, struct script_reg, sd->reg_num); + sd->reg[i].index = reg; + sd->reg[i].data = val; + + return 0; +} + +/*========================================== + * script用文字列変数の値を読む + *------------------------------------------ + */ +char *pc_readregstr (struct map_session_data *sd, int reg) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->regstr_num; i++) + if (sd->regstr[i].index == reg) + return sd->regstr[i].data; + + return NULL; +} + +/*========================================== + * script用文字列変数の値を設定 + *------------------------------------------ + */ +int pc_setregstr (struct map_session_data *sd, int reg, const char *str) +{ + int i; + + nullpo_retr (0, sd); + + if (strlen (str) + 1 > sizeof (sd->regstr[0].data)) + { + printf ("pc_setregstr(): String too long!\n"); + return 0; + } + + for (i = 0; i < sd->regstr_num; i++) + if (sd->regstr[i].index == reg) + { + strcpy (sd->regstr[i].data, str); + return 0; + } + sd->regstr_num++; + RECREATE (sd->regstr, struct script_regstr, sd->regstr_num); + sd->regstr[i].index = reg; + strcpy (sd->regstr[i].data, str); + + return 0; +} + +/*========================================== + * script用グローバル変数の値を読む + *------------------------------------------ + */ +int pc_readglobalreg (struct map_session_data *sd, const char *reg) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->status.global_reg_num; i++) + { + if (strcmp (sd->status.global_reg[i].str, reg) == 0) + return sd->status.global_reg[i].value; + } + + return 0; +} + +/*========================================== + * script用グローバル変数の値を設定 + *------------------------------------------ + */ +int pc_setglobalreg (struct map_session_data *sd, const char *reg, int val) +{ + int i; + + nullpo_retr (0, sd); + + //PC_DIE_COUNTERがスクリプトなどで変更された時の処理 + if (strcmp (reg, "PC_DIE_COUNTER") == 0 && sd->die_counter != val) + { + sd->die_counter = val; + pc_calcstatus (sd, 0); + } + if (val == 0) + { + for (i = 0; i < sd->status.global_reg_num; i++) + { + if (strcmp (sd->status.global_reg[i].str, reg) == 0) + { + sd->status.global_reg[i] = + sd->status.global_reg[sd->status.global_reg_num - 1]; + sd->status.global_reg_num--; + break; + } + } + return 0; + } + for (i = 0; i < sd->status.global_reg_num; i++) + { + if (strcmp (sd->status.global_reg[i].str, reg) == 0) + { + sd->status.global_reg[i].value = val; + return 0; + } + } + if (sd->status.global_reg_num < GLOBAL_REG_NUM) + { + strcpy (sd->status.global_reg[i].str, reg); + sd->status.global_reg[i].value = val; + sd->status.global_reg_num++; + return 0; + } + if (battle_config.error_log) + printf ("pc_setglobalreg : couldn't set %s (GLOBAL_REG_NUM = %d)\n", + reg, GLOBAL_REG_NUM); + + return 1; +} + +/*========================================== + * script用アカウント変数の値を読む + *------------------------------------------ + */ +int pc_readaccountreg (struct map_session_data *sd, const char *reg) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->status.account_reg_num; i++) + { + if (strcmp (sd->status.account_reg[i].str, reg) == 0) + return sd->status.account_reg[i].value; + } + + return 0; +} + +/*========================================== + * script用アカウント変数の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg (struct map_session_data *sd, const char *reg, int val) +{ + int i; + + nullpo_retr (0, sd); + + if (val == 0) + { + for (i = 0; i < sd->status.account_reg_num; i++) + { + if (strcmp (sd->status.account_reg[i].str, reg) == 0) + { + sd->status.account_reg[i] = + sd->status.account_reg[sd->status.account_reg_num - 1]; + sd->status.account_reg_num--; + break; + } + } + intif_saveaccountreg (sd); + return 0; + } + for (i = 0; i < sd->status.account_reg_num; i++) + { + if (strcmp (sd->status.account_reg[i].str, reg) == 0) + { + sd->status.account_reg[i].value = val; + intif_saveaccountreg (sd); + return 0; + } + } + if (sd->status.account_reg_num < ACCOUNT_REG_NUM) + { + strcpy (sd->status.account_reg[i].str, reg); + sd->status.account_reg[i].value = val; + sd->status.account_reg_num++; + intif_saveaccountreg (sd); + return 0; + } + if (battle_config.error_log) + printf ("pc_setaccountreg : couldn't set %s (ACCOUNT_REG_NUM = %d)\n", + reg, ACCOUNT_REG_NUM); + + return 1; +} + +/*========================================== + * script用アカウント変数2の値を読む + *------------------------------------------ + */ +int pc_readaccountreg2 (struct map_session_data *sd, const char *reg) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < sd->status.account_reg2_num; i++) + { + if (strcmp (sd->status.account_reg2[i].str, reg) == 0) + return sd->status.account_reg2[i].value; + } + + return 0; +} + +/*========================================== + * script用アカウント変数2の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg2 (struct map_session_data *sd, const char *reg, int val) +{ + int i; + + nullpo_retr (1, sd); + + if (val == 0) + { + for (i = 0; i < sd->status.account_reg2_num; i++) + { + if (strcmp (sd->status.account_reg2[i].str, reg) == 0) + { + sd->status.account_reg2[i] = + sd->status.account_reg2[sd->status.account_reg2_num - 1]; + sd->status.account_reg2_num--; + break; + } + } + chrif_saveaccountreg2 (sd); + return 0; + } + for (i = 0; i < sd->status.account_reg2_num; i++) + { + if (strcmp (sd->status.account_reg2[i].str, reg) == 0) + { + sd->status.account_reg2[i].value = val; + chrif_saveaccountreg2 (sd); + return 0; + } + } + if (sd->status.account_reg2_num < ACCOUNT_REG2_NUM) + { + strcpy (sd->status.account_reg2[i].str, reg); + sd->status.account_reg2[i].value = val; + sd->status.account_reg2_num++; + chrif_saveaccountreg2 (sd); + return 0; + } + if (battle_config.error_log) + printf + ("pc_setaccountreg2 : couldn't set %s (ACCOUNT_REG2_NUM = %d)\n", + reg, ACCOUNT_REG2_NUM); + + return 1; +} + +/*========================================== + * 精錬成功率 + *------------------------------------------ + */ +int pc_percentrefinery (struct map_session_data *sd, struct item *item) +{ + int percent; + + nullpo_retr (0, item); + percent = percentrefinery[itemdb_wlv (item->nameid)][(int) item->refine]; + + percent += pc_checkskill (sd, BS_WEAPONRESEARCH); // 武器研究スキル所持 + + // 確率の有効範囲チェック + if (percent > 100) + { + percent = 100; + } + if (percent < 0) + { + percent = 0; + } + + return percent; +} + +/*========================================== + * イベントタイマー処理 + *------------------------------------------ + */ +static +void pc_eventtimer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd = map_id2sd (id); + int i; + if (sd == NULL) + return; + + for (i = 0; i < MAX_EVENTTIMER; i++) + { + if (sd->eventtimer[i] == tid) + { + sd->eventtimer[i] = -1; + npc_event (sd, (const char *) data, 0); + break; + } + } + free ((void *) data); + if (i == MAX_EVENTTIMER) + { + if (battle_config.error_log) + printf ("pc_eventtimer: no such event timer\n"); + } +} + +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +int pc_addeventtimer (struct map_session_data *sd, int tick, const char *name) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < MAX_EVENTTIMER; i++) + if (sd->eventtimer[i] == -1) + break; + + if (i < MAX_EVENTTIMER) + { + char *evname = (char *) calloc (24, 1); + strncpy (evname, name, 24); + evname[23] = '\0'; + sd->eventtimer[i] = add_timer (gettick () + tick, + pc_eventtimer, sd->bl.id, + (int) evname); + return 1; + } + + return 0; +} + +/*========================================== + * イベントタイマー削除 + *------------------------------------------ + */ +int pc_deleventtimer (struct map_session_data *sd, const char *name) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < MAX_EVENTTIMER; i++) + if (sd->eventtimer[i] != -1 && strcmp ((char + *) (get_timer (sd->eventtimer + [i])->data), + name) == 0) + { + delete_timer (sd->eventtimer[i], pc_eventtimer); + sd->eventtimer[i] = -1; + break; + } + + return 0; +} + +/*========================================== + * イベントタイマーカウント値追加 + *------------------------------------------ + */ +int pc_addeventtimercount (struct map_session_data *sd, const char *name, + int tick) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < MAX_EVENTTIMER; i++) + if (sd->eventtimer[i] != -1 && strcmp ((char + *) (get_timer (sd->eventtimer + [i])->data), + name) == 0) + { + addtick_timer (sd->eventtimer[i], tick); + break; + } + + return 0; +} + +/*========================================== + * イベントタイマー全削除 + *------------------------------------------ + */ +int pc_cleareventtimer (struct map_session_data *sd) +{ + int i; + + nullpo_retr (0, sd); + + for (i = 0; i < MAX_EVENTTIMER; i++) + if (sd->eventtimer[i] != -1) + { + delete_timer (sd->eventtimer[i], pc_eventtimer); + sd->eventtimer[i] = -1; + } + + return 0; +} + +// +// 装 備物 +// +/*========================================== + * アイテムを装備する + *------------------------------------------ + */ +static int +pc_signal_advanced_equipment_change (struct map_session_data *sd, int n) +{ + if (sd->status.inventory[n].equip & 0x0040) + clif_changelook (&sd->bl, LOOK_SHOES, 0); + if (sd->status.inventory[n].equip & 0x0004) + clif_changelook (&sd->bl, LOOK_GLOVES, 0); + if (sd->status.inventory[n].equip & 0x0008) + clif_changelook (&sd->bl, LOOK_CAPE, 0); + if (sd->status.inventory[n].equip & 0x0010) + clif_changelook (&sd->bl, LOOK_MISC1, 0); + if (sd->status.inventory[n].equip & 0x0080) + clif_changelook (&sd->bl, LOOK_MISC2, 0); + return 0; +} + +int pc_equipitem (struct map_session_data *sd, int n, int pos) +{ + int i, nameid, arrow, view; + struct item_data *id; + //ソス]ソスソスソスソスソス{ソスqソスフ場合ソスフ鯉ソスソスフ職ソスニゑソスソスZソスoソスソスソスソス + + nullpo_retr (0, sd); + + if (n < 0 || n >= MAX_INVENTORY) + { + clif_equipitemack (sd, 0, 0, 0); + return 0; + } + + nameid = sd->status.inventory[n].nameid; + id = sd->inventory_data[n]; + pos = pc_equippoint (sd, n); + + if (battle_config.battle_log) + printf ("equip %d(%d) %x:%x\n", nameid, n, id->equip, pos); + if (!pc_isequip (sd, n) || !pos || sd->status.inventory[n].broken == 1) + { // [Valaris] + clif_equipitemack (sd, n, 0, 0); // fail + return 0; + } + +// -- moonsoul (if player is berserk then cannot equip) +// + if (sd->sc_data[SC_BERSERK].timer != -1) + { + clif_equipitemack (sd, n, 0, 0); // fail + return 0; + } + + if (pos == 0x88) + { // アクセサリ用例外処理 + int epor = 0; + if (sd->equip_index[0] >= 0) + epor |= sd->status.inventory[sd->equip_index[0]].equip; + if (sd->equip_index[1] >= 0) + epor |= sd->status.inventory[sd->equip_index[1]].equip; + epor &= 0x88; + pos = epor == 0x08 ? 0x80 : 0x08; + } + + // 二刀流処理 + if ((pos == 0x22) // 一応、装備要求箇所が二刀流武器かチェックする + && (id->equip == 2) // 単 手武器 + && (pc_checkskill (sd, AS_LEFT) > 0 || sd->status.pc_class == 12)) // 左手修錬有 + { + int tpos = 0; + if (sd->equip_index[8] >= 0) + tpos |= sd->status.inventory[sd->equip_index[8]].equip; + if (sd->equip_index[9] >= 0) + tpos |= sd->status.inventory[sd->equip_index[9]].equip; + tpos &= 0x02; + pos = tpos == 0x02 ? 0x20 : 0x02; + } + + arrow = pc_search_inventory (sd, pc_checkequip (sd, 9)); // Added by RoVeRT + for (i = 0; i < 11; i++) + { + if (pos & equip_pos[i]) + { + if (sd->equip_index[i] >= 0) //Slot taken, remove item from there. + pc_unequipitem (sd, sd->equip_index[i], 1); + sd->equip_index[i] = n; + } + } + // 弓矢装備 + if (pos == 0x8000) + { + clif_arrowequip (sd, n); + clif_arrow_fail (sd, 3); // 3=矢が装備できました + } + else + { + /* Don't update re-equipping if we're using a spell */ + if (!(pos == 4 && sd->attack_spell_override)) + clif_equipitemack (sd, n, pos, 1); + } + + for (i = 0; i < 11; i++) + { + if (pos & equip_pos[i]) + sd->equip_index[i] = n; + } + sd->status.inventory[n].equip = pos; + + if (sd->inventory_data[n]) + { + view = sd->inventory_data[n]->look; + if (view == 0) + view = sd->inventory_data[n]->nameid; + } + else + { + view = 0; + } + + if (sd->status.inventory[n].equip & 0x0002) + { + sd->weapontype1 = view; + pc_calcweapontype (sd); + pc_set_weapon_look (sd); + } + if (sd->status.inventory[n].equip & 0x0020) + { + if (sd->inventory_data[n]) + { + if (sd->inventory_data[n]->type == 4) + { + sd->status.shield = 0; + if (sd->status.inventory[n].equip == 0x0020) + sd->weapontype2 = view; + } + else if (sd->inventory_data[n]->type == 5) + { + sd->status.shield = view; + sd->weapontype2 = 0; + } + } + else + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype (sd); + clif_changelook (&sd->bl, LOOK_SHIELD, sd->status.shield); + } + if (sd->status.inventory[n].equip & 0x0001) + { + sd->status.head_bottom = view; + clif_changelook (&sd->bl, LOOK_HEAD_BOTTOM, sd->status.head_bottom); + } + if (sd->status.inventory[n].equip & 0x0100) + { + sd->status.head_top = view; + clif_changelook (&sd->bl, LOOK_HEAD_TOP, sd->status.head_top); + } + if (sd->status.inventory[n].equip & 0x0200) + { + sd->status.head_mid = view; + clif_changelook (&sd->bl, LOOK_HEAD_MID, sd->status.head_mid); + } + pc_signal_advanced_equipment_change (sd, n); + + pc_checkallowskill (sd); // 装備品でスキルか解除されるかチェック + if (itemdb_look (sd->status.inventory[n].nameid) == 11 && arrow) + { // Added by RoVeRT + clif_arrowequip (sd, arrow); + sd->status.inventory[arrow].equip = 32768; + } + pc_calcstatus (sd, 0); + + if (sd->special_state.infinite_endure) + { + if (sd->sc_data[SC_ENDURE].timer == -1) + skill_status_change_start (&sd->bl, SC_ENDURE, 10, 1, 0, 0, 0, 0); + } + else + { + if (sd->sc_data[SC_ENDURE].timer != -1 && sd->sc_data[SC_ENDURE].val2) + skill_status_change_end (&sd->bl, SC_ENDURE, -1); + } + + if (sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 + && !battle_check_undead (7, sd->def_ele)) + skill_status_change_end (&sd->bl, SC_SIGNUMCRUCIS, -1); + if (sd->sc_data[SC_DANCING].timer != -1 + && (sd->status.weapon != 13 && sd->status.weapon != 14)) + skill_stop_dancing (&sd->bl, 0); + + return 0; +} + +/*========================================== + * 装 備した物を外す + *------------------------------------------ + */ +int pc_unequipitem (struct map_session_data *sd, int n, int type) +{ + nullpo_retr (0, sd); + +// -- moonsoul (if player is berserk then cannot unequip) +// + if (sd->sc_data[SC_BERSERK].timer != -1) + { + clif_unequipitemack (sd, n, 0, 0); + return 0; + } + + if (battle_config.battle_log) + printf ("unequip %d %x:%x\n", n, pc_equippoint (sd, n), + sd->status.inventory[n].equip); + if (sd->status.inventory[n].equip) + { + int i; + for (i = 0; i < 11; i++) + { + if (sd->status.inventory[n].equip & equip_pos[i]) + sd->equip_index[i] = -1; + } + if (sd->status.inventory[n].equip & 0x0002) + { + sd->weapontype1 = 0; + sd->status.weapon = sd->weapontype2; + pc_calcweapontype (sd); + pc_set_weapon_look (sd); + } + if (sd->status.inventory[n].equip & 0x0020) + { + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype (sd); + clif_changelook (&sd->bl, LOOK_SHIELD, sd->status.shield); + } + if (sd->status.inventory[n].equip & 0x0001) + { + sd->status.head_bottom = 0; + clif_changelook (&sd->bl, LOOK_HEAD_BOTTOM, + sd->status.head_bottom); + } + if (sd->status.inventory[n].equip & 0x0100) + { + sd->status.head_top = 0; + clif_changelook (&sd->bl, LOOK_HEAD_TOP, sd->status.head_top); + } + if (sd->status.inventory[n].equip & 0x0200) + { + sd->status.head_mid = 0; + clif_changelook (&sd->bl, LOOK_HEAD_MID, sd->status.head_mid); + } + pc_signal_advanced_equipment_change (sd, n); + + if (sd->sc_data[SC_BROKNWEAPON].timer != -1 + && sd->status.inventory[n].equip & 0x0002 + && sd->status.inventory[i].broken == 1) + skill_status_change_end (&sd->bl, SC_BROKNWEAPON, -1); + + clif_unequipitemack (sd, n, sd->status.inventory[n].equip, 1); + sd->status.inventory[n].equip = 0; + if (!type) + pc_checkallowskill (sd); + if (sd->weapontype1 == 0 && sd->weapontype2 == 0) + skill_encchant_eremental_end (&sd->bl, -1); //武器持ち誓えは無条件で属性付与解除 + } + else + { + clif_unequipitemack (sd, n, 0, 0); + } + if (!type) + { + pc_calcstatus (sd, 0); + if (sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 + && !battle_check_undead (7, sd->def_ele)) + skill_status_change_end (&sd->bl, SC_SIGNUMCRUCIS, -1); + } + + return 0; +} + +int pc_unequipinvyitem (struct map_session_data *sd, int n, int type) +{ + int i; + + nullpo_retr (1, sd); + + for (i = 0; i < 11; i++) + { + if (equip_pos[i] > 0 && sd->equip_index[i] == n) + { //Slot taken, remove item from there. + pc_unequipitem (sd, sd->equip_index[i], type); + sd->equip_index[i] = -1; + } + } + + return 0; +} + +/*========================================== + * アイテムのindex番号を詰めたり + * 装 備品の装備可能チェックを行なう + *------------------------------------------ + */ +int pc_checkitem (struct map_session_data *sd) +{ + int i, j, k, id, calc_flag = 0; + struct item_data *it = NULL; + + nullpo_retr (0, sd); + + // 所持品空き詰め + for (i = j = 0; i < MAX_INVENTORY; i++) + { + if ((id = sd->status.inventory[i].nameid) == 0) + continue; + if (battle_config.item_check && !itemdb_available (id)) + { + if (battle_config.error_log) + printf ("illeagal item id %d in %d[%s] inventory.\n", id, + sd->bl.id, sd->status.name); + pc_delitem (sd, i, sd->status.inventory[i].amount, 3); + continue; + } + if (i > j) + { + memcpy (&sd->status.inventory[j], &sd->status.inventory[i], + sizeof (struct item)); + sd->inventory_data[j] = sd->inventory_data[i]; + } + j++; + } + if (j < MAX_INVENTORY) + memset (&sd->status.inventory[j], 0, + sizeof (struct item) * (MAX_INVENTORY - j)); + for (k = j; k < MAX_INVENTORY; k++) + sd->inventory_data[k] = NULL; + + // カート内空き詰め + for (i = j = 0; i < MAX_CART; i++) + { + if ((id = sd->status.cart[i].nameid) == 0) + continue; + if (battle_config.item_check && !itemdb_available (id)) + { + if (battle_config.error_log) + printf ("illeagal item id %d in %d[%s] cart.\n", id, + sd->bl.id, sd->status.name); + pc_cart_delitem (sd, i, sd->status.cart[i].amount, 1); + continue; + } + if (i > j) + { + memcpy (&sd->status.cart[j], &sd->status.cart[i], + sizeof (struct item)); + } + j++; + } + if (j < MAX_CART) + memset (&sd->status.cart[j], 0, + sizeof (struct item) * (MAX_CART - j)); + + // 装 備位置チェック + + for (i = 0; i < MAX_INVENTORY; i++) + { + + it = sd->inventory_data[i]; + + if (sd->status.inventory[i].nameid == 0) + continue; + if (sd->status.inventory[i].equip & ~pc_equippoint (sd, i)) + { + sd->status.inventory[i].equip = 0; + calc_flag = 1; + } + //装備制限チェック + if (sd->status.inventory[i].equip && map[sd->bl.m].flag.pvp + && (it->flag.no_equip == 1 || it->flag.no_equip == 3)) + { //PvP制限 + sd->status.inventory[i].equip = 0; + calc_flag = 1; + } + else if (sd->status.inventory[i].equip && map[sd->bl.m].flag.gvg + && (it->flag.no_equip == 2 || it->flag.no_equip == 3)) + { //GvG制限 + sd->status.inventory[i].equip = 0; + calc_flag = 1; + } + } + + pc_setequipindex (sd); + if (calc_flag) + pc_calcstatus (sd, 2); + + return 0; +} + +int pc_checkoverhp (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->status.hp == sd->status.max_hp) + return 1; + if (sd->status.hp > sd->status.max_hp) + { + sd->status.hp = sd->status.max_hp; + clif_updatestatus (sd, SP_HP); + return 2; + } + + return 0; +} + +int pc_checkoversp (struct map_session_data *sd) +{ + nullpo_retr (0, sd); + + if (sd->status.sp == sd->status.max_sp) + return 1; + if (sd->status.sp > sd->status.max_sp) + { + sd->status.sp = sd->status.max_sp; + clif_updatestatus (sd, SP_SP); + return 2; + } + + return 0; +} + +/*========================================== + * PVP順位計算用(foreachinarea) + *------------------------------------------ + */ +static +int pc_calc_pvprank_sub (struct block_list *bl, va_list ap) +{ + struct map_session_data *sd1, *sd2 = NULL; + + nullpo_retr (0, bl); + nullpo_retr (0, ap); + nullpo_retr (0, sd1 = (struct map_session_data *) bl); + nullpo_retr (0, sd2 = va_arg (ap, struct map_session_data *)); + + if (sd1->pvp_point > sd2->pvp_point) + sd2->pvp_rank++; + return 0; +} + +/*========================================== + * PVP順位計算 + *------------------------------------------ + */ +int pc_calc_pvprank (struct map_session_data *sd) +{ + int old; + struct map_data *m; + + nullpo_retr (0, sd); + nullpo_retr (0, m = &map[sd->bl.m]); + + old = sd->pvp_rank; + + if (!(m->flag.pvp)) + return 0; + sd->pvp_rank = 1; + map_foreachinarea (pc_calc_pvprank_sub, sd->bl.m, 0, 0, m->xs, m->ys, + BL_PC, sd); + if (old != sd->pvp_rank || sd->pvp_lastusers != m->users) + clif_pvpset (sd, sd->pvp_rank, sd->pvp_lastusers = m->users, 0); + return sd->pvp_rank; +} + +/*========================================== + * PVP順位計算(timer) + *------------------------------------------ + */ +void pc_calc_pvprank_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + struct map_session_data *sd = NULL; + if (battle_config.pk_mode) // disable pvp ranking if pk_mode on [Valaris] + return; + + sd = map_id2sd (id); + if (sd == NULL) + return; + sd->pvp_timer = -1; + if (pc_calc_pvprank (sd) > 0) + sd->pvp_timer = add_timer (gettick () + PVP_CALCRANK_INTERVAL, + pc_calc_pvprank_timer, id, data); +} + +/*========================================== + * sdは結婚しているか(既婚の場合は相方のchar_idを返す) + *------------------------------------------ + */ +int pc_ismarried (struct map_session_data *sd) +{ + if (sd == NULL) + return -1; + if (sd->status.partner_id > 0) + return sd->status.partner_id; + else + return 0; +} + +/*========================================== + * sdがdstsdと結婚(dstsd→sdの結婚処理も同時に行う) + *------------------------------------------ + */ +int pc_marriage (struct map_session_data *sd, struct map_session_data *dstsd) +{ + if (sd == NULL || dstsd == NULL || sd->status.partner_id > 0 + || dstsd->status.partner_id > 0) + return -1; + sd->status.partner_id = dstsd->status.char_id; + dstsd->status.partner_id = sd->status.char_id; + return 0; +} + +/*========================================== + * sdが離婚(相手はsd->status.partner_idに依る)(相手も同時に離婚・結婚指輪自動剥奪) + *------------------------------------------ + */ +int pc_divorce (struct map_session_data *sd) +{ + struct map_session_data *p_sd = NULL; + if (sd == NULL || !pc_ismarried (sd)) + return -1; + + // If both are on map server we don't need to bother the char server + if ((p_sd = + map_nick2sd (map_charid2nick (sd->status.partner_id))) != NULL) + { + if (p_sd->status.partner_id != sd->status.char_id + || sd->status.partner_id != p_sd->status.char_id) + { + printf ("pc_divorce: Illegal partner_id sd=%d p_sd=%d\n", + sd->status.partner_id, p_sd->status.partner_id); + return -1; + } + p_sd->status.partner_id = 0; + sd->status.partner_id = 0; + + if (sd->npc_flags.divorce) + { + sd->npc_flags.divorce = 0; + map_scriptcont (sd, sd->npc_id); + } + } + else + chrif_send_divorce (sd->status.char_id); + + return 0; +} + +/*========================================== + * sdの相方のmap_session_dataを返す + *------------------------------------------ + */ +struct map_session_data *pc_get_partner (struct map_session_data *sd) +{ + struct map_session_data *p_sd = NULL; + char *nick; + if (sd == NULL || !pc_ismarried (sd)) + return NULL; + + nick = map_charid2nick (sd->status.partner_id); + + if (nick == NULL) + return NULL; + + if ((p_sd = map_nick2sd (nick)) == NULL) + return NULL; + + return p_sd; +} + +// +// 自然回復物 +// +/*========================================== + * SP回復量計算 + *------------------------------------------ + */ +static int natural_heal_tick, natural_heal_prev_tick, natural_heal_diff_tick; +static int pc_spheal (struct map_session_data *sd) +{ + int a; + struct guild_castle *gc = NULL; + + nullpo_retr (0, sd); + + a = natural_heal_diff_tick; + if (pc_issit (sd)) + a += a; + if (sd->sc_data[SC_MAGNIFICAT].timer != -1) // マグニフィカート + a += a; + + gc = guild_mapname2gc (sd->mapname); // Increased guild castle regen [Valaris] + if (gc) + { + struct guild *g; + g = guild_search (sd->status.guild_id); + if (g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +/*========================================== + * HP回復量計算 + *------------------------------------------ + */ +static int pc_hpheal (struct map_session_data *sd) +{ + int a; + struct guild_castle *gc; + + nullpo_retr (0, sd); + + a = natural_heal_diff_tick; + if (pc_issit (sd)) + a += a; + if (sd->sc_data[SC_MAGNIFICAT].timer != -1) // Modified by RoVeRT + a += a; + + gc = guild_mapname2gc (sd->mapname); // Increased guild castle regen [Valaris] + if (gc) + { + struct guild *g; + g = guild_search (sd->status.guild_id); + if (g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +static int pc_natural_heal_hp (struct map_session_data *sd) +{ + int bhp; + int inc_num, bonus, skill, hp_flag; + + nullpo_retr (0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if (pc_checkoverhp (sd)) + { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + bhp = sd->status.hp; + hp_flag = (pc_checkskill (sd, SM_MOVINGRECOVERY) > 0 + && sd->walktimer != -1); + + if (sd->walktimer == -1) + { + inc_num = pc_hpheal (sd); + if (sd->sc_data[SC_TENSIONRELAX].timer != -1) + { // テンションリラックス + sd->hp_sub += 2 * inc_num; + sd->inchealhptick += 3 * natural_heal_diff_tick; + } + else + { + sd->hp_sub += inc_num; + sd->inchealhptick += natural_heal_diff_tick; + } + } + else if (hp_flag) + { + inc_num = pc_hpheal (sd); + sd->hp_sub += inc_num; + sd->inchealhptick = 0; + } + else + { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + if (sd->hp_sub >= battle_config.natural_healhp_interval) + { + bonus = sd->nhealhp; + if (hp_flag) + { + bonus >>= 2; + if (bonus <= 0) + bonus = 1; + } + while (sd->hp_sub >= battle_config.natural_healhp_interval) + { + sd->hp_sub -= battle_config.natural_healhp_interval; + if (sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else + { + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + } + } + if (bhp != sd->status.hp) + clif_updatestatus (sd, SP_HP); + + if (sd->nshealhp > 0) + { + if (sd->inchealhptick >= battle_config.natural_heal_skill_interval + && sd->status.hp < sd->status.max_hp) + { + bonus = sd->nshealhp; + while (sd->inchealhptick >= + battle_config.natural_heal_skill_interval) + { + sd->inchealhptick -= + battle_config.natural_heal_skill_interval; + if (sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else + { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal (sd->fd, SP_HP, bonus); + } + } + } + else + sd->inchealhptick = 0; + + return 0; + + if (sd->sc_data[SC_APPLEIDUN].timer != -1) + { // Apple of Idun + if (sd->inchealhptick >= 6000 && sd->status.hp < sd->status.max_hp) + { + bonus = skill * 20; + while (sd->inchealhptick >= 6000) + { + sd->inchealhptick -= 6000; + if (sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else + { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal (sd->fd, SP_HP, bonus); + } + } + } + else + sd->inchealhptick = 0; + + return 0; +} + +static int pc_natural_heal_sp (struct map_session_data *sd) +{ + int bsp; + int inc_num, bonus; + + nullpo_retr (0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if (pc_checkoversp (sd)) + { + sd->sp_sub = sd->inchealsptick = 0; + return 0; + } + + bsp = sd->status.sp; + + inc_num = pc_spheal (sd); + if (sd->sc_data[SC_EXPLOSIONSPIRITS].timer == -1) + sd->sp_sub += inc_num; + if (sd->walktimer == -1) + sd->inchealsptick += natural_heal_diff_tick; + else + sd->inchealsptick = 0; + + if (sd->sp_sub >= battle_config.natural_healsp_interval) + { + bonus = sd->nhealsp;; + while (sd->sp_sub >= battle_config.natural_healsp_interval) + { + sd->sp_sub -= battle_config.natural_healsp_interval; + if (sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else + { + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + } + } + + if (bsp != sd->status.sp) + clif_updatestatus (sd, SP_SP); + + if (sd->nshealsp > 0) + { + if (sd->inchealsptick >= battle_config.natural_heal_skill_interval + && sd->status.sp < sd->status.max_sp) + { + struct pc_base_job s_class = pc_calc_base_job (sd->status.pc_class); + if (sd->doridori_counter && s_class.job == 23) + bonus = sd->nshealsp * 2; + else + bonus = sd->nshealsp; + sd->doridori_counter = 0; + while (sd->inchealsptick >= + battle_config.natural_heal_skill_interval) + { + sd->inchealsptick -= + battle_config.natural_heal_skill_interval; + if (sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else + { + bonus = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + clif_heal (sd->fd, SP_SP, bonus); + } + } + } + else + sd->inchealsptick = 0; + + return 0; +} + +static int pc_spirit_heal_hp (struct map_session_data *sd, int level) +{ + int bonus_hp, interval = battle_config.natural_heal_skill_interval; + struct status_change *sc_data = battle_get_sc_data (&sd->bl); + + nullpo_retr (0, sd); + + if (pc_checkoverhp (sd)) + { + sd->inchealspirithptick = 0; + return 0; + } + + sd->inchealspirithptick += natural_heal_diff_tick; + + if (sd->weight * 100 / sd->max_weight >= + battle_config.natural_heal_weight_rate + && sc_data[SC_FLYING_BACKPACK].timer == -1) + interval += interval; + + if (sd->inchealspirithptick >= interval) + { + bonus_hp = sd->nsshealhp; + while (sd->inchealspirithptick >= interval) + { + if (pc_issit (sd)) + { + sd->inchealspirithptick -= interval; + if (sd->status.hp < sd->status.max_hp) + { + if (sd->status.hp + bonus_hp <= sd->status.max_hp) + sd->status.hp += bonus_hp; + else + { + bonus_hp = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + } + clif_heal (sd->fd, SP_HP, bonus_hp); + sd->inchealspirithptick = 0; + } + } + else + { + sd->inchealspirithptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} + +static int pc_spirit_heal_sp (struct map_session_data *sd, int level) +{ + int bonus_sp, interval = battle_config.natural_heal_skill_interval; + + nullpo_retr (0, sd); + + if (pc_checkoversp (sd)) + { + sd->inchealspiritsptick = 0; + return 0; + } + + sd->inchealspiritsptick += natural_heal_diff_tick; + + if (sd->weight * 100 / sd->max_weight >= + battle_config.natural_heal_weight_rate) + interval += interval; + + if (sd->inchealspiritsptick >= interval) + { + bonus_sp = sd->nsshealsp; + while (sd->inchealspiritsptick >= interval) + { + if (pc_issit (sd)) + { + sd->inchealspiritsptick -= interval; + if (sd->status.sp < sd->status.max_sp) + { + if (sd->status.sp + bonus_sp <= sd->status.max_sp) + sd->status.sp += bonus_sp; + else + { + bonus_sp = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + } + clif_heal (sd->fd, SP_SP, bonus_sp); + sd->inchealspiritsptick = 0; + } + } + else + { + sd->inchealspiritsptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} + +/*========================================== + * HP/SP 自然回復 各クライアント + *------------------------------------------ + */ + +static int +pc_quickregenerate_effect (struct quick_regeneration *quick_regen, + int heal_speed) +{ + if (!(quick_regen->tickdelay--)) + { + int bonus = + MIN (heal_speed * battle_config.itemheal_regeneration_factor, + quick_regen->amount); + + quick_regen->amount -= bonus; + + quick_regen->tickdelay = quick_regen->speed; + + return bonus; + } + + return 0; +} + +static int pc_natural_heal_sub (struct map_session_data *sd, va_list ap) +{ + int skill; + + nullpo_retr (0, sd); + + if (sd->heal_xp > 0) + { + if (sd->heal_xp < 64) + --sd->heal_xp; // [Fate] Slowly reduce XP that healers can get for healing this char + else + sd->heal_xp -= (sd->heal_xp >> 6); + } + + // Hijack this callback: Adjust spellpower bonus + if (sd->spellpower_bonus_target < sd->spellpower_bonus_current) + { + sd->spellpower_bonus_current = sd->spellpower_bonus_target; + pc_calcstatus (sd, 0); + } + else if (sd->spellpower_bonus_target > sd->spellpower_bonus_current) + { + sd->spellpower_bonus_current += + 1 + + ((sd->spellpower_bonus_target - + sd->spellpower_bonus_current) >> 5); + pc_calcstatus (sd, 0); + } + + if (sd->sc_data[SC_HALT_REGENERATE].timer != -1) + return 0; + + if (sd->quick_regeneration_hp.amount || sd->quick_regeneration_sp.amount) + { + int hp_bonus = pc_quickregenerate_effect (&sd->quick_regeneration_hp, + (sd->sc_data[SC_POISON].timer == -1 || sd->sc_data[SC_SLOWPOISON].timer != -1) ? sd->nhealhp : 1); // [fate] slow down when poisoned + int sp_bonus = pc_quickregenerate_effect (&sd->quick_regeneration_sp, + sd->nhealsp); + + pc_itemheal_effect (sd, hp_bonus, sp_bonus); + } + skill_update_heal_animation (sd); // if needed. + +// -- moonsoul (if conditions below altered to disallow natural healing if under berserk status) + if ((sd->sc_data[SC_FLYING_BACKPACK].timer != -1 + || battle_config.natural_heal_weight_rate > 100 + || sd->weight * 100 / sd->max_weight < + battle_config.natural_heal_weight_rate) && !pc_isdead (sd) + && !pc_ishiding (sd) && sd->sc_data[SC_POISON].timer == -1) + { + pc_natural_heal_hp (sd); + if (sd->sc_data && sd->sc_data[SC_EXTREMITYFIST].timer == -1 && //阿修羅状態ではSPが回復しない + sd->sc_data[SC_DANCING].timer == -1 && //ダンス状態ではSPが回復しない + sd->sc_data[SC_BERSERK].timer == -1 //バーサーク状態ではSPが回復しない + ) + pc_natural_heal_sp (sd); + } + else + { + sd->hp_sub = sd->inchealhptick = 0; + sd->sp_sub = sd->inchealsptick = 0; + } + if ((skill = pc_checkskill (sd, MO_SPIRITSRECOVERY)) > 0 + && !pc_ishiding (sd) && sd->sc_data[SC_POISON].timer == -1 + && sd->sc_data[SC_BERSERK].timer == -1) + { + pc_spirit_heal_hp (sd, skill); + pc_spirit_heal_sp (sd, skill); + } + else + { + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + } + return 0; +} + +/*========================================== + * HP/SP自然回復 (interval timer関数) + *------------------------------------------ + */ +static +void pc_natural_heal (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + natural_heal_tick = tick; + natural_heal_diff_tick = + DIFF_TICK (natural_heal_tick, natural_heal_prev_tick); + clif_foreachclient (pc_natural_heal_sub); + + natural_heal_prev_tick = tick; +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +int pc_setsavepoint (struct map_session_data *sd, const char *mapname, int x, int y) +{ + nullpo_retr (0, sd); + + strncpy (sd->status.save_point.map, mapname, 23); + sd->status.save_point.map[23] = '\0'; + sd->status.save_point.x = x; + sd->status.save_point.y = y; + + return 0; +} + +/*========================================== + * 自動セーブ 各クライアント + *------------------------------------------ + */ +static int last_save_fd, save_flag; +static int pc_autosave_sub (struct map_session_data *sd, va_list ap) +{ + nullpo_retr (0, sd); + + if (save_flag == 0 && sd->fd > last_save_fd) + { + struct guild_castle *gc = NULL; + int i; + + pc_makesavestatus (sd); + chrif_save (sd); + + for (i = 0; i < MAX_GUILDCASTLE; i++) + { + gc = guild_castle_search (i); + if (!gc) + continue; + if (gc->visibleG0 == 1) + guild_castledatasave (gc->castle_id, 18, gc->Ghp0); + if (gc->visibleG1 == 1) + guild_castledatasave (gc->castle_id, 19, gc->Ghp1); + if (gc->visibleG2 == 1) + guild_castledatasave (gc->castle_id, 20, gc->Ghp2); + if (gc->visibleG3 == 1) + guild_castledatasave (gc->castle_id, 21, gc->Ghp3); + if (gc->visibleG4 == 1) + guild_castledatasave (gc->castle_id, 22, gc->Ghp4); + if (gc->visibleG5 == 1) + guild_castledatasave (gc->castle_id, 23, gc->Ghp5); + if (gc->visibleG6 == 1) + guild_castledatasave (gc->castle_id, 24, gc->Ghp6); + if (gc->visibleG7 == 1) + guild_castledatasave (gc->castle_id, 25, gc->Ghp7); + } + + save_flag = 1; + last_save_fd = sd->fd; + } + + return 0; +} + +/*========================================== + * 自動セーブ (timer関数) + *------------------------------------------ + */ +static +void pc_autosave (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ + int interval; + + save_flag = 0; + clif_foreachclient (pc_autosave_sub); + if (save_flag == 0) + last_save_fd = 0; + + interval = autosave_interval / (clif_countusers () + 1); + if (interval <= 0) + interval = 1; + add_timer (gettick () + interval, pc_autosave, 0, 0); +} + +int pc_read_gm_account (int fd) +{ + int i = 0; + if (gm_account != NULL) + free (gm_account); + GM_num = 0; + + CREATE (gm_account, struct gm_account, (RFIFOW (fd, 2) - 4) / 5); + for (i = 4; i < RFIFOW (fd, 2); i = i + 5) + { + gm_account[GM_num].account_id = RFIFOL (fd, i); + gm_account[GM_num].level = (int) RFIFOB (fd, i + 4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + return GM_num; +} + +/*========================================== + * timer to do the day + *------------------------------------------ + */ +void map_day_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.day_duration > 0) + { // if we want a day + if (night_flag != 0) + { + strcpy (tmpstr, "The day has arrived!"); + night_flag = 0; // 0=day, 1=night [Yor] + for (i = 0; i < fd_max; i++) + { + if (session[i] && (pl_sd = (struct map_session_data *)session[i]->session_data) + && pl_sd->state.auth) + { + pl_sd->opt2 &= ~STATE_BLIND; + clif_changeoption (&pl_sd->bl); + clif_wis_message (pl_sd->fd, wisp_server_name, tmpstr, + strlen (tmpstr) + 1); + } + } + } + } +} + +/*========================================== + * timer to do the night + *------------------------------------------ + */ +void map_night_timer (timer_id tid, tick_t tick, custom_id_t id, custom_data_t data) +{ // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.night_duration > 0) + { // if we want a night + if (night_flag == 0) + { + strcpy (tmpstr, "The night has fallen..."); + night_flag = 1; // 0=day, 1=night [Yor] + for (i = 0; i < fd_max; i++) + { + if (session[i] && (pl_sd = (struct map_session_data *)session[i]->session_data) + && pl_sd->state.auth) + { + pl_sd->opt2 |= STATE_BLIND; + clif_changeoption (&pl_sd->bl); + clif_wis_message (pl_sd->fd, wisp_server_name, tmpstr, + strlen (tmpstr) + 1); + } + } + } + } +} + +void pc_setstand (struct map_session_data *sd) +{ + nullpo_retv (sd); + + if (sd->sc_data && sd->sc_data[SC_TENSIONRELAX].timer != -1) + skill_status_change_end (&sd->bl, SC_TENSIONRELAX, -1); + + sd->state.dead_sit = 0; +} + +// +// 初期化物 +// +/*========================================== + * 設定ファイル読み込む + * exp.txt 必要経験値 + * job_db1.txt 重量,hp,sp,攻撃速度 + * job_db2.txt job能力値ボーナス + * skill_tree.txt 各職毎のスキルツリー + * attr_fix.txt 属性修正テーブル + * size_fix.txt サイズ補正テーブル + * refine_db.txt 精錬データテーブル + *------------------------------------------ + */ +static +int pc_readdb (void) +{ + int i, j, k; + FILE *fp; + char line[1024], *p; + + // 必要経験値読み込み + + fp = fopen_ ("db/exp.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/exp.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + int bn, b1, b2, b3, b4, b5, b6, jn, j1, j2, j3, j4, j5, j6; + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf + (line, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", &bn, &b1, &b2, + &b3, &b4, &b5, &b6, &jn, &j1, &j2, &j3, &j4, &j5, &j6) != 14) + continue; + exp_table[0][i] = bn; + exp_table[1][i] = b1; + exp_table[2][i] = b2; + exp_table[3][i] = b3; + exp_table[4][i] = b4; + exp_table[5][i] = b5; + exp_table[6][i] = b6; + exp_table[7][i] = jn; + exp_table[8][i] = j1; + exp_table[9][i] = j2; + exp_table[10][i] = j3; + exp_table[11][i] = j4; + exp_table[12][i] = j5; + exp_table[13][i] = j6; + i++; + if (i >= battle_config.maximum_level) + break; + } + fclose_ (fp); + printf ("read db/exp.txt done\n"); + + // JOB補正数値1 + fp = fopen_ ("db/job_db1.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/job_db1.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + char *split[50]; + if (line[0] == '/' && line[1] == '/') + continue; + for (j = 0, p = line; j < 21 && p; j++) + { + split[j] = p; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + if (j < 21) + continue; + max_weight_base[i] = atoi (split[0]); + hp_coefficient[i] = atoi (split[1]); + hp_coefficient2[i] = atoi (split[2]); + sp_coefficient[i] = atoi (split[3]); + for (j = 0; j < 17; j++) + aspd_base[i][j] = atoi (split[j + 4]); + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if (i == 24) + i = 4001; + if (i == MAX_PC_CLASS) + break; + } + fclose_ (fp); + printf ("read db/job_db1.txt done\n"); + + // JOBボーナス + fp = fopen_ ("db/job_db2.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/job_db2.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + for (j = 0, p = line; j < MAX_LEVEL && p; j++) + { + if (sscanf (p, "%d", &k) == 0) + break; + job_bonus[0][i][j] = k; + job_bonus[2][i][j] = k; //養子職のボーナスは分からないので仮 + p = strchr (p, ','); + if (p) + p++; + } + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if (i == 24) + i = 4001; + if (i == MAX_PC_CLASS) + break; + } + fclose_ (fp); + printf ("read db/job_db2.txt done\n"); + + // JOBボーナス2 転生職用 + fp = fopen_ ("db/job_db2-2.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/job_db2-2.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + if (line[0] == '/' && line[1] == '/') + continue; + for (j = 0, p = line; j < MAX_LEVEL && p; j++) + { + if (sscanf (p, "%d", &k) == 0) + break; + job_bonus[1][i][j] = k; + p = strchr (p, ','); + if (p) + p++; + } + i++; + if (i == MAX_PC_CLASS) + break; + } + fclose_ (fp); + printf ("read db/job_db2-2.txt done\n"); + + // スキルツリー + memset (skill_tree, 0, sizeof (skill_tree)); + fp = fopen_ ("db/skill_tree.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/skill_tree.txt\n"); + return 1; + } + while (fgets (line, sizeof (line) - 1, fp)) + { + char *split[50]; + if (line[0] == '/' && line[1] == '/') + continue; + for (j = 0, p = line; j < 13 && p; j++) + { + split[j] = p; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + if (j < 13) + continue; + i = atoi (split[0]); + for (j = 0; skill_tree[0][i][j].id; j++); + skill_tree[0][i][j].id = atoi (split[1]); + skill_tree[0][i][j].max = atoi (split[2]); + skill_tree[2][i][j].id = atoi (split[1]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].max = atoi (split[2]); //養子職は良く分からないので暫定 + for (k = 0; k < 5; k++) + { + skill_tree[0][i][j].need[k].id = atoi (split[k * 2 + 3]); + skill_tree[0][i][j].need[k].lv = atoi (split[k * 2 + 4]); + skill_tree[2][i][j].need[k].id = atoi (split[k * 2 + 3]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].need[k].lv = atoi (split[k * 2 + 4]); //養子職は良く分からないので暫定 + } + } + fclose_ (fp); + printf ("read db/skill_tree.txt done\n"); + + // 属性修正テーブル + for (i = 0; i < 4; i++) + for (j = 0; j < 10; j++) + for (k = 0; k < 10; k++) + attr_fix_table[i][j][k] = 100; + fp = fopen_ ("db/attr_fix.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/attr_fix.txt\n"); + return 1; + } + while (fgets (line, sizeof (line) - 1, fp)) + { + char *split[10]; + int lv, n; + if (line[0] == '/' && line[1] == '/') + continue; + for (j = 0, p = line; j < 3 && p; j++) + { + split[j] = p; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + lv = atoi (split[0]); + n = atoi (split[1]); +// printf("%d %d\n",lv,n); + + for (i = 0; i < n;) + { + if (!fgets (line, sizeof (line) - 1, fp)) + break; + if (line[0] == '/' && line[1] == '/') + continue; + + for (j = 0, p = line; j < n && p; j++) + { + while (*p == 32 && *p > 0) + p++; + attr_fix_table[lv - 1][i][j] = atoi (p); + if (battle_config.attr_recover == 0 + && attr_fix_table[lv - 1][i][j] < 0) + attr_fix_table[lv - 1][i][j] = 0; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + + i++; + } + } + fclose_ (fp); + printf ("read db/attr_fix.txt done\n"); + + // サイズ補正テーブル + for (i = 0; i < 3; i++) + for (j = 0; j < 20; j++) + atkmods[i][j] = 100; + fp = fopen_ ("db/size_fix.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/size_fix.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + char *split[20]; + if (line[0] == '/' && line[1] == '/') + continue; + if (atoi (line) <= 0) + continue; + memset (split, 0, sizeof (split)); + for (j = 0, p = line; j < 20 && p; j++) + { + split[j] = p; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + for (j = 0; j < 20 && split[j]; j++) + atkmods[i][j] = atoi (split[j]); + i++; + } + fclose_ (fp); + printf ("read db/size_fix.txt done\n"); + + // 精錬データテーブル + for (i = 0; i < 5; i++) + { + for (j = 0; j < 10; j++) + percentrefinery[i][j] = 100; + refinebonus[i][0] = 0; + refinebonus[i][1] = 0; + refinebonus[i][2] = 10; + } + fp = fopen_ ("db/refine_db.txt", "r"); + if (fp == NULL) + { + printf ("can't read db/refine_db.txt\n"); + return 1; + } + i = 0; + while (fgets (line, sizeof (line) - 1, fp)) + { + char *split[16]; + if (line[0] == '/' && line[1] == '/') + continue; + if (atoi (line) <= 0) + continue; + memset (split, 0, sizeof (split)); + for (j = 0, p = line; j < 16 && p; j++) + { + split[j] = p; + p = strchr (p, ','); + if (p) + *p++ = 0; + } + refinebonus[i][0] = atoi (split[0]); // 精錬ボーナス + refinebonus[i][1] = atoi (split[1]); // 過剰精錬ボーナス + refinebonus[i][2] = atoi (split[2]); // 安全精錬限界 + for (j = 0; j < 10 && split[j]; j++) + percentrefinery[i][j] = atoi (split[j + 3]); + i++; + } + fclose_ (fp); //Lupus. close this file!!! + printf ("read db/refine_db.txt done\n"); + + return 0; +} + +static int pc_calc_sigma (void) +{ + int i, j, k; + + for (i = 0; i < MAX_PC_CLASS; i++) + { + memset (hp_sigma_val[i], 0, sizeof (hp_sigma_val[i])); + for (k = 0, j = 2; j <= MAX_LEVEL; j++) + { + k += hp_coefficient[i] * j + 50; + k -= k % 100; + hp_sigma_val[i][j - 1] = k; + } + } + return 0; +} + +static void pc_statpointdb (void) +{ + char *buf_stat; + int i = 0, j = 0, k = 0, l = 0, end = 0; + + FILE *stp; + + stp = fopen_ ("db/statpoint.txt", "r"); + + if (stp == NULL) + { + printf ("can't read db/statpoint.txt\n"); + return; + } + + fseek (stp, 0, SEEK_END); + end = ftell (stp); + rewind (stp); + + buf_stat = (char *) malloc (end + 1); + l = fread (buf_stat, 1, end, stp); + fclose_ (stp); + printf ("read db/statpoint.txt done (size=%d)\n", l); + + for (i = 0; i < 255; i++) + { + j = 0; + while (*(buf_stat + k) != '\n') + { + statp[i][j] = *(buf_stat + k); + j++; + k++; + } + statp[i][j + 1] = '\0'; + k++; + } + + free (buf_stat); +} + +/*========================================== + * pc関 係初期化 + *------------------------------------------ + */ +int do_init_pc (void) +{ + pc_readdb (); + pc_statpointdb (); + pc_calc_sigma (); + +// gm_account_db = numdb_init(); + + add_timer_interval ((natural_heal_prev_tick = + gettick () + NATURAL_HEAL_INTERVAL), pc_natural_heal, + 0, 0, NATURAL_HEAL_INTERVAL); + add_timer (gettick () + autosave_interval, pc_autosave, 0, 0); + + { + int day_duration = battle_config.day_duration; + int night_duration = battle_config.night_duration; + if (day_duration < 60000) + day_duration = 60000; + if (night_duration < 60000) + night_duration = 60000; + if (battle_config.night_at_start == 0) + { + night_flag = 0; // 0=day, 1=night [Yor] + day_timer_tid = + add_timer_interval (gettick () + day_duration + + night_duration, map_day_timer, 0, 0, + day_duration + night_duration); + night_timer_tid = + add_timer_interval (gettick () + day_duration, + map_night_timer, 0, 0, + day_duration + night_duration); + } + else + { + night_flag = 1; // 0=day, 1=night [Yor] + day_timer_tid = + add_timer_interval (gettick () + night_duration, + map_day_timer, 0, 0, + day_duration + night_duration); + night_timer_tid = + add_timer_interval (gettick () + day_duration + + night_duration, map_night_timer, 0, 0, + day_duration + night_duration); + } + } + + return 0; +} + +void pc_cleanup (struct map_session_data *sd) +{ + magic_stop_completely (sd); +} + +void pc_invisibility (struct map_session_data *sd, int enabled) +{ + if (enabled && !(sd->status.option & OPTION_INVISIBILITY)) + { + clif_clearchar_area (&sd->bl, 3); + sd->status.option |= OPTION_INVISIBILITY; + clif_status_change (&sd->bl, CLIF_OPTION_SC_INVISIBILITY, 1); + } + else if (!enabled) + { + sd->status.option &= ~OPTION_INVISIBILITY; + clif_status_change (&sd->bl, CLIF_OPTION_SC_INVISIBILITY, 0); + pc_setpos (sd, map[sd->bl.m].name, sd->bl.x, sd->bl.y, 3); + } +} + +int pc_logout (struct map_session_data *sd) // [fate] Player logs out +{ + unsigned int tick = gettick (); + + if (!sd) + return 0; + + if (sd->sc_data[SC_POISON].timer != -1) + sd->status.hp = 1; // Logging out while poisoned -> bad + + /* + * Trying to rapidly sign out/in or switch characters to avoid a spell's + * cast time is also bad. [remoitnane] + */ +#if 0 + // Removed because it's buggy, see above. + if (sd->cast_tick > tick) + { + if (pc_setglobalreg (sd, "MAGIC_CAST_TICK", sd->cast_tick - tick)) + sd->status.sp = 1; + } + else +#endif + pc_setglobalreg (sd, "MAGIC_CAST_TICK", 0); + + MAP_LOG_STATS (sd, "LOGOUT") return 0; +} |