Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions HeavenClient/Data/EquipData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ namespace ms
slots = src["tuc"];
recovery = src["recovery"].get_real(1.0);

// A field of 0 or less makes the spell do nothing.
int16_t elemdefault = static_cast<int16_t>(src["elemDefault"].get_integer(100));
const char *boosts[] = { "", "incRMAI", "incRMAF", "incRMAL", "incRMAS", "incRMAH", "", "" };

for (size_t element = 0; element < elemboost.size(); element++)
{
int16_t boost = *boosts[element]
? static_cast<int16_t>(src[boosts[element]].get_integer(elemdefault))
: 100;
elemboost[element] = boost > 0 ? boost : 0;
}

reqstats[MapleStat::Id::LEVEL] = src["reqLevel"];
reqstats[MapleStat::Id::JOB] = src["reqJob"];
reqstats[MapleStat::Id::STR] = src["reqSTR"];
Expand Down Expand Up @@ -180,4 +192,9 @@ namespace ms
{
return recovery;
}

int16_t EquipData::get_elemboost(uint8_t element) const
{
return element < elemboost.size() ? elemboost[element] : 100;
}
}
8 changes: 8 additions & 0 deletions HeavenClient/Data/EquipData.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//////////////////////////////////////////////////////////////////////////////////
#pragma once

#include <array>
#include <string>

#include <cstdint>
Expand Down Expand Up @@ -62,6 +63,12 @@ namespace ms
// recover faster (info/recovery, 1 when absent).
double get_recovery() const;

// The percent of a spell's damage a weapon gives for its element
// (FUN_005d284f): incRMAI, F, L, S or H for ice, fire, lightning,
// poison and holy, else elemDefault, else 100. Other elements get
// 100.
int16_t get_elemboost(uint8_t element) const;

private:
// Allow the cache to use the constructor
friend Cache<EquipData>;
Expand All @@ -79,5 +86,6 @@ namespace ms
bool cash;
bool tradeblock;
double recovery;
std::array<int16_t, 8> elemboost;
};
}
2 changes: 2 additions & 0 deletions HeavenClient/Gameplay/Combat/Attack.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ namespace ms
int32_t mastery = 0;
int32_t amplify = 100;
uint8_t element = 0;
// The weapon's percent for that element (an Elemental Wand or Staff).
int32_t elemboost = 100;
// Heal's `hp` percent, which it hurts the undead with in place of a
// magic attack, and how many it reaches, mobs and party members.
int32_t heal = 0;
Expand Down
35 changes: 33 additions & 2 deletions HeavenClient/Gameplay/Combat/Combat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,9 @@ namespace ms
// nothing. A spell flies one ball to each mob it hits. A mob's
// hits all land 1.5 ms per pixel it is away across, or along a
// spell's path, after the attack's hit frame, but for a few
// skills (0x954867).
// skills (0x954867). Energy Orb's ball instead jumps from 25 px
// ahead to the middle of each mob in turn, each hit when it gets
// there (FUN_0094289f).
Animation animation = move.get_bullet(user, result.bullet);
Point<int16_t> origin = shot_path::origin(user.get_position(), reach, result.toleft);
Point<int16_t> start = shot_path::bullet_start(result.skill, origin, result.toleft);
Expand All @@ -1396,7 +1398,7 @@ namespace ms

if (spell)
count = sets = 1;
else if (shot_path::hides_bullet(result.skill))
else if (shot_path::hides_bullet(result.skill) || shot_path::hops(result.skill))
return;

uint8_t per_set = std::max(count / sets, 1);
Expand All @@ -1413,6 +1415,9 @@ namespace ms
uint16_t first = 0;
std::optional<Point<int16_t>> first_hit;
std::vector<Rectangle<int16_t>> bodies;
bool hops = !spell && shot_path::hops(result.skill);
Point<int16_t> hop_from = shot_path::hop_start(origin, result.toleft);
uint16_t hopped = 0;

for (size_t target = 0; target < result.damagelines.size(); target++)
{
Expand All @@ -1427,6 +1432,16 @@ namespace ms
uint16_t hit_delay = spell ? shot_path::flight_time(origin, hit)
: shot_path::target_delay(result.skill, target, first, origin, hit);

if (hops)
{
Point<int16_t> hop_to = shot_path::hop_point(body);
uint16_t hop = shot_path::hop_time(result.skill, hop_from, hop_to);
bulleteffects.push(delay + hopped, Bullet(animation, hop_from, hop_to, hop));
hopped += hop;
hit_delay = hopped;
hop_from = hop_to;
}

if (target == 0)
first = hit_delay;

Expand Down Expand Up @@ -1644,6 +1659,8 @@ namespace ms
uint16_t first = 0;
std::optional<Point<int16_t>> first_hit;
std::vector<Rectangle<int16_t>> bodies;
std::optional<Point<int16_t>> hop_from;
uint16_t hopped = 0;

for (size_t target = 0; target < result.damagelines.size(); target++)
{
Expand Down Expand Up @@ -1673,6 +1690,20 @@ namespace ms
Point<int16_t> aim = remote_spell::aim(start, body, left);
uint16_t hit = remote_shot::hit_delay(skill, target, first, delay, start, aim);

// Energy Orb's ball jumps from 25 px ahead of where it starts
// to the middle of each mob in turn, as the user's own does
// (0x981ba6).
if (shot_path::hops(skill))
{
Point<int16_t> from = hop_from ? *hop_from : shot_path::hop_start(start, result.toleft);
Point<int16_t> to = shot_path::hop_point(body);
uint16_t hop = shot_path::hop_time(skill, from, to);
bulleteffects.push(delay + hopped, Bullet(animation, from, to, hop));
hopped += hop;
hit = hopped;
hop_from = to;
}

if (target == 0)
first = hit;

Expand Down
27 changes: 27 additions & 0 deletions HeavenClient/Gameplay/Combat/MagicDamage.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,33 @@ namespace ms
return damage - between(mdef * 0.5, mdef * 0.6, roll);
}

// Whether a spell gets through a Magic Immunity: a roll of 0 to 99
// must be at most the player's chance to pierce it, so 1 in 100
// does with none. One that doesn't hits for 1.
inline bool pierces(int32_t roll, int32_t chance)
{
return roll <= chance;
}

// A percent of the hit: Magic Guard Up's n, or the weapon's for
// the element.
inline double percent(double damage, int32_t percent)
{
return percent * damage * 0.01;
}

// Magic Guard Up leaves n% of the hit after the defense.
inline double guarded(double damage, int32_t guard)
{
return guard ? percent(damage, guard) : damage;
}

// Weakness makes the hit n% harder.
inline double weakened(double damage, int32_t weakness)
{
return weakness ? percent(damage, weakness + 100) : damage;
}

// The number shown: between 1 and 199999, rounded down.
inline int32_t finish(double damage)
{
Expand Down
11 changes: 2 additions & 9 deletions HeavenClient/Gameplay/Combat/RemoteShot.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,6 @@ namespace ms
}
}

// Whether the shot's bullets jump from mob to mob instead: Energy Orb
// and Spark.
inline bool hops(int32_t skill)
{
return skill == 5121002 || skill == 15111006;
}

// Whether the shot flies its bullets to every mob it hits, rather
// than to the first alone.
inline bool shoots_all(int32_t skill)
Expand All @@ -171,7 +164,7 @@ namespace ms
inline bool shoots(int32_t skill, size_t target, Rectangle<int16_t> lt_rb, int16_t start_x,
Rectangle<int16_t> body, bool left)
{
if (hides_bullet(skill) || hops(skill))
if (hides_bullet(skill) || shot_path::hops(skill))
return false;

if (target > 0 && !shoots_all(skill))
Expand All @@ -196,7 +189,7 @@ namespace ms
// than hide them otherwise.
inline bool hides_miss(int32_t skill)
{
return shot_path::hides_bullet(skill) || hops(skill);
return shot_path::hides_bullet(skill) || shot_path::hops(skill);
}

// Where the bullets of a shot that hit nothing start, in front of
Expand Down
34 changes: 34 additions & 0 deletions HeavenClient/Gameplay/Combat/ShotPath.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,40 @@ namespace ms
return static_cast<uint16_t>(MS_PER_PIXEL * std::sqrt(dx * dx + dy * dy));
}

// Whether a shot's ball jumps from mob to mob instead, each hit when
// it gets there: Energy Orb and Spark (FUN_0094289f).
inline bool hops(int32_t skill)
{
return skill == 5121002 || skill == 15111006;
}

// Where the ball's first jump starts: 25 px ahead of `origin`.
inline Point<int16_t> hop_start(Point<int16_t> origin, bool toleft)
{
return { static_cast<int16_t>(origin.x() + (toleft ? -25 : 25)), origin.y() };
}

// Where the ball jumps to on a mob with these bounds: their middle.
inline Point<int16_t> hop_point(Rectangle<int16_t> body)
{
return {
static_cast<int16_t>((body.left() + body.right()) / 2),
static_cast<int16_t>((body.top() + body.bottom()) / 2)
};
}

// How long the ball's jump from `from` to `to` takes: 1.25 ms per
// pixel, Spark's 2. Each mob is hit when the ball gets there, a jump
// after the last.
inline uint16_t hop_time(int32_t skill, Point<int16_t> from, Point<int16_t> to)
{
double dx = to.x() - from.x();
double dy = to.y() - from.y();
double per_pixel = skill == 15111006 ? 2.0 : 1.25;

return static_cast<uint16_t>(per_pixel * std::sqrt(dx * dx + dy * dy));
}

// How long after the attack's hit frame the damage at `hit` lands.
// It only counts the horizontal distance.
inline uint16_t hit_delay(Point<int16_t> origin, Point<int16_t> hit)
Expand Down
15 changes: 15 additions & 0 deletions HeavenClient/Gameplay/Combat/Skill.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,27 @@
#include "Heal.h"
#include "Prepare.h"

#include "../../Character/Char.h"
#include "../../Character/SkillId.h"
#include "../../Data/EquipData.h"
#include "../../Data/SkillData.h"
#include "../../Util/Misc.h"

#include <nlnx/nx.hpp>

namespace ms
{
namespace
{
// The percent the user's weapon gives a spell of `element`.
int32_t elemboost(const Char &user, uint8_t element)
{
int32_t weapon = user.get_look().get_equips().get_weapon();

return weapon > 0 ? EquipData::get(weapon).get_elemboost(element) : 100;
}
}

Skill::Skill(int32_t id) : skillid(id)
{
const SkillData &data = SkillData::get(skillid);
Expand Down Expand Up @@ -229,12 +242,14 @@ namespace ms
{
attack.heal = stats.hp;
attack.element = SkillData::get(skillid).get_element();
attack.elemboost = elemboost(user, attack.element);
attack.damagetype = Attack::DMG_MAGIC;
} else if (stats.matk)
{
attack.matk += stats.matk;
attack.mastery = stats.mastery;
attack.element = SkillData::get(skillid).get_element();
attack.elemboost = elemboost(user, attack.element);
attack.damagetype = Attack::DMG_MAGIC;
} else
{
Expand Down
Loading
Loading