Implemented official ammo equip behavior (#3438)

* Fixes #1155.
* Added specific checks for weapons when equipping ammo.
* Ammo is removed if the required weapon is removed.
* Added battle configs to disable behavior.
Thanks to @Atemo, @exneval, and @benching!
This commit is contained in:
Aleos 2018-09-05 10:44:34 -04:00 committed by GitHub
parent 618fc37eab
commit d3d8f3c5a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 208 additions and 116 deletions

View File

@ -141,6 +141,14 @@ delay_battle_damage: yes
// skills should consume ammo when it's acquired via a card or plagiarize)
arrow_decrement: 1
// Should ammo be unequipped when unequipping a weapon?
// Official behavior is "yes".
ammo_unequip: yes
// Should a suitable weapon be equipped when equipping ammo?
// Official behavior is "yes".
ammo_check_weapon: yes
// Should the item script bonus 'Autospell' check for range/obstacles before casting?
// Official behavior is "no", setting this to "yes" will make skills use their defined
// range. For example, Sonic Blow requires a 2 cell distance before autocasting is allowed.

View File

@ -8141,6 +8141,8 @@ static const struct _battle_data {
{ "natural_heal_weight_rate", &battle_config.natural_heal_weight_rate, 50, 0, 100 },
{ "natural_heal_weight_rate_renewal", &battle_config.natural_heal_weight_rate_renewal,70, 0, 100 },
{ "arrow_decrement", &battle_config.arrow_decrement, 1, 0, 2, },
{ "ammo_unequip", &battle_config.ammo_unequip, 1, 0, 1, },
{ "ammo_check_weapon", &battle_config.ammo_check_weapon, 1, 0, 1, },
{ "max_aspd", &battle_config.max_aspd, 190, 100, 199, },
{ "max_third_aspd", &battle_config.max_third_aspd, 193, 100, 199, },
{ "max_walk_speed", &battle_config.max_walk_speed, 300, 100, 100*DEFAULT_WALK_SPEED, },

View File

@ -263,6 +263,8 @@ struct Battle_Config
int natural_heal_weight_rate;
int natural_heal_weight_rate_renewal;
int arrow_decrement;
int ammo_unequip;
int ammo_check_weapon;
int max_aspd;
int max_walk_speed; //Maximum walking speed after buffs [Skotlex]
int max_hp_lv99;

View File

@ -491,25 +491,29 @@ enum clif_messages : uint16_t {
SKILL_CANT_USE_AREA = 0x536,
ITEM_CANT_USE_AREA = 0x537,
VIEW_EQUIP_FAIL = 0x54d,
ITEM_NEED_MADOGEAR = 0x59b,
ITEM_NEED_CART = 0x5ef,
RUNE_CANT_CREATE = 0x61b,
ITEM_CANT_COMBINE = 0x623,
INVENTORY_SPACE_FULL = 0x625,
ITEM_PRODUCE_SUCCESS = 0x627,
ITEM_PRODUCE_FAIL = 0x628,
ITEM_UNIDENTIFIED = 0x62d,
ITEM_NEED_BOW = 0x69b,
ITEM_REUSE_LIMIT = 0x746,
WORK_IN_PROGRESS = 0x783,
NEED_REINS_OF_MOUNT = 0x78c,
PARTY_MASTER_CHANGE_SAME_MAP = 0x82e, ///< "It is only possible to change the party leader while on the same map."
MERGE_ITEM_NOT_AVAILABLE = 0x887,
GUILD_MASTER_WOE = 0xb93, /// <"Currently in WoE hours, unable to delegate Guild leader"
GUILD_MASTER_DELAY = 0xb94, /// <"You have to wait for one day before delegating a new Guild leader"
ITEM_BULLET_EQUIP_FAIL = 0x9bd,
SKILL_NEED_GATLING = 0x9fa,
SKILL_NEED_SHOTGUN = 0x9fb,
SKILL_NEED_RIFLE = 0x9fc,
SKILL_NEED_REVOLVER = 0x9fd,
SKILL_NEED_HOLY_BULLET = 0x9fe,
SKILL_NEED_GRENADE = 0xa01,
GUILD_MASTER_WOE = 0xb93, /// <"Currently in WoE hours, unable to delegate Guild leader"
GUILD_MASTER_DELAY = 0xb94, /// <"You have to wait for one day before delegating a new Guild leader"
MSG_ATTENDANCE_DISABLED = 0xd92,
};

View File

@ -1122,6 +1122,51 @@ uint8 pc_isequip(struct map_session_data *sd,int n)
if (!battle_config.allow_equip_restricted_item && itemdb_isNoEquip(item, sd->bl.m))
return ITEM_EQUIP_ACK_FAIL;
if (item->equip&EQP_AMMO) {
switch (item->look) {
case AMMO_ARROW:
if (battle_config.ammo_check_weapon && sd->status.weapon != W_BOW && sd->status.weapon != W_MUSICAL && sd->status.weapon != W_WHIP) {
clif_msg(sd, ITEM_NEED_BOW);
return ITEM_EQUIP_ACK_FAIL;
}
break;
case AMMO_THROWABLE_DAGGER:
if (!pc_checkskill(sd, AS_VENOMKNIFE))
return ITEM_EQUIP_ACK_FAIL;
break;
case AMMO_BULLET:
case AMMO_SHELL:
if (battle_config.ammo_check_weapon && sd->status.weapon != W_REVOLVER && sd->status.weapon != W_RIFLE && sd->status.weapon != W_GATLING && sd->status.weapon != W_SHOTGUN
#ifdef RENEWAL
&& sd->status.weapon != W_GRENADE
#endif
) {
clif_msg(sd, ITEM_BULLET_EQUIP_FAIL);
return ITEM_EQUIP_ACK_FAIL;
}
break;
#ifndef RENEWAL
case AMMO_GRENADE:
if (battle_config.ammo_check_weapon && sd->status.weapon != W_GRENADE) {
clif_msg(sd, ITEM_BULLET_EQUIP_FAIL);
return ITEM_EQUIP_ACK_FAIL;
}
break;
#endif
case AMMO_CANNONBALL:
if (!pc_ismadogear(sd) && (sd->status.class_ == JOB_MECHANIC_T || sd->status.class_ == JOB_MECHANIC)) {
clif_msg(sd, ITEM_NEED_MADOGEAR); // Item can only be used when Mado Gear is mounted.
return ITEM_EQUIP_ACK_FAIL;
}
if (sd->state.active && !pc_iscarton(sd) && //Check if sc data is already loaded
(sd->status.class_ == JOB_GENETIC_T || sd->status.class_ == JOB_GENETIC)) {
clif_msg(sd, ITEM_NEED_CART); // Only available when cart is mounted.
return ITEM_EQUIP_ACK_FAIL;
}
break;
}
}
if (sd->sc.count) {
if(item->equip & EQP_ARMS && item->type == IT_WEAPON && sd->sc.data[SC_STRIPWEAPON]) // Also works with left-hand weapons [DracoRPG]
return ITEM_EQUIP_ACK_FAIL;
@ -9875,38 +9920,6 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos)
return false;
}
if ((sd->class_&MAPID_BASEMASK) == MAPID_GUNSLINGER) {
/** Failing condition:
* 1. Always failed to equip ammo if no weapon equipped yet
* 2. Grenade only can be equipped if weapon is Grenade Launcher
* 3. Bullet cannot be equipped if the weapon is Grenade Launcher
* (4. The rest is relying on item job/class restriction).
**/
if (id->type == IT_AMMO) {
int w_idx = sd->equip_index[EQI_HAND_R];
enum weapon_type w_type = (w_idx != -1) ? (enum weapon_type)sd->inventory_data[w_idx]->look : W_FIST;
if (w_idx == -1 ||
(id->look == A_GRENADE && w_type != W_GRENADE) ||
(id->look != A_GRENADE && w_type == W_GRENADE))
{
clif_equipitemack(sd, 0, 0, ITEM_EQUIP_ACK_FAIL);
return false;
}
}
else if (id->type == IT_WEAPON && id->look >= W_REVOLVER && id->look <= W_GRENADE) {
int a_idx = sd->equip_index[EQI_AMMO];
if (a_idx != -1) {
enum ammo_type a_type = (enum ammo_type)sd->inventory_data[a_idx]->look;
if ((a_type == A_GRENADE && id->look != W_GRENADE) ||
(a_type != A_GRENADE && id->look == W_GRENADE))
{
clif_equipitemack(sd, 0, 0, ITEM_EQUIP_ACK_FAIL);
return false;
}
}
}
}
if (id->flag.bindOnEquip && !sd->inventory.u.items_inventory[n].bound) {
sd->inventory.u.items_inventory[n].bound = (char)battle_config.default_bind_on_equip;
clif_notify_bindOnEquip(sd,n);
@ -9947,7 +9960,7 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos)
for(i=0;i<EQI_MAX;i++) {
if(pos & equip_bitmask[i]) {
if(sd->equip_index[i] >= 0) //Slot taken, remove item from there.
pc_unequipitem(sd,sd->equip_index[i],2);
pc_unequipitem(sd,sd->equip_index[i],2 | 4);
sd->equip_index[i] = n;
}
@ -9990,6 +10003,34 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos)
if(pos & EQP_SHOES)
clif_changelook(&sd->bl,LOOK_SHOES,0);
if (battle_config.ammo_unequip && (pos&EQP_ARMS) && id->type == IT_WEAPON) {
short idx = sd->equip_index[EQI_AMMO];
if (idx >= 0) {
switch (sd->inventory_data[idx]->look) {
case AMMO_ARROW:
if (id->look != W_BOW && id->look != W_MUSICAL && id->look != W_WHIP)
pc_unequipitem(sd, idx, 2 | 4);
break;
case AMMO_BULLET:
case AMMO_SHELL:
if (id->look != W_REVOLVER && id->look != W_RIFLE && id->look != W_GATLING && id->look != W_SHOTGUN
#ifdef RENEWAL
&& id->look != W_GRENADE
#endif
)
pc_unequipitem(sd, idx, 2 | 4);
break;
#ifndef RENEWAL
case AMMO_GRENADE:
if (id->look != W_GRENADE)
pc_unequipitem(sd, idx, 2 | 4);
break;
#endif
}
}
}
pc_set_costume_view(sd);
pc_checkallowskill(sd); //Check if status changes should be halted.
@ -10040,25 +10081,101 @@ bool pc_equipitem(struct map_session_data *sd,short n,int req_pos)
return true;
}
/*==========================================
* Called when attemting to unequip an item from player
* type:
* 0 - only unequip
* 1 - calculate status after unequipping
* 2 - force unequip
* return: false - fail; true - success
*------------------------------------------*/
bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
/**
* Recalculate player status on unequip
* @param sd: Player data
* @param n: Item inventory index
* @param flag: Whether to recalculate a player's status or not
* @return True on success or false on failure
*/
static void pc_unequipitem_sub(struct map_session_data *sd, int n, int flag) {
int i, iflag;
bool status_calc = false;
if (sd->state.autobonus&sd->inventory.u.items_inventory[n].equip)
sd->state.autobonus &= ~sd->inventory.u.items_inventory[n].equip; //Check for activated autobonus [Inkfish]
sd->inventory.u.items_inventory[n].equip = 0;
pc_checkallowskill(sd);
iflag = sd->npc_item_flag;
/* check for combos (MUST be before status_calc_pc) */
if (sd->inventory_data[n]) {
if (sd->inventory_data[n]->combos_count) {
if (pc_removecombo(sd, sd->inventory_data[n]))
status_calc = true;
}
if (itemdb_isspecial(sd->inventory.u.items_inventory[n].card[0]))
; //No cards
else {
for (i = 0; i < MAX_SLOTS; i++) {
struct item_data *data;
if (!sd->inventory.u.items_inventory[n].card[i])
continue;
if ((data = itemdb_exists(sd->inventory.u.items_inventory[n].card[i])) != NULL) {
if (data->combos_count) {
if (pc_removecombo(sd, data))
status_calc = true;
}
}
}
}
}
if (status_calc)
status_calc_pc(sd, SCO_NONE);
if (sd->sc.data[SC_SIGNUMCRUCIS] && !battle_check_undead(sd->battle_status.race, sd->battle_status.def_ele))
status_change_end(&sd->bl, SC_SIGNUMCRUCIS, INVALID_TIMER);
//OnUnEquip script [Skotlex]
if (sd->inventory_data[n]) {
if (sd->inventory_data[n]->unequip_script)
run_script(sd->inventory_data[n]->unequip_script, 0, sd->bl.id, fake_nd->bl.id);
if (itemdb_isspecial(sd->inventory.u.items_inventory[n].card[0]))
; //No cards
else {
for (i = 0; i < MAX_SLOTS; i++) {
struct item_data *data;
if (!sd->inventory.u.items_inventory[n].card[i])
continue;
if ((data = itemdb_exists(sd->inventory.u.items_inventory[n].card[i])) != NULL) {
if (data->unequip_script)
run_script(data->unequip_script, 0, sd->bl.id, fake_nd->bl.id);
}
}
}
}
if (flag & 1)
status_calc_pc(sd, SCO_FORCE);
sd->npc_item_flag = iflag;
}
/**
* Called when attempting to unequip an item from a player
* @param sd: Player data
* @param n: Item inventory index
* @param flag: Type of unequip
* 0 - only unequip
* 1 - calculate status after unequipping
* 2 - force unequip
* 4 - unequip by switching equipment
* @return True on success or false on failure
*/
bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
int i, pos;
nullpo_retr(false,sd);
if (n < 0 || n >= MAX_INVENTORY) {
clif_unequipitemack(sd,0,0,0);
return false;
}
if (!sd->inventory.u.items_inventory[n].equip) {
if (!(pos = sd->inventory.u.items_inventory[n].equip)) {
clif_unequipitemack(sd,n,0,0);
return false; //Nothing to unequip
}
@ -10076,18 +10193,14 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
}
if (battle_config.battle_log)
ShowInfo("unequip %d %x:%x\n",n,pc_equippoint(sd,n),sd->inventory.u.items_inventory[n].equip);
ShowInfo("unequip %d %x:%x\n",n,pc_equippoint(sd,n),pos);
if (!sd->inventory.u.items_inventory[n].equip) { //Nothing to unequip
clif_unequipitemack(sd, n, 0, 0);
return false;
}
for(i = 0; i < EQI_MAX; i++) {
if (sd->inventory.u.items_inventory[n].equip & equip_bitmask[i])
if (pos & equip_bitmask[i])
sd->equip_index[i] = -1;
}
if(sd->inventory.u.items_inventory[n].equip & EQP_HAND_R) {
if(pos & EQP_HAND_R) {
sd->weapontype1 = 0;
sd->status.weapon = sd->weapontype2;
pc_calcweapontype(sd);
@ -10095,7 +10208,7 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
if( !battle_config.dancing_weaponswitch_fix )
status_change_end(&sd->bl, SC_DANCING, INVALID_TIMER); // Unequipping => stop dancing.
}
if(sd->inventory.u.items_inventory[n].equip & EQP_HAND_L) {
if(pos & EQP_HAND_L) {
if (sd->status.shield && battle_getcurrentskill(&sd->bl) == LG_SHIELDSPELL)
unit_skillcastcancel(&sd->bl, 0); // Cancel Shield Spell if player swaps shields.
@ -10104,15 +10217,36 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield);
}
if(sd->inventory.u.items_inventory[n].equip & EQP_SHOES)
if(pos & EQP_SHOES)
clif_changelook(&sd->bl,LOOK_SHOES,0);
clif_unequipitemack(sd,n,sd->inventory.u.items_inventory[n].equip,1);
clif_unequipitemack(sd,n,pos,1);
pc_set_costume_view(sd);
status_change_end(&sd->bl,SC_HEAT_BARREL,INVALID_TIMER);
// On weapon change (right and left hand)
if ((sd->inventory.u.items_inventory[n].equip & EQP_ARMS) && sd->inventory_data[n]->type == IT_WEAPON) {
if ((pos & EQP_ARMS) && sd->inventory_data[n]->type == IT_WEAPON) {
if (battle_config.ammo_unequip && !(flag & 4)) {
switch (sd->inventory_data[n]->look) {
case W_BOW:
case W_MUSICAL:
case W_WHIP:
case W_REVOLVER:
case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
case W_GRENADE: {
short idx = sd->equip_index[EQI_AMMO];
if (idx >= 0) {
sd->equip_index[EQI_AMMO] = -1;
clif_unequipitemack(sd, idx, sd->inventory.u.items_inventory[idx].equip, 1);
pc_unequipitem_sub(sd, idx, 0);
}
}
break;
}
}
if (!sd->sc.data[SC_SEVENWIND] || sd->sc.data[SC_ASPERSIO]) //Check for seven wind (but not level seven!)
skill_enchant_elemental_end(&sd->bl, SC_NONE);
status_change_end(&sd->bl, SC_FEARBREEZE, INVALID_TIMER);
@ -10120,7 +10254,7 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
}
// On armor change
if (sd->inventory.u.items_inventory[n].equip & EQP_ARMOR) {
if (pos & EQP_ARMOR) {
if (sd->sc.data[SC_HOVERING] && sd->inventory_data[n]->nameid == ITEMID_HOVERING_BOOSTER)
status_change_end(&sd->bl, SC_HOVERING, INVALID_TIMER);
//status_change_end(&sd->bl, SC_BENEDICTIO, INVALID_TIMER); // No longer is removed? Need confirmation
@ -10131,65 +10265,7 @@ bool pc_unequipitem(struct map_session_data *sd, int n, int flag) {
if (sd->inventory_data[n]->type == IT_AMMO && (sd->inventory_data[n]->nameid != ITEMID_SILVER_BULLET || sd->inventory_data[n]->nameid != ITEMID_PURIFICATION_BULLET || sd->inventory_data[n]->nameid != ITEMID_SILVER_BULLET_))
status_change_end(&sd->bl, SC_P_ALTER, INVALID_TIMER);
if (sd->state.autobonus&sd->inventory.u.items_inventory[n].equip)
sd->state.autobonus &= ~sd->inventory.u.items_inventory[n].equip; //Check for activated autobonus [Inkfish]
sd->inventory.u.items_inventory[n].equip = 0;
iflag = sd->npc_item_flag;
/* check for combos (MUST be before status_calc_pc) */
if ( sd->inventory_data[n] ) {
if( sd->inventory_data[n]->combos_count ) {
if( pc_removecombo(sd,sd->inventory_data[n]) )
status_calc = true;
}
if(itemdb_isspecial(sd->inventory.u.items_inventory[n].card[0]))
; //No cards
else {
for( i = 0; i < MAX_SLOTS; i++ ) {
struct item_data *data;
if (!sd->inventory.u.items_inventory[n].card[i])
continue;
if ( ( data = itemdb_exists(sd->inventory.u.items_inventory[n].card[i]) ) != NULL ) {
if( data->combos_count ) {
if( pc_removecombo(sd,data) )
status_calc = true;
}
}
}
}
}
if(flag&1 || status_calc) {
pc_checkallowskill(sd);
status_calc_pc(sd,SCO_NONE);
}
if(sd->sc.data[SC_SIGNUMCRUCIS] && !battle_check_undead(sd->battle_status.race,sd->battle_status.def_ele))
status_change_end(&sd->bl, SC_SIGNUMCRUCIS, INVALID_TIMER);
//OnUnEquip script [Skotlex]
if (sd->inventory_data[n]) {
if (sd->inventory_data[n]->unequip_script)
run_script(sd->inventory_data[n]->unequip_script,0,sd->bl.id,fake_nd->bl.id);
if(itemdb_isspecial(sd->inventory.u.items_inventory[n].card[0]))
; //No cards
else {
for( i = 0; i < MAX_SLOTS; i++ ) {
struct item_data *data;
if (!sd->inventory.u.items_inventory[n].card[i])
continue;
if ( ( data = itemdb_exists(sd->inventory.u.items_inventory[n].card[i]) ) != NULL ) {
if( data->unequip_script )
run_script(data->unequip_script,0,sd->bl.id,fake_nd->bl.id);
}
}
}
}
sd->npc_item_flag = iflag;
pc_unequipitem_sub(sd, n, flag);
return true;
}